359 lines
12 KiB
C++
359 lines
12 KiB
C++
/*
|
|
* 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 "util/Util.h"
|
|
#include "xml/XmlUtil.h"
|
|
|
|
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 ValueVisitor {
|
|
public:
|
|
using ValueVisitor::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;
|
|
TransformReferenceFromNamespace(package_decls_,
|
|
context_->GetCompilationPackage(),
|
|
&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(&msg, entry.key, transformed_reference);
|
|
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)) {
|
|
util::StringBuilder string_builder;
|
|
string_builder.Append(*raw_string->value);
|
|
if (string_builder) {
|
|
transformed = util::make_unique<String>(string_pool_->MakeRef(string_builder.ToString()));
|
|
}
|
|
}
|
|
|
|
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 StringPiece& local_package) const override {
|
|
if (alias.empty()) {
|
|
return xml::ExtractedPackage{local_package.to_string(), true /* private */};
|
|
}
|
|
return {};
|
|
}
|
|
|
|
private:
|
|
DISALLOW_COPY_AND_ASSIGN(EmptyDeclStack);
|
|
};
|
|
|
|
} // namespace
|
|
|
|
/**
|
|
* 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 ReferenceLinker::IsSymbolVisible(const SymbolTable::Symbol& symbol,
|
|
const Reference& ref,
|
|
const CallSite& callsite) {
|
|
if (!symbol.is_public && !ref.private_reference) {
|
|
if (ref.name) {
|
|
return callsite.resource.package == ref.name.value().package;
|
|
} else if (ref.id && symbol.id) {
|
|
return ref.id.value().package_id() == symbol.id.value().package_id();
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
const SymbolTable::Symbol* ReferenceLinker::ResolveSymbol(const Reference& reference,
|
|
SymbolTable* symbols) {
|
|
if (reference.name) {
|
|
return symbols->FindByName(reference.name.value());
|
|
} 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, 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(DiagMessage* out_msg,
|
|
const Reference& orig,
|
|
const Reference& transformed) {
|
|
CHECK(out_msg != nullptr);
|
|
|
|
if (orig.name) {
|
|
*out_msg << orig.name.value();
|
|
if (transformed.name.value() != orig.name.value()) {
|
|
*out_msg << " (aka " << transformed.name.value() << ")";
|
|
}
|
|
} else {
|
|
*out_msg << orig.id.value();
|
|
}
|
|
}
|
|
|
|
bool ReferenceLinker::LinkReference(const CallSite& callsite, Reference* reference,
|
|
IAaptContext* context, SymbolTable* symbols,
|
|
xml::IPackageDeclStack* decls) {
|
|
CHECK(reference != nullptr);
|
|
if (!reference->name && !reference->id) {
|
|
// This is @null.
|
|
return true;
|
|
}
|
|
|
|
Reference transformed_reference = *reference;
|
|
TransformReferenceFromNamespace(decls, context->GetCompilationPackage(), &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;
|
|
return true;
|
|
}
|
|
|
|
DiagMessage error_msg(reference->GetSource());
|
|
error_msg << "resource ";
|
|
WriteResourceName(&error_msg, *reference, transformed_reference);
|
|
error_msg << " " << err_str;
|
|
context->GetDiagnostics()->Error(error_msg);
|
|
return false;
|
|
}
|
|
|
|
bool ReferenceLinker::Consume(IAaptContext* context, ResourceTable* table) {
|
|
EmptyDeclStack decl_stack;
|
|
bool error = false;
|
|
for (auto& package : table->packages) {
|
|
for (auto& type : package->types) {
|
|
for (auto& entry : type->entries) {
|
|
// Symbol state information may be lost if there is no value for the
|
|
// resource.
|
|
if (entry->symbol_status.state != SymbolState::kUndefined &&
|
|
entry->values.empty()) {
|
|
context->GetDiagnostics()->Error(
|
|
DiagMessage(entry->symbol_status.source)
|
|
<< "no definition for declared symbol '"
|
|
<< ResourceNameRef(package->name, type->type, entry->name)
|
|
<< "'");
|
|
error = true;
|
|
}
|
|
|
|
CallSite callsite = {ResourceNameRef(package->name, type->type, entry->name)};
|
|
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
|