Home | History | Annotate | Download | only in unflatten
      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 "ResourceTable.h"
     18 #include "ResourceUtils.h"
     19 #include "ResourceValues.h"
     20 #include "Source.h"
     21 #include "ValueVisitor.h"
     22 #include "unflatten/BinaryResourceParser.h"
     23 #include "unflatten/ResChunkPullParser.h"
     24 #include "util/Util.h"
     25 
     26 #include <androidfw/ResourceTypes.h>
     27 #include <androidfw/TypeWrappers.h>
     28 #include <android-base/macros.h>
     29 #include <algorithm>
     30 #include <map>
     31 #include <string>
     32 
     33 namespace aapt {
     34 
     35 using namespace android;
     36 
     37 namespace {
     38 
     39 /*
     40  * Visitor that converts a reference's resource ID to a resource name,
     41  * given a mapping from resource ID to resource name.
     42  */
     43 class ReferenceIdToNameVisitor : public ValueVisitor {
     44 private:
     45     const std::map<ResourceId, ResourceName>* mMapping;
     46 
     47 public:
     48     using ValueVisitor::visit;
     49 
     50     ReferenceIdToNameVisitor(const std::map<ResourceId, ResourceName>* mapping) :
     51             mMapping(mapping) {
     52         assert(mMapping);
     53     }
     54 
     55     void visit(Reference* reference) override {
     56         if (!reference->id || !reference->id.value().isValid()) {
     57             return;
     58         }
     59 
     60         ResourceId id = reference->id.value();
     61         auto cacheIter = mMapping->find(id);
     62         if (cacheIter != mMapping->end()) {
     63             reference->name = cacheIter->second;
     64             reference->id = {};
     65         }
     66     }
     67 };
     68 
     69 } // namespace
     70 
     71 BinaryResourceParser::BinaryResourceParser(IAaptContext* context, ResourceTable* table,
     72                                            const Source& source, const void* data, size_t len) :
     73         mContext(context), mTable(table), mSource(source), mData(data), mDataLen(len) {
     74 }
     75 
     76 bool BinaryResourceParser::parse() {
     77     ResChunkPullParser parser(mData, mDataLen);
     78 
     79     bool error = false;
     80     while(ResChunkPullParser::isGoodEvent(parser.next())) {
     81         if (parser.getChunk()->type != android::RES_TABLE_TYPE) {
     82             mContext->getDiagnostics()->warn(DiagMessage(mSource)
     83                                              << "unknown chunk of type '"
     84                                              << (int) parser.getChunk()->type << "'");
     85             continue;
     86         }
     87 
     88         if (!parseTable(parser.getChunk())) {
     89             error = true;
     90         }
     91     }
     92 
     93     if (parser.getEvent() == ResChunkPullParser::Event::BadDocument) {
     94         mContext->getDiagnostics()->error(DiagMessage(mSource)
     95                                           << "corrupt resource table: "
     96                                           << parser.getLastError());
     97         return false;
     98     }
     99     return !error;
    100 }
    101 
    102 /**
    103  * Parses the resource table, which contains all the packages, types, and entries.
    104  */
    105 bool BinaryResourceParser::parseTable(const ResChunk_header* chunk) {
    106     const ResTable_header* tableHeader = convertTo<ResTable_header>(chunk);
    107     if (!tableHeader) {
    108         mContext->getDiagnostics()->error(DiagMessage(mSource) << "corrupt ResTable_header chunk");
    109         return false;
    110     }
    111 
    112     ResChunkPullParser parser(getChunkData(&tableHeader->header),
    113                               getChunkDataLen(&tableHeader->header));
    114     while (ResChunkPullParser::isGoodEvent(parser.next())) {
    115         switch (util::deviceToHost16(parser.getChunk()->type)) {
    116         case android::RES_STRING_POOL_TYPE:
    117             if (mValuePool.getError() == NO_INIT) {
    118                 status_t err = mValuePool.setTo(parser.getChunk(),
    119                                                 util::deviceToHost32(parser.getChunk()->size));
    120                 if (err != NO_ERROR) {
    121                     mContext->getDiagnostics()->error(DiagMessage(mSource)
    122                                                       << "corrupt string pool in ResTable: "
    123                                                       << mValuePool.getError());
    124                     return false;
    125                 }
    126 
    127                 // Reserve some space for the strings we are going to add.
    128                 mTable->stringPool.hintWillAdd(mValuePool.size(), mValuePool.styleCount());
    129             } else {
    130                 mContext->getDiagnostics()->warn(DiagMessage(mSource)
    131                                                  << "unexpected string pool in ResTable");
    132             }
    133             break;
    134 
    135         case android::RES_TABLE_PACKAGE_TYPE:
    136             if (!parsePackage(parser.getChunk())) {
    137                 return false;
    138             }
    139             break;
    140 
    141         default:
    142             mContext->getDiagnostics()
    143                     ->warn(DiagMessage(mSource)
    144                            << "unexpected chunk type "
    145                            << (int) util::deviceToHost16(parser.getChunk()->type));
    146             break;
    147         }
    148     }
    149 
    150     if (parser.getEvent() == ResChunkPullParser::Event::BadDocument) {
    151         mContext->getDiagnostics()->error(DiagMessage(mSource)
    152                                           << "corrupt resource table: " << parser.getLastError());
    153         return false;
    154     }
    155     return true;
    156 }
    157 
    158 
    159 bool BinaryResourceParser::parsePackage(const ResChunk_header* chunk) {
    160     const ResTable_package* packageHeader = convertTo<ResTable_package>(chunk);
    161     if (!packageHeader) {
    162         mContext->getDiagnostics()->error(DiagMessage(mSource)
    163                                           << "corrupt ResTable_package chunk");
    164         return false;
    165     }
    166 
    167     uint32_t packageId = util::deviceToHost32(packageHeader->id);
    168     if (packageId > std::numeric_limits<uint8_t>::max()) {
    169         mContext->getDiagnostics()->error(DiagMessage(mSource)
    170                                           << "package ID is too big (" << packageId << ")");
    171         return false;
    172     }
    173 
    174     // Extract the package name.
    175     size_t len = strnlen16((const char16_t*) packageHeader->name, arraysize(packageHeader->name));
    176     std::u16string packageName;
    177     packageName.resize(len);
    178     for (size_t i = 0; i < len; i++) {
    179         packageName[i] = util::deviceToHost16(packageHeader->name[i]);
    180     }
    181 
    182     ResourceTablePackage* package = mTable->createPackage(packageName, (uint8_t) packageId);
    183     if (!package) {
    184         mContext->getDiagnostics()->error(DiagMessage(mSource)
    185                                           << "incompatible package '" << packageName
    186                                           << "' with ID " << packageId);
    187         return false;
    188     }
    189 
    190     // There can be multiple packages in a table, so
    191     // clear the type and key pool in case they were set from a previous package.
    192     mTypePool.uninit();
    193     mKeyPool.uninit();
    194 
    195     ResChunkPullParser parser(getChunkData(&packageHeader->header),
    196                               getChunkDataLen(&packageHeader->header));
    197     while (ResChunkPullParser::isGoodEvent(parser.next())) {
    198         switch (util::deviceToHost16(parser.getChunk()->type)) {
    199         case android::RES_STRING_POOL_TYPE:
    200             if (mTypePool.getError() == NO_INIT) {
    201                 status_t err = mTypePool.setTo(parser.getChunk(),
    202                                                util::deviceToHost32(parser.getChunk()->size));
    203                 if (err != NO_ERROR) {
    204                     mContext->getDiagnostics()->error(DiagMessage(mSource)
    205                                                       << "corrupt type string pool in "
    206                                                       << "ResTable_package: "
    207                                                       << mTypePool.getError());
    208                     return false;
    209                 }
    210             } else if (mKeyPool.getError() == NO_INIT) {
    211                 status_t err = mKeyPool.setTo(parser.getChunk(),
    212                                               util::deviceToHost32(parser.getChunk()->size));
    213                 if (err != NO_ERROR) {
    214                     mContext->getDiagnostics()->error(DiagMessage(mSource)
    215                                                       << "corrupt key string pool in "
    216                                                       << "ResTable_package: "
    217                                                       << mKeyPool.getError());
    218                     return false;
    219                 }
    220             } else {
    221                 mContext->getDiagnostics()->warn(DiagMessage(mSource) << "unexpected string pool");
    222             }
    223             break;
    224 
    225         case android::RES_TABLE_TYPE_SPEC_TYPE:
    226             if (!parseTypeSpec(parser.getChunk())) {
    227                 return false;
    228             }
    229             break;
    230 
    231         case android::RES_TABLE_TYPE_TYPE:
    232             if (!parseType(package, parser.getChunk())) {
    233                 return false;
    234             }
    235             break;
    236 
    237         default:
    238             mContext->getDiagnostics()
    239                     ->warn(DiagMessage(mSource)
    240                            << "unexpected chunk type "
    241                            << (int) util::deviceToHost16(parser.getChunk()->type));
    242             break;
    243         }
    244     }
    245 
    246     if (parser.getEvent() == ResChunkPullParser::Event::BadDocument) {
    247         mContext->getDiagnostics()->error(DiagMessage(mSource)
    248                                           << "corrupt ResTable_package: "
    249                                           << parser.getLastError());
    250         return false;
    251     }
    252 
    253     // Now go through the table and change local resource ID references to
    254     // symbolic references.
    255     ReferenceIdToNameVisitor visitor(&mIdIndex);
    256     visitAllValuesInTable(mTable, &visitor);
    257     return true;
    258 }
    259 
    260 bool BinaryResourceParser::parseTypeSpec(const ResChunk_header* chunk) {
    261     if (mTypePool.getError() != NO_ERROR) {
    262         mContext->getDiagnostics()->error(DiagMessage(mSource)
    263                                           << "missing type string pool");
    264         return false;
    265     }
    266 
    267     const ResTable_typeSpec* typeSpec = convertTo<ResTable_typeSpec>(chunk);
    268     if (!typeSpec) {
    269         mContext->getDiagnostics()->error(DiagMessage(mSource)
    270                                           << "corrupt ResTable_typeSpec chunk");
    271         return false;
    272     }
    273 
    274     if (typeSpec->id == 0) {
    275         mContext->getDiagnostics()->error(DiagMessage(mSource)
    276                                           << "ResTable_typeSpec has invalid id: " << typeSpec->id);
    277         return false;
    278     }
    279     return true;
    280 }
    281 
    282 bool BinaryResourceParser::parseType(const ResourceTablePackage* package,
    283                                      const ResChunk_header* chunk) {
    284     if (mTypePool.getError() != NO_ERROR) {
    285         mContext->getDiagnostics()->error(DiagMessage(mSource)
    286                                           << "missing type string pool");
    287         return false;
    288     }
    289 
    290     if (mKeyPool.getError() != NO_ERROR) {
    291         mContext->getDiagnostics()->error(DiagMessage(mSource)
    292                                           << "missing key string pool");
    293         return false;
    294     }
    295 
    296     const ResTable_type* type = convertTo<ResTable_type>(chunk);
    297     if (!type) {
    298         mContext->getDiagnostics()->error(DiagMessage(mSource)
    299                                           << "corrupt ResTable_type chunk");
    300         return false;
    301     }
    302 
    303     if (type->id == 0) {
    304         mContext->getDiagnostics()->error(DiagMessage(mSource)
    305                                           << "ResTable_type has invalid id: " << (int) type->id);
    306         return false;
    307     }
    308 
    309     ConfigDescription config;
    310     config.copyFromDtoH(type->config);
    311 
    312     StringPiece16 typeStr16 = util::getString(mTypePool, type->id - 1);
    313 
    314     const ResourceType* parsedType = parseResourceType(typeStr16);
    315     if (!parsedType) {
    316         mContext->getDiagnostics()->error(DiagMessage(mSource)
    317                                           << "invalid type name '" << typeStr16
    318                                           << "' for type with ID " << (int) type->id);
    319         return false;
    320     }
    321 
    322     TypeVariant tv(type);
    323     for (auto it = tv.beginEntries(); it != tv.endEntries(); ++it) {
    324         const ResTable_entry* entry = *it;
    325         if (!entry) {
    326             continue;
    327         }
    328 
    329         const ResourceName name(package->name, *parsedType,
    330                                 util::getString(mKeyPool,
    331                                                 util::deviceToHost32(entry->key.index)).toString());
    332 
    333         const ResourceId resId(package->id.value(), type->id, static_cast<uint16_t>(it.index()));
    334 
    335         std::unique_ptr<Value> resourceValue;
    336         if (entry->flags & ResTable_entry::FLAG_COMPLEX) {
    337             const ResTable_map_entry* mapEntry = static_cast<const ResTable_map_entry*>(entry);
    338 
    339             // TODO(adamlesinski): Check that the entry count is valid.
    340             resourceValue = parseMapEntry(name, config, mapEntry);
    341         } else {
    342             const Res_value* value = (const Res_value*)(
    343                     (const uint8_t*) entry + util::deviceToHost32(entry->size));
    344             resourceValue = parseValue(name, config, value, entry->flags);
    345         }
    346 
    347         if (!resourceValue) {
    348             mContext->getDiagnostics()->error(DiagMessage(mSource)
    349                                               << "failed to parse value for resource " << name
    350                                               << " (" << resId << ") with configuration '"
    351                                               << config << "'");
    352             return false;
    353         }
    354 
    355         if (!mTable->addResourceAllowMangled(name, config, {}, std::move(resourceValue),
    356                                              mContext->getDiagnostics())) {
    357             return false;
    358         }
    359 
    360         if ((entry->flags & ResTable_entry::FLAG_PUBLIC) != 0) {
    361             Symbol symbol;
    362             symbol.state = SymbolState::kPublic;
    363             symbol.source = mSource.withLine(0);
    364             if (!mTable->setSymbolStateAllowMangled(name, resId, symbol,
    365                                                     mContext->getDiagnostics())) {
    366                 return false;
    367             }
    368         }
    369 
    370         // Add this resource name->id mapping to the index so
    371         // that we can resolve all ID references to name references.
    372         auto cacheIter = mIdIndex.find(resId);
    373         if (cacheIter == mIdIndex.end()) {
    374             mIdIndex.insert({ resId, name });
    375         }
    376     }
    377     return true;
    378 }
    379 
    380 std::unique_ptr<Item> BinaryResourceParser::parseValue(const ResourceNameRef& name,
    381                                                        const ConfigDescription& config,
    382                                                        const Res_value* value,
    383                                                        uint16_t flags) {
    384     if (name.type == ResourceType::kId) {
    385         return util::make_unique<Id>();
    386     }
    387 
    388     const uint32_t data = util::deviceToHost32(value->data);
    389 
    390     if (value->dataType == Res_value::TYPE_STRING) {
    391         StringPiece16 str = util::getString(mValuePool, data);
    392 
    393         const ResStringPool_span* spans = mValuePool.styleAt(data);
    394 
    395         // Check if the string has a valid style associated with it.
    396         if (spans != nullptr && spans->name.index != ResStringPool_span::END) {
    397             StyleString styleStr = { str.toString() };
    398             while (spans->name.index != ResStringPool_span::END) {
    399                 styleStr.spans.push_back(Span{
    400                         util::getString(mValuePool, spans->name.index).toString(),
    401                         spans->firstChar,
    402                         spans->lastChar
    403                 });
    404                 spans++;
    405             }
    406             return util::make_unique<StyledString>(mTable->stringPool.makeRef(
    407                     styleStr, StringPool::Context{1, config}));
    408         } else {
    409             if (name.type != ResourceType::kString &&
    410                     util::stringStartsWith<char16_t>(str, u"res/")) {
    411                 // This must be a FileReference.
    412                 return util::make_unique<FileReference>(mTable->stringPool.makeRef(
    413                             str, StringPool::Context{ 0, config }));
    414             }
    415 
    416             // There are no styles associated with this string, so treat it as
    417             // a simple string.
    418             return util::make_unique<String>(mTable->stringPool.makeRef(
    419                     str, StringPool::Context{1, config}));
    420         }
    421     }
    422 
    423     if (value->dataType == Res_value::TYPE_REFERENCE ||
    424             value->dataType == Res_value::TYPE_ATTRIBUTE) {
    425         const Reference::Type type = (value->dataType == Res_value::TYPE_REFERENCE) ?
    426                 Reference::Type::kResource : Reference::Type::kAttribute;
    427 
    428         if (data == 0) {
    429             // A reference of 0, must be the magic @null reference.
    430             Res_value nullType = {};
    431             nullType.dataType = Res_value::TYPE_REFERENCE;
    432             return util::make_unique<BinaryPrimitive>(nullType);
    433         }
    434 
    435         // This is a normal reference.
    436         return util::make_unique<Reference>(data, type);
    437     }
    438 
    439     // Treat this as a raw binary primitive.
    440     return util::make_unique<BinaryPrimitive>(*value);
    441 }
    442 
    443 std::unique_ptr<Value> BinaryResourceParser::parseMapEntry(const ResourceNameRef& name,
    444                                                            const ConfigDescription& config,
    445                                                            const ResTable_map_entry* map) {
    446     switch (name.type) {
    447         case ResourceType::kStyle:
    448             return parseStyle(name, config, map);
    449         case ResourceType::kAttrPrivate:
    450             // fallthrough
    451         case ResourceType::kAttr:
    452             return parseAttr(name, config, map);
    453         case ResourceType::kArray:
    454             return parseArray(name, config, map);
    455         case ResourceType::kPlurals:
    456             return parsePlural(name, config, map);
    457         default:
    458             assert(false && "unknown map type");
    459             break;
    460     }
    461     return {};
    462 }
    463 
    464 std::unique_ptr<Style> BinaryResourceParser::parseStyle(const ResourceNameRef& name,
    465                                                         const ConfigDescription& config,
    466                                                         const ResTable_map_entry* map) {
    467     std::unique_ptr<Style> style = util::make_unique<Style>();
    468     if (util::deviceToHost32(map->parent.ident) != 0) {
    469         // The parent is a regular reference to a resource.
    470         style->parent = Reference(util::deviceToHost32(map->parent.ident));
    471     }
    472 
    473     for (const ResTable_map& mapEntry : map) {
    474         if (Res_INTERNALID(util::deviceToHost32(mapEntry.name.ident))) {
    475             continue;
    476         }
    477 
    478         Style::Entry styleEntry;
    479         styleEntry.key = Reference(util::deviceToHost32(mapEntry.name.ident));
    480         styleEntry.value = parseValue(name, config, &mapEntry.value, 0);
    481         if (!styleEntry.value) {
    482             return {};
    483         }
    484         style->entries.push_back(std::move(styleEntry));
    485     }
    486     return style;
    487 }
    488 
    489 std::unique_ptr<Attribute> BinaryResourceParser::parseAttr(const ResourceNameRef& name,
    490                                                            const ConfigDescription& config,
    491                                                            const ResTable_map_entry* map) {
    492     const bool isWeak = (util::deviceToHost16(map->flags) & ResTable_entry::FLAG_WEAK) != 0;
    493     std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(isWeak);
    494 
    495     // First we must discover what type of attribute this is. Find the type mask.
    496     auto typeMaskIter = std::find_if(begin(map), end(map), [](const ResTable_map& entry) -> bool {
    497         return util::deviceToHost32(entry.name.ident) == ResTable_map::ATTR_TYPE;
    498     });
    499 
    500     if (typeMaskIter != end(map)) {
    501         attr->typeMask = util::deviceToHost32(typeMaskIter->value.data);
    502     }
    503 
    504     for (const ResTable_map& mapEntry : map) {
    505         if (Res_INTERNALID(util::deviceToHost32(mapEntry.name.ident))) {
    506             switch (util::deviceToHost32(mapEntry.name.ident)) {
    507             case ResTable_map::ATTR_MIN:
    508                 attr->minInt = static_cast<int32_t>(mapEntry.value.data);
    509                 break;
    510             case ResTable_map::ATTR_MAX:
    511                 attr->maxInt = static_cast<int32_t>(mapEntry.value.data);
    512                 break;
    513             }
    514             continue;
    515         }
    516 
    517         if (attr->typeMask & (ResTable_map::TYPE_ENUM | ResTable_map::TYPE_FLAGS)) {
    518             Attribute::Symbol symbol;
    519             symbol.value = util::deviceToHost32(mapEntry.value.data);
    520             symbol.symbol = Reference(util::deviceToHost32(mapEntry.name.ident));
    521             attr->symbols.push_back(std::move(symbol));
    522         }
    523     }
    524 
    525     // TODO(adamlesinski): Find i80n, attributes.
    526     return attr;
    527 }
    528 
    529 std::unique_ptr<Array> BinaryResourceParser::parseArray(const ResourceNameRef& name,
    530                                                         const ConfigDescription& config,
    531                                                         const ResTable_map_entry* map) {
    532     std::unique_ptr<Array> array = util::make_unique<Array>();
    533     for (const ResTable_map& mapEntry : map) {
    534         array->items.push_back(parseValue(name, config, &mapEntry.value, 0));
    535     }
    536     return array;
    537 }
    538 
    539 std::unique_ptr<Plural> BinaryResourceParser::parsePlural(const ResourceNameRef& name,
    540                                                           const ConfigDescription& config,
    541                                                           const ResTable_map_entry* map) {
    542     std::unique_ptr<Plural> plural = util::make_unique<Plural>();
    543     for (const ResTable_map& mapEntry : map) {
    544         std::unique_ptr<Item> item = parseValue(name, config, &mapEntry.value, 0);
    545         if (!item) {
    546             return {};
    547         }
    548 
    549         switch (util::deviceToHost32(mapEntry.name.ident)) {
    550             case ResTable_map::ATTR_ZERO:
    551                 plural->values[Plural::Zero] = std::move(item);
    552                 break;
    553             case ResTable_map::ATTR_ONE:
    554                 plural->values[Plural::One] = std::move(item);
    555                 break;
    556             case ResTable_map::ATTR_TWO:
    557                 plural->values[Plural::Two] = std::move(item);
    558                 break;
    559             case ResTable_map::ATTR_FEW:
    560                 plural->values[Plural::Few] = std::move(item);
    561                 break;
    562             case ResTable_map::ATTR_MANY:
    563                 plural->values[Plural::Many] = std::move(item);
    564                 break;
    565             case ResTable_map::ATTR_OTHER:
    566                 plural->values[Plural::Other] = std::move(item);
    567                 break;
    568         }
    569     }
    570     return plural;
    571 }
    572 
    573 } // namespace aapt
    574