blob: 8e49fabe6a5ccb1200a36957eef9ff957a45637e [file] [log] [blame]
/*
* 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 "android-base/stringprintf.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;
using ::android::base::StringPrintf;
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_, context_, 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,
IAaptContext* context,
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.
const SymbolTable::Symbol* symbol = symbols->FindByName(
ResourceName(callsite.package, name.type, name.entry));
if (symbol) {
return symbol;
}
// If the callsite package is the same as the current compilation package,
// check the feature split dependencies as well. Feature split resources
// can be referenced without a namespace, just like the base package.
// TODO: modify the package name of included splits instead of having the
// symbol table look up the resource in in every package. b/136105066
if (callsite.package == context->GetCompilationPackage()) {
const auto& split_name_dependencies = context->GetSplitNameDependencies();
for (const std::string& split_name : split_name_dependencies) {
std::string split_package =
StringPrintf("%s.%s", callsite.package.c_str(), split_name.c_str());
symbol = symbols->FindByName(ResourceName(split_package, name.type, name.entry));
if (symbol) {
return symbol;
}
}
}
return nullptr;
}
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,
IAaptContext* context,
SymbolTable* symbols,
std::string* out_error) {
const SymbolTable::Symbol* symbol = ResolveSymbol(reference, callsite, context, 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, IAaptContext* context,
SymbolTable* symbols, std::string* out_error) {
const SymbolTable::Symbol* symbol =
ResolveSymbolCheckVisibility(reference, callsite, context, 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,
IAaptContext* context,
SymbolTable* symbols,
std::string* out_error) {
const SymbolTable::Symbol* symbol =
ResolveAttributeCheckVisibility(reference, callsite, context, 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, context, 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