| /* |
| * Copyright (C) 2015 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 "link/ReferenceLinker.h" |
| |
| #include "android-base/logging.h" |
| #include "androidfw/ResourceTypes.h" |
| |
| #include "Diagnostics.h" |
| #include "ResourceTable.h" |
| #include "ResourceUtils.h" |
| #include "ResourceValues.h" |
| #include "ValueVisitor.h" |
| #include "link/Linkers.h" |
| #include "process/IResourceTableConsumer.h" |
| #include "process/SymbolTable.h" |
| #include "trace/TraceBuffer.h" |
| #include "util/Util.h" |
| #include "xml/XmlUtil.h" |
| |
| using ::aapt::ResourceUtils::StringBuilder; |
| using ::android::StringPiece; |
| |
| namespace aapt { |
| |
| namespace { |
| |
| // The ReferenceLinkerVisitor will follow all references and make sure they point |
| // to resources that actually exist, either in the local resource table, or as external |
| // symbols. Once the target resource has been found, the ID of the resource will be assigned |
| // to the reference object. |
| // |
| // NOTE: All of the entries in the ResourceTable must be assigned IDs. |
| class ReferenceLinkerVisitor : public DescendingValueVisitor { |
| public: |
| using DescendingValueVisitor::Visit; |
| |
| ReferenceLinkerVisitor(const CallSite& callsite, IAaptContext* context, SymbolTable* symbols, |
| StringPool* string_pool, xml::IPackageDeclStack* decl) |
| : callsite_(callsite), |
| context_(context), |
| symbols_(symbols), |
| package_decls_(decl), |
| string_pool_(string_pool) {} |
| |
| void Visit(Reference* ref) override { |
| if (!ReferenceLinker::LinkReference(callsite_, ref, context_, symbols_, package_decls_)) { |
| error_ = true; |
| } |
| } |
| |
| // We visit the Style specially because during this phase, values of attributes are |
| // all RawString values. Now that we are expected to resolve all symbols, we can |
| // lookup the attributes to find out which types are allowed for the attributes' values. |
| void Visit(Style* style) override { |
| if (style->parent) { |
| Visit(&style->parent.value()); |
| } |
| |
| for (Style::Entry& entry : style->entries) { |
| std::string err_str; |
| |
| // Transform the attribute reference so that it is using the fully qualified package |
| // name. This will also mark the reference as being able to see private resources if |
| // there was a '*' in the reference or if the package came from the private namespace. |
| Reference transformed_reference = entry.key; |
| ResolvePackage(package_decls_, &transformed_reference); |
| |
| // Find the attribute in the symbol table and check if it is visible from this callsite. |
| const SymbolTable::Symbol* symbol = ReferenceLinker::ResolveAttributeCheckVisibility( |
| transformed_reference, callsite_, symbols_, &err_str); |
| if (symbol) { |
| // Assign our style key the correct ID. The ID may not exist. |
| entry.key.id = symbol->id; |
| |
| // Try to convert the value to a more specific, typed value based on the attribute it is |
| // set to. |
| entry.value = ParseValueWithAttribute(std::move(entry.value), symbol->attribute.get()); |
| |
| // Link/resolve the final value (mostly if it's a reference). |
| entry.value->Accept(this); |
| |
| // Now verify that the type of this item is compatible with the |
| // attribute it is defined for. We pass `nullptr` as the DiagMessage so that this |
| // check is fast and we avoid creating a DiagMessage when the match is successful. |
| if (!symbol->attribute->Matches(*entry.value, nullptr)) { |
| // The actual type of this item is incompatible with the attribute. |
| DiagMessage msg(entry.key.GetSource()); |
| |
| // Call the matches method again, this time with a DiagMessage so we fill in the actual |
| // error message. |
| symbol->attribute->Matches(*entry.value, &msg); |
| context_->GetDiagnostics()->Error(msg); |
| error_ = true; |
| } |
| |
| } else { |
| DiagMessage msg(entry.key.GetSource()); |
| msg << "style attribute '"; |
| ReferenceLinker::WriteResourceName(entry.key, callsite_, package_decls_, &msg); |
| msg << "' " << err_str; |
| context_->GetDiagnostics()->Error(msg); |
| error_ = true; |
| } |
| } |
| } |
| |
| bool HasError() { |
| return error_; |
| } |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(ReferenceLinkerVisitor); |
| |
| // Transform a RawString value into a more specific, appropriate value, based on the |
| // Attribute. If a non RawString value is passed in, this is an identity transform. |
| std::unique_ptr<Item> ParseValueWithAttribute(std::unique_ptr<Item> value, |
| const Attribute* attr) { |
| if (RawString* raw_string = ValueCast<RawString>(value.get())) { |
| std::unique_ptr<Item> transformed = |
| ResourceUtils::TryParseItemForAttribute(*raw_string->value, attr); |
| |
| // If we could not parse as any specific type, try a basic STRING. |
| if (!transformed && (attr->type_mask & android::ResTable_map::TYPE_STRING)) { |
| StringBuilder string_builder; |
| string_builder.AppendText(*raw_string->value); |
| if (string_builder) { |
| transformed = |
| util::make_unique<String>(string_pool_->MakeRef(string_builder.to_string())); |
| } |
| } |
| |
| if (transformed) { |
| return transformed; |
| } |
| } |
| return value; |
| } |
| |
| const CallSite& callsite_; |
| IAaptContext* context_; |
| SymbolTable* symbols_; |
| xml::IPackageDeclStack* package_decls_; |
| StringPool* string_pool_; |
| bool error_ = false; |
| }; |
| |
| class EmptyDeclStack : public xml::IPackageDeclStack { |
| public: |
| EmptyDeclStack() = default; |
| |
| Maybe<xml::ExtractedPackage> TransformPackageAlias(const StringPiece& alias) const override { |
| if (alias.empty()) { |
| return xml::ExtractedPackage{{}, true /*private*/}; |
| } |
| return {}; |
| } |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(EmptyDeclStack); |
| }; |
| |
| // The symbol is visible if it is public, or if the reference to it is requesting private access |
| // or if the callsite comes from the same package. |
| bool IsSymbolVisible(const SymbolTable::Symbol& symbol, const Reference& ref, |
| const CallSite& callsite) { |
| if (symbol.is_public || ref.private_reference) { |
| return true; |
| } |
| |
| if (ref.name) { |
| const ResourceName& name = ref.name.value(); |
| if (name.package.empty()) { |
| // If the symbol was found, and the package is empty, that means it was found in the local |
| // scope, which is always visible (private local). |
| return true; |
| } |
| |
| // The symbol is visible if the reference is local to the same package it is defined in. |
| return callsite.package == name.package; |
| } |
| |
| if (ref.id && symbol.id) { |
| return ref.id.value().package_id() == symbol.id.value().package_id(); |
| } |
| return false; |
| } |
| |
| } // namespace |
| |
| const SymbolTable::Symbol* ReferenceLinker::ResolveSymbol(const Reference& reference, |
| const CallSite& callsite, |
| SymbolTable* symbols) { |
| if (reference.name) { |
| const ResourceName& name = reference.name.value(); |
| if (name.package.empty()) { |
| // Use the callsite's package name if no package name was defined. |
| return symbols->FindByName(ResourceName(callsite.package, name.type, name.entry)); |
| } |
| return symbols->FindByName(name); |
| } else if (reference.id) { |
| return symbols->FindById(reference.id.value()); |
| } else { |
| return nullptr; |
| } |
| } |
| |
| const SymbolTable::Symbol* ReferenceLinker::ResolveSymbolCheckVisibility(const Reference& reference, |
| const CallSite& callsite, |
| SymbolTable* symbols, |
| std::string* out_error) { |
| const SymbolTable::Symbol* symbol = ResolveSymbol(reference, callsite, symbols); |
| if (!symbol) { |
| if (out_error) *out_error = "not found"; |
| return nullptr; |
| } |
| |
| if (!IsSymbolVisible(*symbol, reference, callsite)) { |
| if (out_error) *out_error = "is private"; |
| return nullptr; |
| } |
| return symbol; |
| } |
| |
| const SymbolTable::Symbol* ReferenceLinker::ResolveAttributeCheckVisibility( |
| const Reference& reference, const CallSite& callsite, SymbolTable* symbols, |
| std::string* out_error) { |
| const SymbolTable::Symbol* symbol = |
| ResolveSymbolCheckVisibility(reference, callsite, symbols, out_error); |
| if (!symbol) { |
| return nullptr; |
| } |
| |
| if (!symbol->attribute) { |
| if (out_error) *out_error = "is not an attribute"; |
| return nullptr; |
| } |
| return symbol; |
| } |
| |
| Maybe<xml::AaptAttribute> ReferenceLinker::CompileXmlAttribute(const Reference& reference, |
| const CallSite& callsite, |
| SymbolTable* symbols, |
| std::string* out_error) { |
| const SymbolTable::Symbol* symbol = |
| ResolveAttributeCheckVisibility(reference, callsite, symbols, out_error); |
| if (!symbol) { |
| return {}; |
| } |
| |
| if (!symbol->attribute) { |
| if (out_error) *out_error = "is not an attribute"; |
| return {}; |
| } |
| return xml::AaptAttribute(*symbol->attribute, symbol->id); |
| } |
| |
| void ReferenceLinker::WriteResourceName(const Reference& ref, const CallSite& callsite, |
| const xml::IPackageDeclStack* decls, DiagMessage* out_msg) { |
| CHECK(out_msg != nullptr); |
| if (!ref.name) { |
| *out_msg << ref.id.value(); |
| return; |
| } |
| |
| *out_msg << ref.name.value(); |
| |
| Reference fully_qualified = ref; |
| xml::ResolvePackage(decls, &fully_qualified); |
| |
| ResourceName& full_name = fully_qualified.name.value(); |
| if (full_name.package.empty()) { |
| full_name.package = callsite.package; |
| } |
| |
| if (full_name != ref.name.value()) { |
| *out_msg << " (aka " << full_name << ")"; |
| } |
| } |
| |
| void ReferenceLinker::WriteAttributeName(const Reference& ref, const CallSite& callsite, |
| const xml::IPackageDeclStack* decls, |
| DiagMessage* out_msg) { |
| CHECK(out_msg != nullptr); |
| if (!ref.name) { |
| *out_msg << ref.id.value(); |
| return; |
| } |
| |
| const ResourceName& ref_name = ref.name.value(); |
| CHECK_EQ(ref_name.type, ResourceType::kAttr); |
| |
| if (!ref_name.package.empty()) { |
| *out_msg << ref_name.package << ":"; |
| } |
| *out_msg << ref_name.entry; |
| |
| Reference fully_qualified = ref; |
| xml::ResolvePackage(decls, &fully_qualified); |
| |
| ResourceName& full_name = fully_qualified.name.value(); |
| if (full_name.package.empty()) { |
| full_name.package = callsite.package; |
| } |
| |
| if (full_name != ref.name.value()) { |
| *out_msg << " (aka " << full_name.package << ":" << full_name.entry << ")"; |
| } |
| } |
| |
| bool ReferenceLinker::LinkReference(const CallSite& callsite, Reference* reference, |
| IAaptContext* context, SymbolTable* symbols, |
| const xml::IPackageDeclStack* decls) { |
| CHECK(reference != nullptr); |
| if (!reference->name && !reference->id) { |
| // This is @null. |
| return true; |
| } |
| |
| Reference transformed_reference = *reference; |
| xml::ResolvePackage(decls, &transformed_reference); |
| |
| std::string err_str; |
| const SymbolTable::Symbol* s = |
| ResolveSymbolCheckVisibility(transformed_reference, callsite, symbols, &err_str); |
| if (s) { |
| // The ID may not exist. This is fine because of the possibility of building |
| // against libraries without assigned IDs. |
| // Ex: Linking against own resources when building a static library. |
| reference->id = s->id; |
| reference->is_dynamic = s->is_dynamic; |
| return true; |
| } |
| |
| DiagMessage error_msg(reference->GetSource()); |
| error_msg << "resource "; |
| WriteResourceName(*reference, callsite, decls, &error_msg); |
| error_msg << " " << err_str; |
| context->GetDiagnostics()->Error(error_msg); |
| return false; |
| } |
| |
| bool ReferenceLinker::Consume(IAaptContext* context, ResourceTable* table) { |
| TRACE_NAME("ReferenceLinker::Consume"); |
| EmptyDeclStack decl_stack; |
| bool error = false; |
| for (auto& package : table->packages) { |
| // Since we're linking, each package must have a name. |
| CHECK(!package->name.empty()) << "all packages being linked must have a name"; |
| |
| for (auto& type : package->types) { |
| for (auto& entry : type->entries) { |
| // First, unmangle the name if necessary. |
| ResourceName name(package->name, type->type, entry->name); |
| NameMangler::Unmangle(&name.entry, &name.package); |
| |
| // Symbol state information may be lost if there is no value for the resource. |
| if (entry->visibility.level != Visibility::Level::kUndefined && entry->values.empty()) { |
| context->GetDiagnostics()->Error(DiagMessage(entry->visibility.source) |
| << "no definition for declared symbol '" << name |
| << "'"); |
| error = true; |
| } |
| |
| // Ensure that definitions for values declared as overlayable exist |
| if (entry->overlayable_item && entry->values.empty()) { |
| context->GetDiagnostics()->Error(DiagMessage(entry->overlayable_item.value().source) |
| << "no definition for overlayable symbol '" |
| << name << "'"); |
| error = true; |
| } |
| |
| // The context of this resource is the package in which it is defined. |
| const CallSite callsite{name.package}; |
| ReferenceLinkerVisitor visitor(callsite, context, context->GetExternalSymbols(), |
| &table->string_pool, &decl_stack); |
| |
| for (auto& config_value : entry->values) { |
| config_value->value->Accept(&visitor); |
| } |
| |
| if (visitor.HasError()) { |
| error = true; |
| } |
| } |
| } |
| } |
| return !error; |
| } |
| |
| } // namespace aapt |