| /* | |
| * Copyright (C) 2016 The Android Open Source Project | |
| * | |
| * Licensed under the Apache License, Version 2.0 (the "License"); | |
| * you may not use this file except in compliance with the License. | |
| * You may obtain a copy of the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, software | |
| * distributed under the License is distributed on an "AS IS" BASIS, | |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| * See the License for the specific language governing permissions and | |
| * limitations under the License. | |
| */ | |
| #include "ResourceTable.h" | |
| #include "ResourceUtils.h" | |
| #include "ValueVisitor.h" | |
| #include "proto/ProtoHelpers.h" | |
| #include "proto/ProtoSerialize.h" | |
| #include <androidfw/ResourceTypes.h> | |
| namespace aapt { | |
| namespace { | |
| class ReferenceIdToNameVisitor : public ValueVisitor { | |
| public: | |
| using ValueVisitor::visit; | |
| explicit ReferenceIdToNameVisitor(const std::map<ResourceId, ResourceNameRef>* mapping) : | |
| mMapping(mapping) { | |
| assert(mMapping); | |
| } | |
| void visit(Reference* reference) override { | |
| if (!reference->id || !reference->id.value().isValid()) { | |
| return; | |
| } | |
| ResourceId id = reference->id.value(); | |
| auto cacheIter = mMapping->find(id); | |
| if (cacheIter != mMapping->end()) { | |
| reference->name = cacheIter->second.toResourceName(); | |
| } | |
| } | |
| private: | |
| const std::map<ResourceId, ResourceNameRef>* mMapping; | |
| }; | |
| class PackagePbDeserializer { | |
| public: | |
| PackagePbDeserializer(const android::ResStringPool* valuePool, | |
| const android::ResStringPool* sourcePool, | |
| const android::ResStringPool* symbolPool, | |
| const Source& source, IDiagnostics* diag) : | |
| mValuePool(valuePool), mSourcePool(sourcePool), mSymbolPool(symbolPool), | |
| mSource(source), mDiag(diag) { | |
| } | |
| public: | |
| bool deserializeFromPb(const pb::Package& pbPackage, ResourceTable* table) { | |
| Maybe<uint8_t> id; | |
| if (pbPackage.has_package_id()) { | |
| id = static_cast<uint8_t>(pbPackage.package_id()); | |
| } | |
| std::map<ResourceId, ResourceNameRef> idIndex; | |
| ResourceTablePackage* pkg = table->createPackage(pbPackage.package_name(), id); | |
| for (const pb::Type& pbType : pbPackage.types()) { | |
| const ResourceType* resType = parseResourceType(pbType.name()); | |
| if (!resType) { | |
| mDiag->error(DiagMessage(mSource) << "unknown type '" << pbType.name() << "'"); | |
| return {}; | |
| } | |
| ResourceTableType* type = pkg->findOrCreateType(*resType); | |
| for (const pb::Entry& pbEntry : pbType.entries()) { | |
| ResourceEntry* entry = type->findOrCreateEntry(pbEntry.name()); | |
| // Deserialize the symbol status (public/private with source and comments). | |
| if (pbEntry.has_symbol_status()) { | |
| const pb::SymbolStatus& pbStatus = pbEntry.symbol_status(); | |
| if (pbStatus.has_source()) { | |
| deserializeSourceFromPb(pbStatus.source(), *mSourcePool, | |
| &entry->symbolStatus.source); | |
| } | |
| if (pbStatus.has_comment()) { | |
| entry->symbolStatus.comment = pbStatus.comment(); | |
| } | |
| SymbolState visibility = deserializeVisibilityFromPb(pbStatus.visibility()); | |
| entry->symbolStatus.state = visibility; | |
| if (visibility == SymbolState::kPublic) { | |
| // This is a public symbol, we must encode the ID now if there is one. | |
| if (pbEntry.has_id()) { | |
| entry->id = static_cast<uint16_t>(pbEntry.id()); | |
| } | |
| if (type->symbolStatus.state != SymbolState::kPublic) { | |
| // If the type has not been made public, do so now. | |
| type->symbolStatus.state = SymbolState::kPublic; | |
| if (pbType.has_id()) { | |
| type->id = static_cast<uint8_t>(pbType.id()); | |
| } | |
| } | |
| } else if (visibility == SymbolState::kPrivate) { | |
| if (type->symbolStatus.state == SymbolState::kUndefined) { | |
| type->symbolStatus.state = SymbolState::kPrivate; | |
| } | |
| } | |
| } | |
| ResourceId resId(pbPackage.package_id(), pbType.id(), pbEntry.id()); | |
| if (resId.isValid()) { | |
| idIndex[resId] = ResourceNameRef(pkg->name, type->type, entry->name); | |
| } | |
| for (const pb::ConfigValue& pbConfigValue : pbEntry.config_values()) { | |
| const pb::ConfigDescription& pbConfig = pbConfigValue.config(); | |
| ConfigDescription config; | |
| if (!deserializeConfigDescriptionFromPb(pbConfig, &config)) { | |
| mDiag->error(DiagMessage(mSource) << "invalid configuration"); | |
| return {}; | |
| } | |
| ResourceConfigValue* configValue = entry->findOrCreateValue(config, | |
| pbConfig.product()); | |
| if (configValue->value) { | |
| // Duplicate config. | |
| mDiag->error(DiagMessage(mSource) << "duplicate configuration"); | |
| return {}; | |
| } | |
| configValue->value = deserializeValueFromPb(pbConfigValue.value(), | |
| config, &table->stringPool); | |
| if (!configValue->value) { | |
| return {}; | |
| } | |
| } | |
| } | |
| } | |
| ReferenceIdToNameVisitor visitor(&idIndex); | |
| visitAllValuesInPackage(pkg, &visitor); | |
| return true; | |
| } | |
| private: | |
| std::unique_ptr<Item> deserializeItemFromPb(const pb::Item& pbItem, | |
| const ConfigDescription& config, | |
| StringPool* pool) { | |
| if (pbItem.has_ref()) { | |
| const pb::Reference& pbRef = pbItem.ref(); | |
| std::unique_ptr<Reference> ref = util::make_unique<Reference>(); | |
| if (!deserializeReferenceFromPb(pbRef, ref.get())) { | |
| return {}; | |
| } | |
| return std::move(ref); | |
| } else if (pbItem.has_prim()) { | |
| const pb::Primitive& pbPrim = pbItem.prim(); | |
| android::Res_value prim = {}; | |
| prim.dataType = static_cast<uint8_t>(pbPrim.type()); | |
| prim.data = pbPrim.data(); | |
| return util::make_unique<BinaryPrimitive>(prim); | |
| } else if (pbItem.has_id()) { | |
| return util::make_unique<Id>(); | |
| } else if (pbItem.has_str()) { | |
| const uint32_t idx = pbItem.str().idx(); | |
| const std::string str = util::getString(*mValuePool, idx); | |
| const android::ResStringPool_span* spans = mValuePool->styleAt(idx); | |
| if (spans && spans->name.index != android::ResStringPool_span::END) { | |
| StyleString styleStr = { str }; | |
| while (spans->name.index != android::ResStringPool_span::END) { | |
| styleStr.spans.push_back(Span{ | |
| util::getString(*mValuePool, spans->name.index), | |
| spans->firstChar, | |
| spans->lastChar | |
| }); | |
| spans++; | |
| } | |
| return util::make_unique<StyledString>( | |
| pool->makeRef(styleStr, StringPool::Context{ 1, config })); | |
| } | |
| return util::make_unique<String>( | |
| pool->makeRef(str, StringPool::Context{ 1, config })); | |
| } else if (pbItem.has_raw_str()) { | |
| const uint32_t idx = pbItem.raw_str().idx(); | |
| const std::string str = util::getString(*mValuePool, idx); | |
| return util::make_unique<RawString>( | |
| pool->makeRef(str, StringPool::Context{ 1, config })); | |
| } else if (pbItem.has_file()) { | |
| const uint32_t idx = pbItem.file().path_idx(); | |
| const std::string str = util::getString(*mValuePool, idx); | |
| return util::make_unique<FileReference>( | |
| pool->makeRef(str, StringPool::Context{ 0, config })); | |
| } else { | |
| mDiag->error(DiagMessage(mSource) << "unknown item"); | |
| } | |
| return {}; | |
| } | |
| std::unique_ptr<Value> deserializeValueFromPb(const pb::Value& pbValue, | |
| const ConfigDescription& config, | |
| StringPool* pool) { | |
| const bool isWeak = pbValue.has_weak() ? pbValue.weak() : false; | |
| std::unique_ptr<Value> value; | |
| if (pbValue.has_item()) { | |
| value = deserializeItemFromPb(pbValue.item(), config, pool); | |
| if (!value) { | |
| return {}; | |
| } | |
| } else if (pbValue.has_compound_value()) { | |
| const pb::CompoundValue& pbCompoundValue = pbValue.compound_value(); | |
| if (pbCompoundValue.has_attr()) { | |
| const pb::Attribute& pbAttr = pbCompoundValue.attr(); | |
| std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(isWeak); | |
| attr->typeMask = pbAttr.format_flags(); | |
| attr->minInt = pbAttr.min_int(); | |
| attr->maxInt = pbAttr.max_int(); | |
| for (const pb::Attribute_Symbol& pbSymbol : pbAttr.symbols()) { | |
| Attribute::Symbol symbol; | |
| deserializeItemCommon(pbSymbol, &symbol.symbol); | |
| if (!deserializeReferenceFromPb(pbSymbol.name(), &symbol.symbol)) { | |
| return {}; | |
| } | |
| symbol.value = pbSymbol.value(); | |
| attr->symbols.push_back(std::move(symbol)); | |
| } | |
| value = std::move(attr); | |
| } else if (pbCompoundValue.has_style()) { | |
| const pb::Style& pbStyle = pbCompoundValue.style(); | |
| std::unique_ptr<Style> style = util::make_unique<Style>(); | |
| if (pbStyle.has_parent()) { | |
| style->parent = Reference(); | |
| if (!deserializeReferenceFromPb(pbStyle.parent(), &style->parent.value())) { | |
| return {}; | |
| } | |
| if (pbStyle.has_parent_source()) { | |
| Source parentSource; | |
| deserializeSourceFromPb(pbStyle.parent_source(), *mSourcePool, | |
| &parentSource); | |
| style->parent.value().setSource(std::move(parentSource)); | |
| } | |
| } | |
| for (const pb::Style_Entry& pbEntry : pbStyle.entries()) { | |
| Style::Entry entry; | |
| deserializeItemCommon(pbEntry, &entry.key); | |
| if (!deserializeReferenceFromPb(pbEntry.key(), &entry.key)) { | |
| return {}; | |
| } | |
| entry.value = deserializeItemFromPb(pbEntry.item(), config, pool); | |
| if (!entry.value) { | |
| return {}; | |
| } | |
| deserializeItemCommon(pbEntry, entry.value.get()); | |
| style->entries.push_back(std::move(entry)); | |
| } | |
| value = std::move(style); | |
| } else if (pbCompoundValue.has_styleable()) { | |
| const pb::Styleable& pbStyleable = pbCompoundValue.styleable(); | |
| std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>(); | |
| for (const pb::Styleable_Entry& pbEntry : pbStyleable.entries()) { | |
| Reference attrRef; | |
| deserializeItemCommon(pbEntry, &attrRef); | |
| deserializeReferenceFromPb(pbEntry.attr(), &attrRef); | |
| styleable->entries.push_back(std::move(attrRef)); | |
| } | |
| value = std::move(styleable); | |
| } else if (pbCompoundValue.has_array()) { | |
| const pb::Array& pbArray = pbCompoundValue.array(); | |
| std::unique_ptr<Array> array = util::make_unique<Array>(); | |
| for (const pb::Array_Entry& pbEntry : pbArray.entries()) { | |
| std::unique_ptr<Item> item = deserializeItemFromPb(pbEntry.item(), config, | |
| pool); | |
| if (!item) { | |
| return {}; | |
| } | |
| deserializeItemCommon(pbEntry, item.get()); | |
| array->items.push_back(std::move(item)); | |
| } | |
| value = std::move(array); | |
| } else if (pbCompoundValue.has_plural()) { | |
| const pb::Plural& pbPlural = pbCompoundValue.plural(); | |
| std::unique_ptr<Plural> plural = util::make_unique<Plural>(); | |
| for (const pb::Plural_Entry& pbEntry : pbPlural.entries()) { | |
| size_t pluralIdx = deserializePluralEnumFromPb(pbEntry.arity()); | |
| plural->values[pluralIdx] = deserializeItemFromPb(pbEntry.item(), config, | |
| pool); | |
| if (!plural->values[pluralIdx]) { | |
| return {}; | |
| } | |
| deserializeItemCommon(pbEntry, plural->values[pluralIdx].get()); | |
| } | |
| value = std::move(plural); | |
| } else { | |
| mDiag->error(DiagMessage(mSource) << "unknown compound value"); | |
| return {}; | |
| } | |
| } else { | |
| mDiag->error(DiagMessage(mSource) << "unknown value"); | |
| return {}; | |
| } | |
| assert(value && "forgot to set value"); | |
| value->setWeak(isWeak); | |
| deserializeItemCommon(pbValue, value.get()); | |
| return value; | |
| } | |
| bool deserializeReferenceFromPb(const pb::Reference& pbRef, Reference* outRef) { | |
| outRef->referenceType = deserializeReferenceTypeFromPb(pbRef.type()); | |
| outRef->privateReference = pbRef.private_(); | |
| if (!pbRef.has_id() && !pbRef.has_symbol_idx()) { | |
| return false; | |
| } | |
| if (pbRef.has_id()) { | |
| outRef->id = ResourceId(pbRef.id()); | |
| } | |
| if (pbRef.has_symbol_idx()) { | |
| const std::string strSymbol = util::getString(*mSymbolPool, pbRef.symbol_idx()); | |
| ResourceNameRef nameRef; | |
| if (!ResourceUtils::parseResourceName(strSymbol, &nameRef, nullptr)) { | |
| mDiag->error(DiagMessage(mSource) << "invalid reference name '" | |
| << strSymbol << "'"); | |
| return false; | |
| } | |
| outRef->name = nameRef.toResourceName(); | |
| } | |
| return true; | |
| } | |
| template <typename T> | |
| void deserializeItemCommon(const T& pbItem, Value* outValue) { | |
| if (pbItem.has_source()) { | |
| Source source; | |
| deserializeSourceFromPb(pbItem.source(), *mSourcePool, &source); | |
| outValue->setSource(std::move(source)); | |
| } | |
| if (pbItem.has_comment()) { | |
| outValue->setComment(pbItem.comment()); | |
| } | |
| } | |
| private: | |
| const android::ResStringPool* mValuePool; | |
| const android::ResStringPool* mSourcePool; | |
| const android::ResStringPool* mSymbolPool; | |
| const Source mSource; | |
| IDiagnostics* mDiag; | |
| }; | |
| } // namespace | |
| std::unique_ptr<ResourceTable> deserializeTableFromPb(const pb::ResourceTable& pbTable, | |
| const Source& source, | |
| IDiagnostics* diag) { | |
| // We import the android namespace because on Windows NO_ERROR is a macro, not an enum, which | |
| // causes errors when qualifying it with android:: | |
| using namespace android; | |
| std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>(); | |
| if (!pbTable.has_string_pool()) { | |
| diag->error(DiagMessage(source) << "no string pool found"); | |
| return {}; | |
| } | |
| ResStringPool valuePool; | |
| status_t result = valuePool.setTo(pbTable.string_pool().data().data(), | |
| pbTable.string_pool().data().size()); | |
| if (result != NO_ERROR) { | |
| diag->error(DiagMessage(source) << "invalid string pool"); | |
| return {}; | |
| } | |
| ResStringPool sourcePool; | |
| if (pbTable.has_source_pool()) { | |
| result = sourcePool.setTo(pbTable.source_pool().data().data(), | |
| pbTable.source_pool().data().size()); | |
| if (result != NO_ERROR) { | |
| diag->error(DiagMessage(source) << "invalid source pool"); | |
| return {}; | |
| } | |
| } | |
| ResStringPool symbolPool; | |
| if (pbTable.has_symbol_pool()) { | |
| result = symbolPool.setTo(pbTable.symbol_pool().data().data(), | |
| pbTable.symbol_pool().data().size()); | |
| if (result != NO_ERROR) { | |
| diag->error(DiagMessage(source) << "invalid symbol pool"); | |
| return {}; | |
| } | |
| } | |
| PackagePbDeserializer packagePbDeserializer(&valuePool, &sourcePool, &symbolPool, source, diag); | |
| for (const pb::Package& pbPackage : pbTable.packages()) { | |
| if (!packagePbDeserializer.deserializeFromPb(pbPackage, table.get())) { | |
| return {}; | |
| } | |
| } | |
| return table; | |
| } | |
| std::unique_ptr<ResourceFile> deserializeCompiledFileFromPb(const pb::CompiledFile& pbFile, | |
| const Source& source, | |
| IDiagnostics* diag) { | |
| std::unique_ptr<ResourceFile> file = util::make_unique<ResourceFile>(); | |
| ResourceNameRef nameRef; | |
| // Need to create an lvalue here so that nameRef can point to something real. | |
| if (!ResourceUtils::parseResourceName(pbFile.resource_name(), &nameRef)) { | |
| diag->error(DiagMessage(source) << "invalid resource name in compiled file header: " | |
| << pbFile.resource_name()); | |
| return {}; | |
| } | |
| file->name = nameRef.toResourceName(); | |
| file->source.path = pbFile.source_path(); | |
| deserializeConfigDescriptionFromPb(pbFile.config(), &file->config); | |
| for (const pb::CompiledFile_Symbol& pbSymbol : pbFile.exported_symbols()) { | |
| // Need to create an lvalue here so that nameRef can point to something real. | |
| if (!ResourceUtils::parseResourceName(pbSymbol.resource_name(), &nameRef)) { | |
| diag->error(DiagMessage(source) << "invalid resource name for exported symbol in " | |
| "compiled file header: " | |
| << pbFile.resource_name()); | |
| return {}; | |
| } | |
| file->exportedSymbols.push_back( | |
| SourcedResourceName{ nameRef.toResourceName(), pbSymbol.line_no() }); | |
| } | |
| return file; | |
| } | |
| } // namespace aapt |