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 "ConfigDescription.h"
     18 #include "Logger.h"
     19 #include "NameMangler.h"
     20 #include "ResourceTable.h"
     21 #include "ResourceValues.h"
     22 #include "Util.h"
     23 
     24 #include <algorithm>
     25 #include <androidfw/ResourceTypes.h>
     26 #include <memory>
     27 #include <string>
     28 #include <tuple>
     29 
     30 namespace aapt {
     31 
     32 static bool compareConfigs(const ResourceConfigValue& lhs, const ConfigDescription& rhs) {
     33     return lhs.config < rhs;
     34 }
     35 
     36 static bool lessThanType(const std::unique_ptr<ResourceTableType>& lhs, ResourceType rhs) {
     37     return lhs->type < rhs;
     38 }
     39 
     40 static bool lessThanEntry(const std::unique_ptr<ResourceEntry>& lhs, const StringPiece16& rhs) {
     41     return lhs->name.compare(0, lhs->name.size(), rhs.data(), rhs.size()) < 0;
     42 }
     43 
     44 ResourceTable::ResourceTable() : mPackageId(kUnsetPackageId) {
     45     // Make sure attrs always have type ID 1.
     46     findOrCreateType(ResourceType::kAttr)->typeId = 1;
     47 }
     48 
     49 std::unique_ptr<ResourceTableType>& ResourceTable::findOrCreateType(ResourceType type) {
     50     auto last = mTypes.end();
     51     auto iter = std::lower_bound(mTypes.begin(), last, type, lessThanType);
     52     if (iter != last) {
     53         if ((*iter)->type == type) {
     54             return *iter;
     55         }
     56     }
     57     return *mTypes.emplace(iter, new ResourceTableType{ type });
     58 }
     59 
     60 std::unique_ptr<ResourceEntry>& ResourceTable::findOrCreateEntry(
     61         std::unique_ptr<ResourceTableType>& type, const StringPiece16& name) {
     62     auto last = type->entries.end();
     63     auto iter = std::lower_bound(type->entries.begin(), last, name, lessThanEntry);
     64     if (iter != last) {
     65         if (name == (*iter)->name) {
     66             return *iter;
     67         }
     68     }
     69     return *type->entries.emplace(iter, new ResourceEntry{ name });
     70 }
     71 
     72 struct IsAttributeVisitor : ConstValueVisitor {
     73     bool isAttribute = false;
     74 
     75     void visit(const Attribute&, ValueVisitorArgs&) override {
     76         isAttribute = true;
     77     }
     78 
     79     operator bool() {
     80         return isAttribute;
     81     }
     82 };
     83 
     84 /**
     85  * The default handler for collisions. A return value of -1 means keep the
     86  * existing value, 0 means fail, and +1 means take the incoming value.
     87  */
     88 static int defaultCollisionHandler(const Value& existing, const Value& incoming) {
     89     IsAttributeVisitor existingIsAttr, incomingIsAttr;
     90     existing.accept(existingIsAttr, {});
     91     incoming.accept(incomingIsAttr, {});
     92 
     93     if (!incomingIsAttr) {
     94         if (incoming.isWeak()) {
     95             // We're trying to add a weak resource but a resource
     96             // already exists. Keep the existing.
     97             return -1;
     98         } else if (existing.isWeak()) {
     99             // Override the weak resource with the new strong resource.
    100             return 1;
    101         }
    102         // The existing and incoming values are strong, this is an error
    103         // if the values are not both attributes.
    104         return 0;
    105     }
    106 
    107     if (!existingIsAttr) {
    108         if (existing.isWeak()) {
    109             // The existing value is not an attribute and it is weak,
    110             // so take the incoming attribute value.
    111             return 1;
    112         }
    113         // The existing value is not an attribute and it is strong,
    114         // so the incoming attribute value is an error.
    115         return 0;
    116     }
    117 
    118     //
    119     // Attribute specific handling. At this point we know both
    120     // values are attributes. Since we can declare and define
    121     // attributes all-over, we do special handling to see
    122     // which definition sticks.
    123     //
    124     const Attribute& existingAttr = static_cast<const Attribute&>(existing);
    125     const Attribute& incomingAttr = static_cast<const Attribute&>(incoming);
    126     if (existingAttr.typeMask == incomingAttr.typeMask) {
    127         // The two attributes are both DECLs, but they are plain attributes
    128         // with the same formats.
    129         // Keep the strongest one.
    130         return existingAttr.isWeak() ? 1 : -1;
    131     }
    132 
    133     if (existingAttr.isWeak() && existingAttr.typeMask == android::ResTable_map::TYPE_ANY) {
    134         // Any incoming attribute is better than this.
    135         return 1;
    136     }
    137 
    138     if (incomingAttr.isWeak() && incomingAttr.typeMask == android::ResTable_map::TYPE_ANY) {
    139         // The incoming attribute may be a USE instead of a DECL.
    140         // Keep the existing attribute.
    141         return -1;
    142     }
    143     return 0;
    144 }
    145 
    146 static constexpr const char16_t* kValidNameChars = u"._-";
    147 static constexpr const char16_t* kValidNameMangledChars = u"._-$";
    148 
    149 bool ResourceTable::addResource(const ResourceNameRef& name, const ConfigDescription& config,
    150                                 const SourceLine& source, std::unique_ptr<Value> value) {
    151     return addResourceImpl(name, ResourceId{}, config, source, std::move(value), kValidNameChars);
    152 }
    153 
    154 bool ResourceTable::addResource(const ResourceNameRef& name, const ResourceId resId,
    155                                 const ConfigDescription& config, const SourceLine& source,
    156                                 std::unique_ptr<Value> value) {
    157     return addResourceImpl(name, resId, config, source, std::move(value), kValidNameChars);
    158 }
    159 
    160 bool ResourceTable::addResourceAllowMangled(const ResourceNameRef& name,
    161                                             const ConfigDescription& config,
    162                                             const SourceLine& source,
    163                                             std::unique_ptr<Value> value) {
    164     return addResourceImpl(name, ResourceId{}, config, source, std::move(value),
    165                            kValidNameMangledChars);
    166 }
    167 
    168 bool ResourceTable::addResourceImpl(const ResourceNameRef& name, const ResourceId resId,
    169                                     const ConfigDescription& config, const SourceLine& source,
    170                                     std::unique_ptr<Value> value, const char16_t* validChars) {
    171     if (!name.package.empty() && name.package != mPackage) {
    172         Logger::error(source)
    173                 << "resource '"
    174                 << name
    175                 << "' has incompatible package. Must be '"
    176                 << mPackage
    177                 << "'."
    178                 << std::endl;
    179         return false;
    180     }
    181 
    182     auto badCharIter = util::findNonAlphaNumericAndNotInSet(name.entry, validChars);
    183     if (badCharIter != name.entry.end()) {
    184         Logger::error(source)
    185                 << "resource '"
    186                 << name
    187                 << "' has invalid entry name '"
    188                 << name.entry
    189                 << "'. Invalid character '"
    190                 << StringPiece16(badCharIter, 1)
    191                 << "'."
    192                 << std::endl;
    193         return false;
    194     }
    195 
    196     std::unique_ptr<ResourceTableType>& type = findOrCreateType(name.type);
    197     if (resId.isValid() && type->typeId != ResourceTableType::kUnsetTypeId &&
    198             type->typeId != resId.typeId()) {
    199         Logger::error(source)
    200                 << "trying to add resource '"
    201                 << name
    202                 << "' with ID "
    203                 << resId
    204                 << " but type '"
    205                 << type->type
    206                 << "' already has ID "
    207                 << std::hex << type->typeId << std::dec
    208                 << "."
    209                 << std::endl;
    210         return false;
    211     }
    212 
    213     std::unique_ptr<ResourceEntry>& entry = findOrCreateEntry(type, name.entry);
    214     if (resId.isValid() && entry->entryId != ResourceEntry::kUnsetEntryId &&
    215             entry->entryId != resId.entryId()) {
    216         Logger::error(source)
    217                 << "trying to add resource '"
    218                 << name
    219                 << "' with ID "
    220                 << resId
    221                 << " but resource already has ID "
    222                 << ResourceId(mPackageId, type->typeId, entry->entryId)
    223                 << "."
    224                 << std::endl;
    225         return false;
    226     }
    227 
    228     const auto endIter = std::end(entry->values);
    229     auto iter = std::lower_bound(std::begin(entry->values), endIter, config, compareConfigs);
    230     if (iter == endIter || iter->config != config) {
    231         // This resource did not exist before, add it.
    232         entry->values.insert(iter, ResourceConfigValue{ config, source, {}, std::move(value) });
    233     } else {
    234         int collisionResult = defaultCollisionHandler(*iter->value, *value);
    235         if (collisionResult > 0) {
    236             // Take the incoming value.
    237             *iter = ResourceConfigValue{ config, source, {}, std::move(value) };
    238         } else if (collisionResult == 0) {
    239             Logger::error(source)
    240                     << "duplicate value for resource '" << name << "' "
    241                     << "with config '" << iter->config << "'."
    242                     << std::endl;
    243 
    244             Logger::error(iter->source)
    245                     << "resource previously defined here."
    246                     << std::endl;
    247             return false;
    248         }
    249     }
    250 
    251     if (resId.isValid()) {
    252         type->typeId = resId.typeId();
    253         entry->entryId = resId.entryId();
    254     }
    255     return true;
    256 }
    257 
    258 bool ResourceTable::markPublic(const ResourceNameRef& name, const ResourceId resId,
    259                                const SourceLine& source) {
    260     return markPublicImpl(name, resId, source, kValidNameChars);
    261 }
    262 
    263 bool ResourceTable::markPublicAllowMangled(const ResourceNameRef& name, const ResourceId resId,
    264                                            const SourceLine& source) {
    265     return markPublicImpl(name, resId, source, kValidNameMangledChars);
    266 }
    267 
    268 bool ResourceTable::markPublicImpl(const ResourceNameRef& name, const ResourceId resId,
    269                                    const SourceLine& source, const char16_t* validChars) {
    270     if (!name.package.empty() && name.package != mPackage) {
    271         Logger::error(source)
    272                 << "resource '"
    273                 << name
    274                 << "' has incompatible package. Must be '"
    275                 << mPackage
    276                 << "'."
    277             << std::endl;
    278         return false;
    279     }
    280 
    281     auto badCharIter = util::findNonAlphaNumericAndNotInSet(name.entry, validChars);
    282     if (badCharIter != name.entry.end()) {
    283         Logger::error(source)
    284                 << "resource '"
    285                 << name
    286                 << "' has invalid entry name '"
    287                 << name.entry
    288                 << "'. Invalid character '"
    289                 << StringPiece16(badCharIter, 1)
    290                 << "'."
    291                 << std::endl;
    292         return false;
    293     }
    294 
    295     std::unique_ptr<ResourceTableType>& type = findOrCreateType(name.type);
    296     if (resId.isValid() && type->typeId != ResourceTableType::kUnsetTypeId &&
    297             type->typeId != resId.typeId()) {
    298         Logger::error(source)
    299                 << "trying to make resource '"
    300                 << name
    301                 << "' public with ID "
    302                 << resId
    303                 << " but type '"
    304                 << type->type
    305                 << "' already has ID "
    306                 << std::hex << type->typeId << std::dec
    307                 << "."
    308                 << std::endl;
    309         return false;
    310     }
    311 
    312     std::unique_ptr<ResourceEntry>& entry = findOrCreateEntry(type, name.entry);
    313     if (resId.isValid() && entry->entryId != ResourceEntry::kUnsetEntryId &&
    314             entry->entryId != resId.entryId()) {
    315         Logger::error(source)
    316                 << "trying to make resource '"
    317                 << name
    318                 << "' public with ID "
    319                 << resId
    320                 << " but resource already has ID "
    321                 << ResourceId(mPackageId, type->typeId, entry->entryId)
    322                 << "."
    323                 << std::endl;
    324         return false;
    325     }
    326 
    327     type->publicStatus.isPublic = true;
    328     entry->publicStatus.isPublic = true;
    329     entry->publicStatus.source = source;
    330 
    331     if (resId.isValid()) {
    332         type->typeId = resId.typeId();
    333         entry->entryId = resId.entryId();
    334     }
    335     return true;
    336 }
    337 
    338 bool ResourceTable::merge(ResourceTable&& other) {
    339     const bool mangleNames = mPackage != other.getPackage();
    340     std::u16string mangledName;
    341 
    342     for (auto& otherType : other) {
    343         std::unique_ptr<ResourceTableType>& type = findOrCreateType(otherType->type);
    344         if (otherType->publicStatus.isPublic) {
    345             if (type->publicStatus.isPublic && type->typeId != otherType->typeId) {
    346                 Logger::error() << "can not merge type '" << type->type
    347                                 << "': conflicting public IDs "
    348                                 << "(" << type->typeId << " vs " << otherType->typeId << ")."
    349                                 << std::endl;
    350                 return false;
    351             }
    352             type->publicStatus = std::move(otherType->publicStatus);
    353             type->typeId = otherType->typeId;
    354         }
    355 
    356         for (auto& otherEntry : otherType->entries) {
    357             const std::u16string* nameToAdd = &otherEntry->name;
    358             if (mangleNames) {
    359                 mangledName = otherEntry->name;
    360                 NameMangler::mangle(other.getPackage(), &mangledName);
    361                 nameToAdd = &mangledName;
    362             }
    363 
    364             std::unique_ptr<ResourceEntry>& entry = findOrCreateEntry(type, *nameToAdd);
    365             if (otherEntry->publicStatus.isPublic) {
    366                 if (entry->publicStatus.isPublic && entry->entryId != otherEntry->entryId) {
    367                     Logger::error() << "can not merge entry '" << type->type << "/" << entry->name
    368                                     << "': conflicting public IDs "
    369                                     << "(" << entry->entryId << " vs " << entry->entryId << ")."
    370                                     << std::endl;
    371                     return false;
    372                 }
    373                 entry->publicStatus = std::move(otherEntry->publicStatus);
    374                 entry->entryId = otherEntry->entryId;
    375             }
    376 
    377             for (ResourceConfigValue& otherValue : otherEntry->values) {
    378                 auto iter = std::lower_bound(entry->values.begin(), entry->values.end(),
    379                                              otherValue.config, compareConfigs);
    380                 if (iter != entry->values.end() && iter->config == otherValue.config) {
    381                     int collisionResult = defaultCollisionHandler(*iter->value, *otherValue.value);
    382                     if (collisionResult > 0) {
    383                         // Take the incoming value.
    384                         iter->source = std::move(otherValue.source);
    385                         iter->comment = std::move(otherValue.comment);
    386                         iter->value = std::unique_ptr<Value>(otherValue.value->clone(&mValuePool));
    387                     } else if (collisionResult == 0) {
    388                         ResourceNameRef resourceName = { mPackage, type->type, entry->name };
    389                         Logger::error(otherValue.source)
    390                                 << "resource '" << resourceName << "' has a conflicting value for "
    391                                 << "configuration (" << otherValue.config << ")."
    392                                 << std::endl;
    393                         Logger::note(iter->source) << "originally defined here." << std::endl;
    394                         return false;
    395                     }
    396                 } else {
    397                     entry->values.insert(iter, ResourceConfigValue{
    398                             otherValue.config,
    399                             std::move(otherValue.source),
    400                             std::move(otherValue.comment),
    401                             std::unique_ptr<Value>(otherValue.value->clone(&mValuePool)),
    402                     });
    403                 }
    404             }
    405         }
    406     }
    407     return true;
    408 }
    409 
    410 std::tuple<const ResourceTableType*, const ResourceEntry*>
    411 ResourceTable::findResource(const ResourceNameRef& name) const {
    412     if (name.package != mPackage) {
    413         return {};
    414     }
    415 
    416     auto iter = std::lower_bound(mTypes.begin(), mTypes.end(), name.type, lessThanType);
    417     if (iter == mTypes.end() || (*iter)->type != name.type) {
    418         return {};
    419     }
    420 
    421     const std::unique_ptr<ResourceTableType>& type = *iter;
    422     auto iter2 = std::lower_bound(type->entries.begin(), type->entries.end(), name.entry,
    423                                   lessThanEntry);
    424     if (iter2 == type->entries.end() || name.entry != (*iter2)->name) {
    425         return {};
    426     }
    427     return std::make_tuple(iter->get(), iter2->get());
    428 }
    429 
    430 } // namespace aapt
    431