XmlFlattener.cpp revision 330edcdf1316ed599fe0eb16a64330821fd92f18
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
38constexpr const char16_t* kSchemaAndroid = u"http://schemas.android.com/apk/res/android";
39
40struct AttributeValueFlattener : ValueVisitor {
41    AttributeValueFlattener(
42            std::shared_ptr<IResolver> resolver, SourceLogger* logger,
43            android::Res_value* outValue, std::shared_ptr<XmlPullParser> parser, bool* outError,
44            StringPool::Ref rawValue, std::u16string* defaultPackage,
45            std::vector<std::pair<StringPool::Ref, android::ResStringPool_ref*>>* outStringRefs) :
46            mResolver(resolver), mLogger(logger), mOutValue(outValue), mParser(parser),
47            mError(outError), mRawValue(rawValue), mDefaultPackage(defaultPackage),
48            mStringRefs(outStringRefs) {
49    }
50
51    void visit(Reference& reference, ValueVisitorArgs&) override {
52        // First see if we can convert the package name from a prefix to a real
53        // package name.
54        ResourceName aliasedName = reference.name;
55
56        if (!reference.name.package.empty()) {
57            // Only if we specified a package do we look for its alias.
58            mParser->applyPackageAlias(&reference.name.package, *mDefaultPackage);
59        } else {
60            reference.name.package = *mDefaultPackage;
61        }
62
63        Maybe<ResourceId> result = mResolver->findId(reference.name);
64        if (!result || !result.value().isValid()) {
65            std::ostream& out = mLogger->error(mParser->getLineNumber())
66                    << "unresolved reference '"
67                    << aliasedName
68                    << "'";
69            if (aliasedName != reference.name) {
70                out << " (aka '" << reference.name << "')";
71            }
72            out << "'." << std::endl;
73            *mError = true;
74        } else {
75            reference.id = result.value();
76            reference.flatten(*mOutValue);
77        }
78    }
79
80    void visit(String& string, ValueVisitorArgs&) override {
81        mOutValue->dataType = android::Res_value::TYPE_STRING;
82        mStringRefs->emplace_back(
83                mRawValue,
84                reinterpret_cast<android::ResStringPool_ref*>(mOutValue->data));
85    }
86
87    void visitItem(Item& item, ValueVisitorArgs&) override {
88        item.flatten(*mOutValue);
89    }
90
91private:
92    std::shared_ptr<IResolver> mResolver;
93    SourceLogger* mLogger;
94    android::Res_value* mOutValue;
95    std::shared_ptr<XmlPullParser> mParser;
96    bool* mError;
97    StringPool::Ref mRawValue;
98    std::u16string* mDefaultPackage;
99    std::vector<std::pair<StringPool::Ref, android::ResStringPool_ref*>>* mStringRefs;
100};
101
102struct XmlAttribute {
103    uint32_t resourceId;
104    const XmlPullParser::Attribute* xmlAttr;
105    const Attribute* attr;
106    StringPool::Ref nameRef;
107};
108
109static bool lessAttributeId(const XmlAttribute& a, uint32_t id) {
110    return a.resourceId < id;
111}
112
113XmlFlattener::XmlFlattener(const std::shared_ptr<ResourceTable>& table,
114                           const std::shared_ptr<IResolver>& resolver) :
115        mTable(table), mResolver(resolver) {
116}
117
118/**
119 * Reads events from the parser and writes to a BigBuffer. The binary XML file
120 * expects the StringPool to appear first, but we haven't collected the strings yet. We
121 * write to a temporary BigBuffer while parsing the input, adding strings we encounter
122 * to the StringPool. At the end, we write the StringPool to the given BigBuffer and
123 * then move the data from the temporary BigBuffer into the given one. This incurs no
124 * copies as the given BigBuffer simply takes ownership of the data.
125 */
126Maybe<size_t> XmlFlattener::flatten(const Source& source,
127                                    const std::shared_ptr<XmlPullParser>& parser,
128                                    BigBuffer* outBuffer, Options options) {
129    SourceLogger logger(source);
130    StringPool pool;
131    bool error = false;
132
133    size_t smallestStrippedAttributeSdk = std::numeric_limits<size_t>::max();
134
135    // Attribute names are stored without packages, but we use
136    // their StringPool index to lookup their resource IDs.
137    // This will cause collisions, so we can't dedupe
138    // attribute names from different packages. We use separate
139    // pools that we later combine.
140    std::map<std::u16string, StringPool> packagePools;
141
142    // Attribute resource IDs are stored in the same order
143    // as the attribute names appear in the StringPool.
144    // Since the StringPool contains more than just attribute
145    // names, to maintain a tight packing of resource IDs,
146    // we must ensure that attribute names appear first
147    // in our StringPool. For this, we assign a low priority
148    // (0xffffffff) to non-attribute strings. Attribute
149    // names will be stored along with a priority equal
150    // to their resource ID so that they are ordered.
151    StringPool::Context lowPriority { 0xffffffffu };
152
153    // Once we sort the StringPool, we can assign the updated indices
154    // to the correct data locations.
155    std::vector<std::pair<StringPool::Ref, android::ResStringPool_ref*>> stringRefs;
156
157    // Since we don't know the size of the final StringPool, we write to this
158    // temporary BigBuffer, which we will append to outBuffer later.
159    BigBuffer out(1024);
160    while (XmlPullParser::isGoodEvent(parser->next())) {
161        XmlPullParser::Event event = parser->getEvent();
162        switch (event) {
163            case XmlPullParser::Event::kStartNamespace:
164            case XmlPullParser::Event::kEndNamespace: {
165                const size_t startIndex = out.size();
166                android::ResXMLTree_node* node = out.nextBlock<android::ResXMLTree_node>();
167                if (event == XmlPullParser::Event::kStartNamespace) {
168                    node->header.type = android::RES_XML_START_NAMESPACE_TYPE;
169                } else {
170                    node->header.type = android::RES_XML_END_NAMESPACE_TYPE;
171                }
172
173                node->header.headerSize = sizeof(*node);
174                node->lineNumber = parser->getLineNumber();
175                node->comment.index = -1;
176
177                android::ResXMLTree_namespaceExt* ns =
178                        out.nextBlock<android::ResXMLTree_namespaceExt>();
179                stringRefs.emplace_back(
180                        pool.makeRef(parser->getNamespacePrefix(), lowPriority), &ns->prefix);
181                stringRefs.emplace_back(
182                        pool.makeRef(parser->getNamespaceUri(), lowPriority), &ns->uri);
183
184                out.align4();
185                node->header.size = out.size() - startIndex;
186                break;
187            }
188
189            case XmlPullParser::Event::kStartElement: {
190                const size_t startIndex = out.size();
191                android::ResXMLTree_node* node = out.nextBlock<android::ResXMLTree_node>();
192                node->header.type = android::RES_XML_START_ELEMENT_TYPE;
193                node->header.headerSize = sizeof(*node);
194                node->lineNumber = parser->getLineNumber();
195                node->comment.index = -1;
196
197                android::ResXMLTree_attrExt* elem = out.nextBlock<android::ResXMLTree_attrExt>();
198                if (!parser->getElementNamespace().empty()) {
199                    stringRefs.emplace_back(
200                            pool.makeRef(parser->getElementNamespace(), lowPriority), &elem->ns);
201                } else {
202                    // The device doesn't think a string of size 0 is the same as null.
203                    elem->ns.index = -1;
204                }
205                stringRefs.emplace_back(
206                        pool.makeRef(parser->getElementName(), lowPriority), &elem->name);
207                elem->attributeStart = sizeof(*elem);
208                elem->attributeSize = sizeof(android::ResXMLTree_attribute);
209
210                // The resource system expects attributes to be sorted by resource ID.
211                std::vector<XmlAttribute> sortedAttributes;
212                uint32_t nextAttributeId = 0;
213                const auto endAttrIter = parser->endAttributes();
214                for (auto attrIter = parser->beginAttributes();
215                        attrIter != endAttrIter;
216                        ++attrIter) {
217                    uint32_t id;
218                    StringPool::Ref nameRef;
219                    const Attribute* attr = nullptr;
220
221                    if (options.maxSdkAttribute && attrIter->namespaceUri == kSchemaAndroid) {
222                        size_t sdkVersion = findAttributeSdkLevel(attrIter->name);
223                        if (sdkVersion > options.maxSdkAttribute.value()) {
224                            // We will silently omit this attribute
225                            smallestStrippedAttributeSdk =
226                                    std::min(smallestStrippedAttributeSdk, sdkVersion);
227                            continue;
228                        }
229                    }
230
231                    ResourceNameRef genIdName;
232                    bool create = false;
233                    bool privateRef = false;
234                    if (mTable && ResourceParser::tryParseReference(attrIter->value, &genIdName,
235                            &create, &privateRef) && create) {
236                        mTable->addResource(genIdName, {}, source.line(parser->getLineNumber()),
237                                            util::make_unique<Id>());
238                    }
239
240
241                    Maybe<std::u16string> package = util::extractPackageFromNamespace(
242                            attrIter->namespaceUri);
243                    if (!package || !mResolver) {
244                        // Attributes that have no resource ID (because they don't belong to a
245                        // package) should appear after those that do have resource IDs. Assign
246                        // them some integer value that will appear after.
247                        id = 0x80000000u | nextAttributeId++;
248                        nameRef = pool.makeRef(attrIter->name, StringPool::Context{ id });
249
250                    } else {
251                        // Find the Attribute object via our Resolver.
252                        ResourceName attrName = {
253                                package.value(), ResourceType::kAttr, attrIter->name };
254
255                        if (attrName.package.empty()) {
256                            attrName.package = options.defaultPackage;
257                        }
258
259                        Maybe<IResolver::Entry> result = mResolver->findAttribute(attrName);
260                        if (!result || !result.value().id.isValid()) {
261                            logger.error(parser->getLineNumber())
262                                    << "unresolved attribute '"
263                                    << attrName
264                                    << "'."
265                                    << std::endl;
266                            error = true;
267                            continue;
268                        }
269
270                        if (!result.value().attr) {
271                            logger.error(parser->getLineNumber())
272                                    << "not a valid attribute '"
273                                    << attrName
274                                    << "'."
275                                    << std::endl;
276                            error = true;
277                            continue;
278                        }
279
280                        id = result.value().id.id;
281                        attr = result.value().attr;
282
283                        // Put the attribute name into a package specific pool, since we don't
284                        // want to collapse names from different packages.
285                        nameRef = packagePools[package.value()].makeRef(
286                                attrIter->name, StringPool::Context{ id });
287                    }
288
289                    // Insert the attribute into the sorted vector.
290                    auto iter = std::lower_bound(sortedAttributes.begin(), sortedAttributes.end(),
291                                                 id, lessAttributeId);
292                    sortedAttributes.insert(iter, XmlAttribute{ id, &*attrIter, attr, nameRef });
293                }
294
295                if (error) {
296                    break;
297                }
298
299                // Now that we have filtered out some attributes, get the final count.
300                elem->attributeCount = sortedAttributes.size();
301
302                // Flatten the sorted attributes.
303                uint16_t attributeIndex = 1;
304                for (auto entry : sortedAttributes) {
305                    android::ResXMLTree_attribute* attr =
306                            out.nextBlock<android::ResXMLTree_attribute>();
307                    if (!entry.xmlAttr->namespaceUri.empty()) {
308                        stringRefs.emplace_back(
309                                pool.makeRef(entry.xmlAttr->namespaceUri, lowPriority), &attr->ns);
310                    } else {
311                        attr->ns.index = -1;
312                    }
313
314                    StringPool::Ref rawValueRef = pool.makeRef(entry.xmlAttr->value, lowPriority);
315
316                    stringRefs.emplace_back(entry.nameRef, &attr->name);
317
318                    if (options.keepRawValues) {
319                        stringRefs.emplace_back(rawValueRef, &attr->rawValue);
320                    } else {
321                        attr->rawValue.index = -1;
322                    }
323
324                    // Assign the indices for specific attributes.
325                    if (entry.xmlAttr->namespaceUri == kSchemaAndroid &&
326                            entry.xmlAttr->name == u"id") {
327                        elem->idIndex = attributeIndex;
328                    } else if (entry.xmlAttr->namespaceUri.empty()) {
329                        if (entry.xmlAttr->name == u"class") {
330                            elem->classIndex = attributeIndex;
331                        } else if (entry.xmlAttr->name == u"style") {
332                            elem->styleIndex = attributeIndex;
333                        }
334                    }
335
336                    std::unique_ptr<Item> value;
337                    if (entry.attr) {
338                        value = ResourceParser::parseItemForAttribute(entry.xmlAttr->value,
339                                                                      *entry.attr);
340                    } else {
341                        bool create = false;
342                        value = ResourceParser::tryParseReference(entry.xmlAttr->value, &create);
343                    }
344
345                    if (mResolver && value) {
346                        AttributeValueFlattener flattener(
347                                mResolver,
348                                &logger,
349                                &attr->typedValue,
350                                parser,
351                                &error,
352                                rawValueRef,
353                                &options.defaultPackage,
354                                &stringRefs);
355                        value->accept(flattener, {});
356                    } else if (!value && entry.attr &&
357                            !(entry.attr->typeMask & android::ResTable_map::TYPE_STRING)) {
358                        logger.error(parser->getLineNumber())
359                                << "'"
360                                << *rawValueRef
361                                << "' is not compatible with attribute "
362                                << *entry.attr
363                                << "."
364                                << std::endl;
365                        error = true;
366                    } else {
367                        attr->typedValue.dataType = android::Res_value::TYPE_STRING;
368                        if (!options.keepRawValues) {
369                            // Don't set the string twice.
370                            stringRefs.emplace_back(rawValueRef, &attr->rawValue);
371                        }
372                        stringRefs.emplace_back(rawValueRef,
373                                reinterpret_cast<android::ResStringPool_ref*>(
374                                        &attr->typedValue.data));
375                    }
376                    attr->typedValue.size = sizeof(attr->typedValue);
377                }
378
379                out.align4();
380                node->header.size = out.size() - startIndex;
381                break;
382            }
383
384            case XmlPullParser::Event::kEndElement: {
385                const size_t startIndex = out.size();
386                android::ResXMLTree_node* node = out.nextBlock<android::ResXMLTree_node>();
387                node->header.type = android::RES_XML_END_ELEMENT_TYPE;
388                node->header.headerSize = sizeof(*node);
389                node->lineNumber = parser->getLineNumber();
390                node->comment.index = -1;
391
392                android::ResXMLTree_endElementExt* elem =
393                        out.nextBlock<android::ResXMLTree_endElementExt>();
394                stringRefs.emplace_back(
395                        pool.makeRef(parser->getElementNamespace(), lowPriority), &elem->ns);
396                stringRefs.emplace_back(
397                        pool.makeRef(parser->getElementName(), lowPriority), &elem->name);
398
399                out.align4();
400                node->header.size = out.size() - startIndex;
401                break;
402            }
403
404            case XmlPullParser::Event::kText: {
405                StringPiece16 text = util::trimWhitespace(parser->getText());
406                if (text.empty()) {
407                    break;
408                }
409
410                const size_t startIndex = out.size();
411                android::ResXMLTree_node* node = out.nextBlock<android::ResXMLTree_node>();
412                node->header.type = android::RES_XML_CDATA_TYPE;
413                node->header.headerSize = sizeof(*node);
414                node->lineNumber = parser->getLineNumber();
415                node->comment.index = -1;
416
417                android::ResXMLTree_cdataExt* elem = out.nextBlock<android::ResXMLTree_cdataExt>();
418                stringRefs.emplace_back(pool.makeRef(text, lowPriority), &elem->data);
419
420                out.align4();
421                node->header.size = out.size() - startIndex;
422                break;
423            }
424
425            default:
426                break;
427        }
428
429    }
430    out.align4();
431
432    if (error) {
433        return {};
434    }
435
436    if (parser->getEvent() == XmlPullParser::Event::kBadDocument) {
437        logger.error(parser->getLineNumber())
438                << parser->getLastError()
439                << std::endl;
440        return {};
441    }
442
443    // Merge the package pools into the main pool.
444    for (auto& packagePoolEntry : packagePools) {
445        pool.merge(std::move(packagePoolEntry.second));
446    }
447
448    // Sort so that attribute resource IDs show up first.
449    pool.sort([](const StringPool::Entry& a, const StringPool::Entry& b) -> bool {
450        return a.context.priority < b.context.priority;
451    });
452
453    // Now we flatten the string pool references into the correct places.
454    for (const auto& refEntry : stringRefs) {
455        refEntry.second->index = refEntry.first.getIndex();
456    }
457
458    // Write the XML header.
459    const size_t beforeXmlTreeIndex = outBuffer->size();
460    android::ResXMLTree_header* header = outBuffer->nextBlock<android::ResXMLTree_header>();
461    header->header.type = android::RES_XML_TYPE;
462    header->header.headerSize = sizeof(*header);
463
464    // Flatten the StringPool.
465    StringPool::flattenUtf16(outBuffer, pool);
466
467    // Write the array of resource IDs, indexed by StringPool order.
468    const size_t beforeResIdMapIndex = outBuffer->size();
469    android::ResChunk_header* resIdMapChunk = outBuffer->nextBlock<android::ResChunk_header>();
470    resIdMapChunk->type = android::RES_XML_RESOURCE_MAP_TYPE;
471    resIdMapChunk->headerSize = sizeof(*resIdMapChunk);
472    for (const auto& str : pool) {
473        ResourceId id { str->context.priority };
474        if (!id.isValid()) {
475            // When we see the first non-resource ID,
476            // we're done.
477            break;
478        }
479
480        uint32_t* flatId = outBuffer->nextBlock<uint32_t>();
481        *flatId = id.id;
482    }
483    resIdMapChunk->size = outBuffer->size() - beforeResIdMapIndex;
484
485    // Move the temporary BigBuffer into outBuffer.
486    outBuffer->appendBuffer(std::move(out));
487
488    header->header.size = outBuffer->size() - beforeXmlTreeIndex;
489
490    if (smallestStrippedAttributeSdk == std::numeric_limits<size_t>::max()) {
491        // Nothing was stripped
492        return 0u;
493    }
494    return smallestStrippedAttributeSdk;
495}
496
497} // namespace aapt
498