XmlFlattener.cpp revision 6f6ceb7e1456698b1f33e04536bfb3227f9fcfcb
1/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include "BigBuffer.h"
18#include "Logger.h"
19#include "Maybe.h"
20#include "Resolver.h"
21#include "Resource.h"
22#include "ResourceParser.h"
23#include "ResourceValues.h"
24#include "SdkConstants.h"
25#include "Source.h"
26#include "StringPool.h"
27#include "Util.h"
28#include "XmlFlattener.h"
29
30#include <androidfw/ResourceTypes.h>
31#include <limits>
32#include <map>
33#include <string>
34#include <vector>
35
36namespace aapt {
37
38struct AttributeValueFlattener : ValueVisitor {
39    struct Args : ValueVisitorArgs {
40        Args(std::shared_ptr<Resolver> r, SourceLogger& s, android::Res_value& oV,
41                std::shared_ptr<XmlPullParser> p, bool& e, StringPool::Ref& rV,
42                std::vector<std::pair<StringPool::Ref, android::ResStringPool_ref*>>& sR) :
43                resolver(r), logger(s), outValue(oV), parser(p), error(e), rawValue(rV),
44                stringRefs(sR) {
45        }
46
47        std::shared_ptr<Resolver> resolver;
48        SourceLogger& logger;
49        android::Res_value& outValue;
50        std::shared_ptr<XmlPullParser> parser;
51        bool& error;
52        StringPool::Ref& rawValue;
53        std::vector<std::pair<StringPool::Ref, android::ResStringPool_ref*>>& stringRefs;
54    };
55
56    void visit(Reference& reference, ValueVisitorArgs& a) override {
57        Args& args = static_cast<Args&>(a);
58
59        Maybe<ResourceId> result = args.resolver->findId(reference.name);
60        if (!result || !result.value().isValid()) {
61            args.logger.error(args.parser->getLineNumber())
62                    << "unresolved reference '"
63                    << reference.name
64                    << "'."
65                    << std::endl;
66            args.error = true;
67        } else {
68            reference.id = result.value();
69            reference.flatten(args.outValue);
70        }
71    }
72
73    void visit(String& string, ValueVisitorArgs& a) override {
74        Args& args = static_cast<Args&>(a);
75
76        args.outValue.dataType = android::Res_value::TYPE_STRING;
77        args.stringRefs.emplace_back(args.rawValue,
78                reinterpret_cast<android::ResStringPool_ref*>(&args.outValue.data));
79    }
80
81    void visitItem(Item& item, ValueVisitorArgs& a) override {
82        Args& args = static_cast<Args&>(a);
83        item.flatten(args.outValue);
84    }
85};
86
87struct XmlAttribute {
88    uint32_t resourceId;
89    const XmlPullParser::Attribute* xmlAttr;
90    const Attribute* attr;
91    StringPool::Ref nameRef;
92};
93
94static bool lessAttributeId(const XmlAttribute& a, uint32_t id) {
95    return a.resourceId < id;
96}
97
98XmlFlattener::XmlFlattener(const std::shared_ptr<Resolver>& resolver) : mResolver(resolver) {
99}
100
101/**
102 * Reads events from the parser and writes to a BigBuffer. The binary XML file
103 * expects the StringPool to appear first, but we haven't collected the strings yet. We
104 * write to a temporary BigBuffer while parsing the input, adding strings we encounter
105 * to the StringPool. At the end, we write the StringPool to the given BigBuffer and
106 * then move the data from the temporary BigBuffer into the given one. This incurs no
107 * copies as the given BigBuffer simply takes ownership of the data.
108 */
109Maybe<size_t> XmlFlattener::flatten(const Source& source,
110                                    const std::shared_ptr<XmlPullParser>& parser,
111                                    BigBuffer* outBuffer, Options options) {
112    SourceLogger logger(source);
113    StringPool pool;
114    bool error = false;
115
116    size_t smallestStrippedAttributeSdk = std::numeric_limits<size_t>::max();
117
118    // Attribute names are stored without packages, but we use
119    // their StringPool index to lookup their resource IDs.
120    // This will cause collisions, so we can't dedupe
121    // attribute names from different packages. We use separate
122    // pools that we later combine.
123    std::map<std::u16string, StringPool> packagePools;
124
125    // Attribute resource IDs are stored in the same order
126    // as the attribute names appear in the StringPool.
127    // Since the StringPool contains more than just attribute
128    // names, to maintain a tight packing of resource IDs,
129    // we must ensure that attribute names appear first
130    // in our StringPool. For this, we assign a low priority
131    // (0xffffffff) to non-attribute strings. Attribute
132    // names will be stored along with a priority equal
133    // to their resource ID so that they are ordered.
134    StringPool::Context lowPriority { 0xffffffffu };
135
136    // Once we sort the StringPool, we can assign the updated indices
137    // to the correct data locations.
138    std::vector<std::pair<StringPool::Ref, android::ResStringPool_ref*>> stringRefs;
139
140    // Since we don't know the size of the final StringPool, we write to this
141    // temporary BigBuffer, which we will append to outBuffer later.
142    BigBuffer out(1024);
143    while (XmlPullParser::isGoodEvent(parser->next())) {
144        XmlPullParser::Event event = parser->getEvent();
145        switch (event) {
146            case XmlPullParser::Event::kStartNamespace:
147            case XmlPullParser::Event::kEndNamespace: {
148                const size_t startIndex = out.size();
149                android::ResXMLTree_node* node = out.nextBlock<android::ResXMLTree_node>();
150                if (event == XmlPullParser::Event::kStartNamespace) {
151                    node->header.type = android::RES_XML_START_NAMESPACE_TYPE;
152                } else {
153                    node->header.type = android::RES_XML_END_NAMESPACE_TYPE;
154                }
155
156                node->header.headerSize = sizeof(*node);
157                node->lineNumber = parser->getLineNumber();
158                node->comment.index = -1;
159
160                android::ResXMLTree_namespaceExt* ns =
161                        out.nextBlock<android::ResXMLTree_namespaceExt>();
162                stringRefs.emplace_back(
163                        pool.makeRef(parser->getNamespacePrefix(), lowPriority), &ns->prefix);
164                stringRefs.emplace_back(
165                        pool.makeRef(parser->getNamespaceUri(), lowPriority), &ns->uri);
166
167                out.align4();
168                node->header.size = out.size() - startIndex;
169                break;
170            }
171
172            case XmlPullParser::Event::kStartElement: {
173                const size_t startIndex = out.size();
174                android::ResXMLTree_node* node = out.nextBlock<android::ResXMLTree_node>();
175                node->header.type = android::RES_XML_START_ELEMENT_TYPE;
176                node->header.headerSize = sizeof(*node);
177                node->lineNumber = parser->getLineNumber();
178                node->comment.index = -1;
179
180                android::ResXMLTree_attrExt* elem = out.nextBlock<android::ResXMLTree_attrExt>();
181                stringRefs.emplace_back(
182                        pool.makeRef(parser->getElementNamespace(), lowPriority), &elem->ns);
183                stringRefs.emplace_back(
184                        pool.makeRef(parser->getElementName(), lowPriority), &elem->name);
185                elem->attributeStart = sizeof(*elem);
186                elem->attributeSize = sizeof(android::ResXMLTree_attribute);
187
188                // The resource system expects attributes to be sorted by resource ID.
189                std::vector<XmlAttribute> sortedAttributes;
190                uint32_t nextAttributeId = 0;
191                const auto endAttrIter = parser->endAttributes();
192                for (auto attrIter = parser->beginAttributes();
193                     attrIter != endAttrIter;
194                     ++attrIter) {
195                    uint32_t id;
196                    StringPool::Ref nameRef;
197                    const Attribute* attr = nullptr;
198                    if (attrIter->namespaceUri.empty()) {
199                        // Attributes that have no resource ID (because they don't belong to a
200                        // package) should appear after those that do have resource IDs. Assign
201                        // them some/ integer value that will appear after.
202                        id = 0x80000000u | nextAttributeId++;
203                        nameRef = pool.makeRef(attrIter->name, StringPool::Context{ id });
204                    } else {
205                        StringPiece16 package;
206                        if (attrIter->namespaceUri == u"http://schemas.android.com/apk/res-auto") {
207                            package = mResolver->getDefaultPackage();
208                        } else {
209                            // TODO(adamlesinski): Extract package from namespace.
210                            // The package name appears like so:
211                            // http://schemas.android.com/apk/res/<package name>
212                            package = u"android";
213                        }
214
215                        // Find the Attribute object via our Resolver.
216                        ResourceName attrName = {
217                                package.toString(), ResourceType::kAttr, attrIter->name };
218                        Maybe<Resolver::Entry> result = mResolver->findAttribute(attrName);
219                        if (!result || !result.value().id.isValid()) {
220                            logger.error(parser->getLineNumber())
221                                    << "unresolved attribute '"
222                                    << attrName
223                                    << "'."
224                                    << std::endl;
225                            error = true;
226                            continue;
227                        }
228
229                        if (!result.value().attr) {
230                            logger.error(parser->getLineNumber())
231                                    << "not a valid attribute '"
232                                    << attrName
233                                    << "'."
234                                    << std::endl;
235                            error = true;
236                            continue;
237                        }
238
239                        if (options.maxSdkAttribute && package == u"android") {
240                            size_t sdkVersion = findAttributeSdkLevel(attrIter->name);
241                            if (sdkVersion > options.maxSdkAttribute.value()) {
242                                // We will silently omit this attribute
243                                smallestStrippedAttributeSdk =
244                                        std::min(smallestStrippedAttributeSdk, sdkVersion);
245                                continue;
246                            }
247                        }
248
249                        id = result.value().id.id;
250                        attr = result.value().attr;
251
252                        // Put the attribute name into a package specific pool, since we don't
253                        // want to collapse names from different packages.
254                        nameRef = packagePools[package.toString()].makeRef(
255                                attrIter->name, StringPool::Context{ id });
256                    }
257
258                    // Insert the attribute into the sorted vector.
259                    auto iter = std::lower_bound(sortedAttributes.begin(), sortedAttributes.end(),
260                                                 id, lessAttributeId);
261                    sortedAttributes.insert(iter, XmlAttribute{ id, &*attrIter, attr, nameRef });
262                }
263
264                if (error) {
265                    break;
266                }
267
268                // Now that we have filtered out some attributes, get the final count.
269                elem->attributeCount = sortedAttributes.size();
270
271                // Flatten the sorted attributes.
272                for (auto entry : sortedAttributes) {
273                    android::ResXMLTree_attribute* attr =
274                            out.nextBlock<android::ResXMLTree_attribute>();
275                    stringRefs.emplace_back(
276                            pool.makeRef(entry.xmlAttr->namespaceUri, lowPriority), &attr->ns);
277                    StringPool::Ref rawValueRef = pool.makeRef(entry.xmlAttr->value, lowPriority);
278                    stringRefs.emplace_back(rawValueRef, &attr->rawValue);
279                    stringRefs.emplace_back(entry.nameRef, &attr->name);
280
281                    if (entry.attr) {
282                        std::unique_ptr<Item> value = ResourceParser::parseItemForAttribute(
283                                entry.xmlAttr->value, *entry.attr, mResolver->getDefaultPackage());
284                        if (value) {
285                            AttributeValueFlattener flattener;
286                            value->accept(flattener, AttributeValueFlattener::Args{
287                                    mResolver,
288                                    logger,
289                                    attr->typedValue,
290                                    parser,
291                                    error,
292                                    rawValueRef,
293                                    stringRefs
294                            });
295                        } else if (!(entry.attr->typeMask & android::ResTable_map::TYPE_STRING)) {
296                            logger.error(parser->getLineNumber())
297                                    << "'"
298                                    << *rawValueRef
299                                    << "' is not compatible with attribute "
300                                    << *entry.attr
301                                    << "."
302                                    << std::endl;
303                            error = true;
304                        } else {
305                            attr->typedValue.dataType = android::Res_value::TYPE_STRING;
306                            stringRefs.emplace_back(rawValueRef,
307                                    reinterpret_cast<android::ResStringPool_ref*>(
308                                            &attr->typedValue.data));
309                        }
310                    } else {
311                        attr->typedValue.dataType = android::Res_value::TYPE_STRING;
312                        stringRefs.emplace_back(rawValueRef,
313                                reinterpret_cast<android::ResStringPool_ref*>(
314                                        &attr->typedValue.data));
315                    }
316                    attr->typedValue.size = sizeof(attr->typedValue);
317                }
318
319                out.align4();
320                node->header.size = out.size() - startIndex;
321                break;
322            }
323
324            case XmlPullParser::Event::kEndElement: {
325                const size_t startIndex = out.size();
326                android::ResXMLTree_node* node = out.nextBlock<android::ResXMLTree_node>();
327                node->header.type = android::RES_XML_END_ELEMENT_TYPE;
328                node->header.headerSize = sizeof(*node);
329                node->lineNumber = parser->getLineNumber();
330                node->comment.index = -1;
331
332                android::ResXMLTree_endElementExt* elem =
333                        out.nextBlock<android::ResXMLTree_endElementExt>();
334                stringRefs.emplace_back(
335                        pool.makeRef(parser->getElementNamespace(), lowPriority), &elem->ns);
336                stringRefs.emplace_back(
337                        pool.makeRef(parser->getElementName(), lowPriority), &elem->name);
338
339                out.align4();
340                node->header.size = out.size() - startIndex;
341                break;
342            }
343
344            case XmlPullParser::Event::kText: {
345                StringPiece16 text = util::trimWhitespace(parser->getText());
346                if (text.empty()) {
347                    break;
348                }
349
350                const size_t startIndex = out.size();
351                android::ResXMLTree_node* node = out.nextBlock<android::ResXMLTree_node>();
352                node->header.type = android::RES_XML_CDATA_TYPE;
353                node->header.headerSize = sizeof(*node);
354                node->lineNumber = parser->getLineNumber();
355                node->comment.index = -1;
356
357                android::ResXMLTree_cdataExt* elem = out.nextBlock<android::ResXMLTree_cdataExt>();
358                stringRefs.emplace_back(pool.makeRef(text, lowPriority), &elem->data);
359
360                out.align4();
361                node->header.size = out.size() - startIndex;
362                break;
363            }
364
365            default:
366                break;
367        }
368
369    }
370    out.align4();
371
372    if (error) {
373        return {};
374    }
375
376    if (parser->getEvent() == XmlPullParser::Event::kBadDocument) {
377        logger.error(parser->getLineNumber())
378                << parser->getLastError()
379                << std::endl;
380        return {};
381    }
382
383    // Merge the package pools into the main pool.
384    for (auto& packagePoolEntry : packagePools) {
385        pool.merge(std::move(packagePoolEntry.second));
386    }
387
388    // Sort so that attribute resource IDs show up first.
389    pool.sort([](const StringPool::Entry& a, const StringPool::Entry& b) -> bool {
390        return a.context.priority < b.context.priority;
391    });
392
393    // Now we flatten the string pool references into the correct places.
394    for (const auto& refEntry : stringRefs) {
395        refEntry.second->index = refEntry.first.getIndex();
396    }
397
398    // Write the XML header.
399    const size_t beforeXmlTreeIndex = outBuffer->size();
400    android::ResXMLTree_header* header = outBuffer->nextBlock<android::ResXMLTree_header>();
401    header->header.type = android::RES_XML_TYPE;
402    header->header.headerSize = sizeof(*header);
403
404    // Write the array of resource IDs, indexed by StringPool order.
405    const size_t beforeResIdMapIndex = outBuffer->size();
406    android::ResChunk_header* resIdMapChunk = outBuffer->nextBlock<android::ResChunk_header>();
407    resIdMapChunk->type = android::RES_XML_RESOURCE_MAP_TYPE;
408    resIdMapChunk->headerSize = sizeof(*resIdMapChunk);
409    for (const auto& str : pool) {
410        ResourceId id { str->context.priority };
411        if (!id.isValid()) {
412            // When we see the first non-resource ID,
413            // we're done.
414            break;
415        }
416
417        uint32_t* flatId = outBuffer->nextBlock<uint32_t>();
418        *flatId = id.id;
419    }
420    resIdMapChunk->size = outBuffer->size() - beforeResIdMapIndex;
421
422    // Flatten the StringPool.
423    StringPool::flattenUtf8(outBuffer, pool);
424
425    // Move the temporary BigBuffer into outBuffer->
426    outBuffer->appendBuffer(std::move(out));
427
428    header->header.size = outBuffer->size() - beforeXmlTreeIndex;
429
430    if (smallestStrippedAttributeSdk == std::numeric_limits<size_t>::max()) {
431        // Nothing was stripped
432        return 0u;
433    }
434    return smallestStrippedAttributeSdk;
435}
436
437} // namespace aapt
438