Home | History | Annotate | Download | only in flatten
      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 "SdkConstants.h"
     18 #include "flatten/ChunkWriter.h"
     19 #include "flatten/ResourceTypeExtensions.h"
     20 #include "flatten/XmlFlattener.h"
     21 #include "xml/XmlDom.h"
     22 
     23 #include <androidfw/ResourceTypes.h>
     24 #include <algorithm>
     25 #include <utils/misc.h>
     26 #include <vector>
     27 
     28 using namespace android;
     29 
     30 namespace aapt {
     31 
     32 namespace {
     33 
     34 constexpr uint32_t kLowPriority = 0xffffffffu;
     35 
     36 struct XmlFlattenerVisitor : public xml::Visitor {
     37     using xml::Visitor::visit;
     38 
     39     BigBuffer* mBuffer;
     40     XmlFlattenerOptions mOptions;
     41     StringPool mPool;
     42     std::map<uint8_t, StringPool> mPackagePools;
     43 
     44     struct StringFlattenDest {
     45         StringPool::Ref ref;
     46         ResStringPool_ref* dest;
     47     };
     48     std::vector<StringFlattenDest> mStringRefs;
     49 
     50     // Scratch vector to filter attributes. We avoid allocations
     51     // making this a member.
     52     std::vector<xml::Attribute*> mFilteredAttrs;
     53 
     54 
     55     XmlFlattenerVisitor(BigBuffer* buffer, XmlFlattenerOptions options) :
     56             mBuffer(buffer), mOptions(options) {
     57     }
     58 
     59     void addString(const StringPiece16& str, uint32_t priority, android::ResStringPool_ref* dest) {
     60         if (!str.empty()) {
     61             mStringRefs.push_back(StringFlattenDest{
     62                     mPool.makeRef(str, StringPool::Context{ priority }),
     63                     dest });
     64         } else {
     65             // The device doesn't think a string of size 0 is the same as null.
     66             dest->index = util::deviceToHost32(-1);
     67         }
     68     }
     69 
     70     void addString(const StringPool::Ref& ref, android::ResStringPool_ref* dest) {
     71         mStringRefs.push_back(StringFlattenDest{ ref, dest });
     72     }
     73 
     74     void writeNamespace(xml::Namespace* node, uint16_t type) {
     75         ChunkWriter writer(mBuffer);
     76 
     77         ResXMLTree_node* flatNode = writer.startChunk<ResXMLTree_node>(type);
     78         flatNode->lineNumber = util::hostToDevice32(node->lineNumber);
     79         flatNode->comment.index = util::hostToDevice32(-1);
     80 
     81         ResXMLTree_namespaceExt* flatNs = writer.nextBlock<ResXMLTree_namespaceExt>();
     82         addString(node->namespacePrefix, kLowPriority, &flatNs->prefix);
     83         addString(node->namespaceUri, kLowPriority, &flatNs->uri);
     84 
     85         writer.finish();
     86     }
     87 
     88     void visit(xml::Namespace* node) override {
     89         writeNamespace(node, android::RES_XML_START_NAMESPACE_TYPE);
     90         xml::Visitor::visit(node);
     91         writeNamespace(node, android::RES_XML_END_NAMESPACE_TYPE);
     92     }
     93 
     94     void visit(xml::Text* node) override {
     95         if (util::trimWhitespace(node->text).empty()) {
     96             // Skip whitespace only text nodes.
     97             return;
     98         }
     99 
    100         ChunkWriter writer(mBuffer);
    101         ResXMLTree_node* flatNode = writer.startChunk<ResXMLTree_node>(RES_XML_CDATA_TYPE);
    102         flatNode->lineNumber = util::hostToDevice32(node->lineNumber);
    103         flatNode->comment.index = util::hostToDevice32(-1);
    104 
    105         ResXMLTree_cdataExt* flatText = writer.nextBlock<ResXMLTree_cdataExt>();
    106         addString(node->text, kLowPriority, &flatText->data);
    107 
    108         writer.finish();
    109     }
    110 
    111     void visit(xml::Element* node) override {
    112         {
    113             ChunkWriter startWriter(mBuffer);
    114             ResXMLTree_node* flatNode = startWriter.startChunk<ResXMLTree_node>(
    115                     RES_XML_START_ELEMENT_TYPE);
    116             flatNode->lineNumber = util::hostToDevice32(node->lineNumber);
    117             flatNode->comment.index = util::hostToDevice32(-1);
    118 
    119             ResXMLTree_attrExt* flatElem = startWriter.nextBlock<ResXMLTree_attrExt>();
    120             addString(node->namespaceUri, kLowPriority, &flatElem->ns);
    121             addString(node->name, kLowPriority, &flatElem->name);
    122             flatElem->attributeStart = util::hostToDevice16(sizeof(*flatElem));
    123             flatElem->attributeSize = util::hostToDevice16(sizeof(ResXMLTree_attribute));
    124 
    125             writeAttributes(node, flatElem, &startWriter);
    126 
    127             startWriter.finish();
    128         }
    129 
    130         xml::Visitor::visit(node);
    131 
    132         {
    133             ChunkWriter endWriter(mBuffer);
    134             ResXMLTree_node* flatEndNode = endWriter.startChunk<ResXMLTree_node>(
    135                     RES_XML_END_ELEMENT_TYPE);
    136             flatEndNode->lineNumber = util::hostToDevice32(node->lineNumber);
    137             flatEndNode->comment.index = util::hostToDevice32(-1);
    138 
    139             ResXMLTree_endElementExt* flatEndElem = endWriter.nextBlock<ResXMLTree_endElementExt>();
    140             addString(node->namespaceUri, kLowPriority, &flatEndElem->ns);
    141             addString(node->name, kLowPriority, &flatEndElem->name);
    142 
    143             endWriter.finish();
    144         }
    145     }
    146 
    147     static bool cmpXmlAttributeById(const xml::Attribute* a, const xml::Attribute* b) {
    148         if (a->compiledAttribute && a->compiledAttribute.value().id) {
    149             if (b->compiledAttribute && b->compiledAttribute.value().id) {
    150                 return a->compiledAttribute.value().id.value() < b->compiledAttribute.value().id.value();
    151             }
    152             return true;
    153         } else if (!b->compiledAttribute) {
    154             int diff = a->namespaceUri.compare(b->namespaceUri);
    155             if (diff < 0) {
    156                 return true;
    157             } else if (diff > 0) {
    158                 return false;
    159             }
    160             return a->name < b->name;
    161         }
    162         return false;
    163     }
    164 
    165     void writeAttributes(xml::Element* node, ResXMLTree_attrExt* flatElem, ChunkWriter* writer) {
    166         mFilteredAttrs.clear();
    167         mFilteredAttrs.reserve(node->attributes.size());
    168 
    169         // Filter the attributes.
    170         for (xml::Attribute& attr : node->attributes) {
    171             if (mOptions.maxSdkLevel && attr.compiledAttribute && attr.compiledAttribute.value().id) {
    172                 size_t sdkLevel = findAttributeSdkLevel(attr.compiledAttribute.value().id.value());
    173                 if (sdkLevel > mOptions.maxSdkLevel.value()) {
    174                     continue;
    175                 }
    176             }
    177             mFilteredAttrs.push_back(&attr);
    178         }
    179 
    180         if (mFilteredAttrs.empty()) {
    181             return;
    182         }
    183 
    184         const ResourceId kIdAttr(0x010100d0);
    185 
    186         std::sort(mFilteredAttrs.begin(), mFilteredAttrs.end(), cmpXmlAttributeById);
    187 
    188         flatElem->attributeCount = util::hostToDevice16(mFilteredAttrs.size());
    189 
    190         ResXMLTree_attribute* flatAttr = writer->nextBlock<ResXMLTree_attribute>(
    191                 mFilteredAttrs.size());
    192         uint16_t attributeIndex = 1;
    193         for (const xml::Attribute* xmlAttr : mFilteredAttrs) {
    194             // Assign the indices for specific attributes.
    195             if (xmlAttr->compiledAttribute && xmlAttr->compiledAttribute.value().id &&
    196                     xmlAttr->compiledAttribute.value().id.value() == kIdAttr) {
    197                 flatElem->idIndex = util::hostToDevice16(attributeIndex);
    198             } else if (xmlAttr->namespaceUri.empty()) {
    199                 if (xmlAttr->name == u"class") {
    200                     flatElem->classIndex = util::hostToDevice16(attributeIndex);
    201                 } else if (xmlAttr->name == u"style") {
    202                     flatElem->styleIndex = util::hostToDevice16(attributeIndex);
    203                 }
    204             }
    205             attributeIndex++;
    206 
    207             // Add the namespaceUri to the list of StringRefs to encode.
    208             addString(xmlAttr->namespaceUri, kLowPriority, &flatAttr->ns);
    209 
    210             flatAttr->rawValue.index = util::hostToDevice32(-1);
    211 
    212             if (!xmlAttr->compiledAttribute || !xmlAttr->compiledAttribute.value().id) {
    213                 // The attribute has no associated ResourceID, so the string order doesn't matter.
    214                 addString(xmlAttr->name, kLowPriority, &flatAttr->name);
    215             } else {
    216                 // Attribute names are stored without packages, but we use
    217                 // their StringPool index to lookup their resource IDs.
    218                 // This will cause collisions, so we can't dedupe
    219                 // attribute names from different packages. We use separate
    220                 // pools that we later combine.
    221                 //
    222                 // Lookup the StringPool for this package and make the reference there.
    223                 const xml::AaptAttribute& aaptAttr = xmlAttr->compiledAttribute.value();
    224 
    225                 StringPool::Ref nameRef = mPackagePools[aaptAttr.id.value().packageId()].makeRef(
    226                         xmlAttr->name, StringPool::Context{ aaptAttr.id.value().id });
    227 
    228                 // Add it to the list of strings to flatten.
    229                 addString(nameRef, &flatAttr->name);
    230             }
    231 
    232             if (mOptions.keepRawValues || !xmlAttr->compiledValue) {
    233                 // Keep raw values if the value is not compiled or
    234                 // if we're building a static library (need symbols).
    235                 addString(xmlAttr->value, kLowPriority, &flatAttr->rawValue);
    236             }
    237 
    238             if (xmlAttr->compiledValue) {
    239                 bool result = xmlAttr->compiledValue->flatten(&flatAttr->typedValue);
    240                 assert(result);
    241             } else {
    242                 // Flatten as a regular string type.
    243                 flatAttr->typedValue.dataType = android::Res_value::TYPE_STRING;
    244                 addString(xmlAttr->value, kLowPriority,
    245                           (ResStringPool_ref*) &flatAttr->typedValue.data);
    246             }
    247 
    248             flatAttr->typedValue.size = util::hostToDevice16(sizeof(flatAttr->typedValue));
    249             flatAttr++;
    250         }
    251     }
    252 };
    253 
    254 } // namespace
    255 
    256 bool XmlFlattener::flatten(IAaptContext* context, xml::Node* node) {
    257     BigBuffer nodeBuffer(1024);
    258     XmlFlattenerVisitor visitor(&nodeBuffer, mOptions);
    259     node->accept(&visitor);
    260 
    261     // Merge the package pools into the main pool.
    262     for (auto& packagePoolEntry : visitor.mPackagePools) {
    263         visitor.mPool.merge(std::move(packagePoolEntry.second));
    264     }
    265 
    266     // Sort the string pool so that attribute resource IDs show up first.
    267     visitor.mPool.sort([](const StringPool::Entry& a, const StringPool::Entry& b) -> bool {
    268         return a.context.priority < b.context.priority;
    269     });
    270 
    271     // Now we flatten the string pool references into the correct places.
    272     for (const auto& refEntry : visitor.mStringRefs) {
    273         refEntry.dest->index = util::hostToDevice32(refEntry.ref.getIndex());
    274     }
    275 
    276     // Write the XML header.
    277     ChunkWriter xmlHeaderWriter(mBuffer);
    278     xmlHeaderWriter.startChunk<ResXMLTree_header>(RES_XML_TYPE);
    279 
    280     // Flatten the StringPool.
    281     StringPool::flattenUtf16(mBuffer, visitor.mPool);
    282 
    283     {
    284         // Write the array of resource IDs, indexed by StringPool order.
    285         ChunkWriter resIdMapWriter(mBuffer);
    286         resIdMapWriter.startChunk<ResChunk_header>(RES_XML_RESOURCE_MAP_TYPE);
    287         for (const auto& str : visitor.mPool) {
    288             ResourceId id = { str->context.priority };
    289             if (id.id == kLowPriority || !id.isValid()) {
    290                 // When we see the first non-resource ID,
    291                 // we're done.
    292                 break;
    293             }
    294 
    295             *resIdMapWriter.nextBlock<uint32_t>() = id.id;
    296         }
    297         resIdMapWriter.finish();
    298     }
    299 
    300     // Move the nodeBuffer and append it to the out buffer.
    301     mBuffer->appendBuffer(std::move(nodeBuffer));
    302 
    303     // Finish the xml header.
    304     xmlHeaderWriter.finish();
    305     return true;
    306 }
    307 
    308 bool XmlFlattener::consume(IAaptContext* context, xml::XmlResource* resource) {
    309     if (!resource->root) {
    310         return false;
    311     }
    312     return flatten(context, resource->root.get());
    313 }
    314 
    315 } // namespace aapt
    316