Home | History | Annotate | Download | only in aapt2
      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 "ConfigDescription.h"
     19 #include "Logger.h"
     20 #include "ResourceTable.h"
     21 #include "ResourceTypeExtensions.h"
     22 #include "ResourceValues.h"
     23 #include "StringPool.h"
     24 #include "TableFlattener.h"
     25 #include "Util.h"
     26 
     27 #include <algorithm>
     28 #include <androidfw/ResourceTypes.h>
     29 #include <sstream>
     30 
     31 namespace aapt {
     32 
     33 struct FlatEntry {
     34     const ResourceEntry* entry;
     35     const Value* value;
     36     uint32_t entryKey;
     37     uint32_t sourcePathKey;
     38     uint32_t sourceLine;
     39 };
     40 
     41 /**
     42  * Visitor that knows how to encode Map values.
     43  */
     44 class MapFlattener : public ConstValueVisitor {
     45 public:
     46     MapFlattener(BigBuffer* out, const FlatEntry& flatEntry, SymbolEntryVector* symbols) :
     47             mOut(out), mSymbols(symbols) {
     48         mMap = mOut->nextBlock<android::ResTable_map_entry>();
     49         mMap->key.index = flatEntry.entryKey;
     50         mMap->flags = android::ResTable_entry::FLAG_COMPLEX;
     51         if (flatEntry.entry->publicStatus.isPublic) {
     52             mMap->flags |= android::ResTable_entry::FLAG_PUBLIC;
     53         }
     54         if (flatEntry.value->isWeak()) {
     55             mMap->flags |= android::ResTable_entry::FLAG_WEAK;
     56         }
     57 
     58         ResTable_entry_source* sourceBlock = mOut->nextBlock<ResTable_entry_source>();
     59         sourceBlock->pathIndex = flatEntry.sourcePathKey;
     60         sourceBlock->line = flatEntry.sourceLine;
     61 
     62         mMap->size = sizeof(*mMap) + sizeof(*sourceBlock);
     63     }
     64 
     65     void flattenParent(const Reference& ref) {
     66         if (!ref.id.isValid()) {
     67             mSymbols->push_back({
     68                     ResourceNameRef(ref.name),
     69                     (mOut->size() - mMap->size) + sizeof(*mMap) - sizeof(android::ResTable_entry)
     70             });
     71         }
     72         mMap->parent.ident = ref.id.id;
     73     }
     74 
     75     void flattenEntry(const Reference& key, const Item& value) {
     76         mMap->count++;
     77 
     78         android::ResTable_map* outMapEntry = mOut->nextBlock<android::ResTable_map>();
     79 
     80         // Write the key.
     81         if (!Res_INTERNALID(key.id.id) && !key.id.isValid()) {
     82             assert(!key.name.entry.empty());
     83             mSymbols->push_back(std::make_pair(ResourceNameRef(key.name),
     84                     mOut->size() - sizeof(*outMapEntry)));
     85         }
     86         outMapEntry->name.ident = key.id.id;
     87 
     88         // Write the value.
     89         value.flatten(outMapEntry->value);
     90 
     91         if (outMapEntry->value.data == 0x0) {
     92             visitFunc<Reference>(value, [&](const Reference& reference) {
     93                 mSymbols->push_back(std::make_pair(ResourceNameRef(reference.name),
     94                         mOut->size() - sizeof(outMapEntry->value.data)));
     95             });
     96         }
     97         outMapEntry->value.size = sizeof(outMapEntry->value);
     98     }
     99 
    100     void flattenValueOnly(const Item& value) {
    101         mMap->count++;
    102 
    103         android::ResTable_map* outMapEntry = mOut->nextBlock<android::ResTable_map>();
    104 
    105         // Write the value.
    106         value.flatten(outMapEntry->value);
    107 
    108         if (outMapEntry->value.data == 0x0) {
    109             visitFunc<Reference>(value, [&](const Reference& reference) {
    110                 mSymbols->push_back(std::make_pair(ResourceNameRef(reference.name),
    111                         mOut->size() - sizeof(outMapEntry->value.data)));
    112             });
    113         }
    114         outMapEntry->value.size = sizeof(outMapEntry->value);
    115     }
    116 
    117     static bool compareStyleEntries(const Style::Entry* lhs, const Style::Entry* rhs) {
    118         return lhs->key.id < rhs->key.id;
    119     }
    120 
    121     void visit(const Style& style, ValueVisitorArgs&) override {
    122         if (style.parent.name.isValid()) {
    123             flattenParent(style.parent);
    124         }
    125 
    126         // First sort the entries by ID.
    127         std::vector<const Style::Entry*> sortedEntries;
    128         for (const auto& styleEntry : style.entries) {
    129             auto iter = std::lower_bound(sortedEntries.begin(), sortedEntries.end(),
    130                     &styleEntry, compareStyleEntries);
    131             sortedEntries.insert(iter, &styleEntry);
    132         }
    133 
    134         for (const Style::Entry* styleEntry : sortedEntries) {
    135             flattenEntry(styleEntry->key, *styleEntry->value);
    136         }
    137     }
    138 
    139     void visit(const Attribute& attr, ValueVisitorArgs&) override {
    140         android::Res_value tempVal;
    141         tempVal.dataType = android::Res_value::TYPE_INT_DEC;
    142         tempVal.data = attr.typeMask;
    143         flattenEntry(Reference(ResourceId{android::ResTable_map::ATTR_TYPE}),
    144                 BinaryPrimitive(tempVal));
    145 
    146         for (const auto& symbol : attr.symbols) {
    147             tempVal.data = symbol.value;
    148             flattenEntry(symbol.symbol, BinaryPrimitive(tempVal));
    149         }
    150     }
    151 
    152     void visit(const Styleable& styleable, ValueVisitorArgs&) override {
    153         for (const auto& attr : styleable.entries) {
    154             flattenEntry(attr, BinaryPrimitive(android::Res_value{}));
    155         }
    156     }
    157 
    158     void visit(const Array& array, ValueVisitorArgs&) override {
    159         for (const auto& item : array.items) {
    160             flattenValueOnly(*item);
    161         }
    162     }
    163 
    164     void visit(const Plural& plural, ValueVisitorArgs&) override {
    165         const size_t count = plural.values.size();
    166         for (size_t i = 0; i < count; i++) {
    167             if (!plural.values[i]) {
    168                 continue;
    169             }
    170 
    171             ResourceId q;
    172             switch (i) {
    173                 case Plural::Zero:
    174                     q.id = android::ResTable_map::ATTR_ZERO;
    175                     break;
    176 
    177                 case Plural::One:
    178                     q.id = android::ResTable_map::ATTR_ONE;
    179                     break;
    180 
    181                 case Plural::Two:
    182                     q.id = android::ResTable_map::ATTR_TWO;
    183                     break;
    184 
    185                 case Plural::Few:
    186                     q.id = android::ResTable_map::ATTR_FEW;
    187                     break;
    188 
    189                 case Plural::Many:
    190                     q.id = android::ResTable_map::ATTR_MANY;
    191                     break;
    192 
    193                 case Plural::Other:
    194                     q.id = android::ResTable_map::ATTR_OTHER;
    195                     break;
    196 
    197                 default:
    198                     assert(false);
    199                     break;
    200             }
    201 
    202             flattenEntry(Reference(q), *plural.values[i]);
    203         }
    204     }
    205 
    206 private:
    207     BigBuffer* mOut;
    208     SymbolEntryVector* mSymbols;
    209     android::ResTable_map_entry* mMap;
    210 };
    211 
    212 /**
    213  * Flattens a value, with special handling for References.
    214  */
    215 struct ValueFlattener : ConstValueVisitor {
    216     ValueFlattener(BigBuffer* out, SymbolEntryVector* symbols) :
    217             result(false), mOut(out), mOutValue(nullptr), mSymbols(symbols) {
    218         mOutValue = mOut->nextBlock<android::Res_value>();
    219     }
    220 
    221     virtual void visit(const Reference& ref, ValueVisitorArgs& a) override {
    222         visitItem(ref, a);
    223         if (mOutValue->data == 0x0) {
    224             mSymbols->push_back({
    225                     ResourceNameRef(ref.name),
    226                     mOut->size() - sizeof(mOutValue->data)});
    227         }
    228     }
    229 
    230     virtual void visitItem(const Item& item, ValueVisitorArgs&) override {
    231         result = item.flatten(*mOutValue);
    232         mOutValue->res0 = 0;
    233         mOutValue->size = sizeof(*mOutValue);
    234     }
    235 
    236     bool result;
    237 
    238 private:
    239     BigBuffer* mOut;
    240     android::Res_value* mOutValue;
    241     SymbolEntryVector* mSymbols;
    242 };
    243 
    244 TableFlattener::TableFlattener(Options options)
    245 : mOptions(options) {
    246 }
    247 
    248 bool TableFlattener::flattenValue(BigBuffer* out, const FlatEntry& flatEntry,
    249                                   SymbolEntryVector* symbols) {
    250     if (flatEntry.value->isItem()) {
    251         android::ResTable_entry* entry = out->nextBlock<android::ResTable_entry>();
    252 
    253         if (flatEntry.entry->publicStatus.isPublic) {
    254             entry->flags |= android::ResTable_entry::FLAG_PUBLIC;
    255         }
    256 
    257         if (flatEntry.value->isWeak()) {
    258             entry->flags |= android::ResTable_entry::FLAG_WEAK;
    259         }
    260 
    261         entry->key.index = flatEntry.entryKey;
    262         entry->size = sizeof(*entry);
    263 
    264         if (mOptions.useExtendedChunks) {
    265             // Write the extra source block. This will be ignored by
    266             // the Android runtime.
    267             ResTable_entry_source* sourceBlock = out->nextBlock<ResTable_entry_source>();
    268             sourceBlock->pathIndex = flatEntry.sourcePathKey;
    269             sourceBlock->line = flatEntry.sourceLine;
    270             entry->size += sizeof(*sourceBlock);
    271         }
    272 
    273         const Item* item = static_cast<const Item*>(flatEntry.value);
    274         ValueFlattener flattener(out, symbols);
    275         item->accept(flattener, {});
    276         return flattener.result;
    277     }
    278 
    279     MapFlattener flattener(out, flatEntry, symbols);
    280     flatEntry.value->accept(flattener, {});
    281     return true;
    282 }
    283 
    284 bool TableFlattener::flatten(BigBuffer* out, const ResourceTable& table) {
    285     const size_t beginning = out->size();
    286 
    287     if (table.getPackageId() == ResourceTable::kUnsetPackageId) {
    288         Logger::error()
    289                 << "ResourceTable has no package ID set."
    290                 << std::endl;
    291         return false;
    292     }
    293 
    294     SymbolEntryVector symbolEntries;
    295 
    296     StringPool typePool;
    297     StringPool keyPool;
    298     StringPool sourcePool;
    299 
    300     // Sort the types by their IDs. They will be inserted into the StringPool
    301     // in this order.
    302     std::vector<ResourceTableType*> sortedTypes;
    303     for (const auto& type : table) {
    304         if (type->type == ResourceType::kStyleable && !mOptions.useExtendedChunks) {
    305             continue;
    306         }
    307 
    308         auto iter = std::lower_bound(std::begin(sortedTypes), std::end(sortedTypes), type.get(),
    309                 [](const ResourceTableType* lhs, const ResourceTableType* rhs) -> bool {
    310                     return lhs->typeId < rhs->typeId;
    311                 });
    312         sortedTypes.insert(iter, type.get());
    313     }
    314 
    315     BigBuffer typeBlock(1024);
    316     size_t expectedTypeId = 1;
    317     for (const ResourceTableType* type : sortedTypes) {
    318         if (type->typeId == ResourceTableType::kUnsetTypeId
    319                 || type->typeId == 0) {
    320             Logger::error()
    321                     << "resource type '"
    322                     << type->type
    323                     << "' from package '"
    324                     << table.getPackage()
    325                     << "' has no ID."
    326                     << std::endl;
    327             return false;
    328         }
    329 
    330         // If there is a gap in the type IDs, fill in the StringPool
    331         // with empty values until we reach the ID we expect.
    332         while (type->typeId > expectedTypeId) {
    333             std::u16string typeName(u"?");
    334             typeName += expectedTypeId;
    335             typePool.makeRef(typeName);
    336             expectedTypeId++;
    337         }
    338         expectedTypeId++;
    339         typePool.makeRef(toString(type->type));
    340 
    341         android::ResTable_typeSpec* spec = typeBlock.nextBlock<android::ResTable_typeSpec>();
    342         spec->header.type = android::RES_TABLE_TYPE_SPEC_TYPE;
    343         spec->header.headerSize = sizeof(*spec);
    344         spec->header.size = spec->header.headerSize + (type->entries.size() * sizeof(uint32_t));
    345         spec->id = type->typeId;
    346         spec->entryCount = type->entries.size();
    347 
    348         if (type->entries.empty()) {
    349             continue;
    350         }
    351 
    352         // Reserve space for the masks of each resource in this type. These
    353         // show for which configuration axis the resource changes.
    354         uint32_t* configMasks = typeBlock.nextBlock<uint32_t>(type->entries.size());
    355 
    356         // Sort the entries by entry ID and write their configuration masks.
    357         std::vector<ResourceEntry*> entries;
    358         const size_t entryCount = type->entries.size();
    359         for (size_t entryIndex = 0; entryIndex < entryCount; entryIndex++) {
    360             const auto& entry = type->entries[entryIndex];
    361 
    362             if (entry->entryId == ResourceEntry::kUnsetEntryId) {
    363                 Logger::error()
    364                         << "resource '"
    365                         << ResourceName{ table.getPackage(), type->type, entry->name }
    366                         << "' has no ID."
    367                         << std::endl;
    368                 return false;
    369             }
    370 
    371             auto iter = std::lower_bound(std::begin(entries), std::end(entries), entry.get(),
    372                     [](const ResourceEntry* lhs, const ResourceEntry* rhs) -> bool {
    373                         return lhs->entryId < rhs->entryId;
    374                     });
    375             entries.insert(iter, entry.get());
    376 
    377             // Populate the config masks for this entry.
    378             if (entry->publicStatus.isPublic) {
    379                 configMasks[entry->entryId] |= android::ResTable_typeSpec::SPEC_PUBLIC;
    380             }
    381 
    382             const size_t configCount = entry->values.size();
    383             for (size_t i = 0; i < configCount; i++) {
    384                 const ConfigDescription& config = entry->values[i].config;
    385                 for (size_t j = i + 1; j < configCount; j++) {
    386                     configMasks[entry->entryId] |= config.diff(entry->values[j].config);
    387                 }
    388             }
    389         }
    390 
    391         const size_t beforePublicHeader = typeBlock.size();
    392         Public_header* publicHeader = nullptr;
    393         if (mOptions.useExtendedChunks) {
    394             publicHeader = typeBlock.nextBlock<Public_header>();
    395             publicHeader->header.type = RES_TABLE_PUBLIC_TYPE;
    396             publicHeader->header.headerSize = sizeof(*publicHeader);
    397             publicHeader->typeId = type->typeId;
    398         }
    399 
    400         // The binary resource table lists resource entries for each configuration.
    401         // We store them inverted, where a resource entry lists the values for each
    402         // configuration available. Here we reverse this to match the binary table.
    403         std::map<ConfigDescription, std::vector<FlatEntry>> data;
    404         for (const ResourceEntry* entry : entries) {
    405             size_t keyIndex = keyPool.makeRef(entry->name).getIndex();
    406 
    407             if (keyIndex > std::numeric_limits<uint32_t>::max()) {
    408                 Logger::error()
    409                         << "resource key string pool exceeded max size."
    410                         << std::endl;
    411                 return false;
    412             }
    413 
    414             if (publicHeader && entry->publicStatus.isPublic) {
    415                 // Write the public status of this entry.
    416                 Public_entry* publicEntry = typeBlock.nextBlock<Public_entry>();
    417                 publicEntry->entryId = static_cast<uint32_t>(entry->entryId);
    418                 publicEntry->key.index = static_cast<uint32_t>(keyIndex);
    419                 publicEntry->source.index = static_cast<uint32_t>(sourcePool.makeRef(
    420                             util::utf8ToUtf16(entry->publicStatus.source.path)).getIndex());
    421                 publicEntry->sourceLine = static_cast<uint32_t>(entry->publicStatus.source.line);
    422                 publicHeader->count += 1;
    423             }
    424 
    425             for (const auto& configValue : entry->values) {
    426                 data[configValue.config].push_back(FlatEntry{
    427                         entry,
    428                         configValue.value.get(),
    429                         static_cast<uint32_t>(keyIndex),
    430                         static_cast<uint32_t>(sourcePool.makeRef(util::utf8ToUtf16(
    431                                     configValue.source.path)).getIndex()),
    432                         static_cast<uint32_t>(configValue.source.line)
    433                 });
    434             }
    435         }
    436 
    437         if (publicHeader) {
    438             typeBlock.align4();
    439             publicHeader->header.size =
    440                     static_cast<uint32_t>(typeBlock.size() - beforePublicHeader);
    441         }
    442 
    443         // Begin flattening a configuration for the current type.
    444         for (const auto& entry : data) {
    445             const size_t typeHeaderStart = typeBlock.size();
    446             android::ResTable_type* typeHeader = typeBlock.nextBlock<android::ResTable_type>();
    447             typeHeader->header.type = android::RES_TABLE_TYPE_TYPE;
    448             typeHeader->header.headerSize = sizeof(*typeHeader);
    449             typeHeader->id = type->typeId;
    450             typeHeader->entryCount = type->entries.size();
    451             typeHeader->entriesStart = typeHeader->header.headerSize
    452                     + (sizeof(uint32_t) * type->entries.size());
    453             typeHeader->config = entry.first;
    454 
    455             uint32_t* indices = typeBlock.nextBlock<uint32_t>(type->entries.size());
    456             memset(indices, 0xff, type->entries.size() * sizeof(uint32_t));
    457 
    458             const size_t entryStart = typeBlock.size();
    459             for (const FlatEntry& flatEntry : entry.second) {
    460                 assert(flatEntry.entry->entryId < type->entries.size());
    461                 indices[flatEntry.entry->entryId] = typeBlock.size() - entryStart;
    462                 if (!flattenValue(&typeBlock, flatEntry, &symbolEntries)) {
    463                     Logger::error()
    464                             << "failed to flatten resource '"
    465                             << ResourceNameRef {
    466                                     table.getPackage(), type->type, flatEntry.entry->name }
    467                             << "' for configuration '"
    468                             << entry.first
    469                             << "'."
    470                             << std::endl;
    471                     return false;
    472                 }
    473             }
    474 
    475             typeBlock.align4();
    476             typeHeader->header.size = typeBlock.size() - typeHeaderStart;
    477         }
    478     }
    479 
    480     const size_t beforeTable = out->size();
    481     android::ResTable_header* header = out->nextBlock<android::ResTable_header>();
    482     header->header.type = android::RES_TABLE_TYPE;
    483     header->header.headerSize = sizeof(*header);
    484     header->packageCount = 1;
    485 
    486     SymbolTable_entry* symbolEntryData = nullptr;
    487     if (!symbolEntries.empty() && mOptions.useExtendedChunks) {
    488         const size_t beforeSymbolTable = out->size();
    489         StringPool symbolPool;
    490         SymbolTable_header* symbolHeader = out->nextBlock<SymbolTable_header>();
    491         symbolHeader->header.type = RES_TABLE_SYMBOL_TABLE_TYPE;
    492         symbolHeader->header.headerSize = sizeof(*symbolHeader);
    493         symbolHeader->count = symbolEntries.size();
    494 
    495         symbolEntryData = out->nextBlock<SymbolTable_entry>(symbolHeader->count);
    496 
    497         size_t i = 0;
    498         for (const auto& entry : symbolEntries) {
    499             symbolEntryData[i].offset = entry.second;
    500             StringPool::Ref ref = symbolPool.makeRef(
    501                     entry.first.package.toString() + u":" +
    502                     toString(entry.first.type).toString() + u"/" +
    503                     entry.first.entry.toString());
    504             symbolEntryData[i].stringIndex = ref.getIndex();
    505             i++;
    506         }
    507 
    508         StringPool::flattenUtf8(out, symbolPool);
    509         out->align4();
    510         symbolHeader->header.size = out->size() - beforeSymbolTable;
    511     }
    512 
    513     if (sourcePool.size() > 0 && mOptions.useExtendedChunks) {
    514         const size_t beforeSourcePool = out->size();
    515         android::ResChunk_header* sourceHeader = out->nextBlock<android::ResChunk_header>();
    516         sourceHeader->type = RES_TABLE_SOURCE_POOL_TYPE;
    517         sourceHeader->headerSize = sizeof(*sourceHeader);
    518         StringPool::flattenUtf8(out, sourcePool);
    519         out->align4();
    520         sourceHeader->size = out->size() - beforeSourcePool;
    521     }
    522 
    523     StringPool::flattenUtf8(out, table.getValueStringPool());
    524 
    525     const size_t beforePackageIndex = out->size();
    526     android::ResTable_package* package = out->nextBlock<android::ResTable_package>();
    527     package->header.type = android::RES_TABLE_PACKAGE_TYPE;
    528     package->header.headerSize = sizeof(*package);
    529 
    530     if (table.getPackageId() > std::numeric_limits<uint8_t>::max()) {
    531         Logger::error()
    532                 << "package ID 0x'"
    533                 << std::hex << table.getPackageId() << std::dec
    534                 << "' is invalid."
    535                 << std::endl;
    536         return false;
    537     }
    538     package->id = table.getPackageId();
    539 
    540     if (table.getPackage().size() >= sizeof(package->name) / sizeof(package->name[0])) {
    541         Logger::error()
    542                 << "package name '"
    543                 << table.getPackage()
    544                 << "' is too long."
    545                 << std::endl;
    546         return false;
    547     }
    548     memcpy(package->name, reinterpret_cast<const uint16_t*>(table.getPackage().data()),
    549             table.getPackage().length() * sizeof(char16_t));
    550     package->name[table.getPackage().length()] = 0;
    551 
    552     package->typeStrings = package->header.headerSize;
    553     StringPool::flattenUtf16(out, typePool);
    554     package->keyStrings = out->size() - beforePackageIndex;
    555     StringPool::flattenUtf16(out, keyPool);
    556 
    557     if (symbolEntryData != nullptr) {
    558         for (size_t i = 0; i < symbolEntries.size(); i++) {
    559             symbolEntryData[i].offset += out->size() - beginning;
    560         }
    561     }
    562 
    563     out->appendBuffer(std::move(typeBlock));
    564 
    565     package->header.size = out->size() - beforePackageIndex;
    566     header->header.size = out->size() - beforeTable;
    567     return true;
    568 }
    569 
    570 } // namespace aapt
    571