6cc479b76e
The compile phase doesn't use the AndroidManifest, so we had to specify the package name on the command line. We can omit the package name, since we don't resolve external references in the compile phase. Packages that reference the current package will be encoded with no package name. When loaded by the link phase, the package name will be supplied and all the references with no package name will use that one. Change-Id: I9fe4902b747b06899b45c968f30ba1aa05c5cd69
898 lines
32 KiB
C++
898 lines
32 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 "BinaryResourceParser.h"
|
|
#include "Logger.h"
|
|
#include "ResChunkPullParser.h"
|
|
#include "Resolver.h"
|
|
#include "ResourceParser.h"
|
|
#include "ResourceTable.h"
|
|
#include "ResourceTypeExtensions.h"
|
|
#include "ResourceValues.h"
|
|
#include "Source.h"
|
|
#include "Util.h"
|
|
|
|
#include <androidfw/ResourceTypes.h>
|
|
#include <androidfw/TypeWrappers.h>
|
|
#include <map>
|
|
#include <string>
|
|
|
|
namespace aapt {
|
|
|
|
using namespace android;
|
|
|
|
/*
|
|
* Visitor that converts a reference's resource ID to a resource name,
|
|
* given a mapping from resource ID to resource name.
|
|
*/
|
|
struct ReferenceIdToNameVisitor : ValueVisitor {
|
|
ReferenceIdToNameVisitor(const std::shared_ptr<IResolver>& resolver,
|
|
std::map<ResourceId, ResourceName>* cache) :
|
|
mResolver(resolver), mCache(cache) {
|
|
}
|
|
|
|
void visit(Reference& reference, ValueVisitorArgs&) override {
|
|
idToName(reference);
|
|
}
|
|
|
|
void visit(Attribute& attr, ValueVisitorArgs&) override {
|
|
for (auto& entry : attr.symbols) {
|
|
idToName(entry.symbol);
|
|
}
|
|
}
|
|
|
|
void visit(Style& style, ValueVisitorArgs&) override {
|
|
if (style.parent.id.isValid()) {
|
|
idToName(style.parent);
|
|
}
|
|
|
|
for (auto& entry : style.entries) {
|
|
idToName(entry.key);
|
|
entry.value->accept(*this, {});
|
|
}
|
|
}
|
|
|
|
void visit(Styleable& styleable, ValueVisitorArgs&) override {
|
|
for (auto& attr : styleable.entries) {
|
|
idToName(attr);
|
|
}
|
|
}
|
|
|
|
void visit(Array& array, ValueVisitorArgs&) override {
|
|
for (auto& item : array.items) {
|
|
item->accept(*this, {});
|
|
}
|
|
}
|
|
|
|
void visit(Plural& plural, ValueVisitorArgs&) override {
|
|
for (auto& item : plural.values) {
|
|
if (item) {
|
|
item->accept(*this, {});
|
|
}
|
|
}
|
|
}
|
|
|
|
private:
|
|
void idToName(Reference& reference) {
|
|
if (!reference.id.isValid()) {
|
|
return;
|
|
}
|
|
|
|
auto cacheIter = mCache->find(reference.id);
|
|
if (cacheIter != mCache->end()) {
|
|
reference.name = cacheIter->second;
|
|
reference.id = 0;
|
|
} else {
|
|
Maybe<ResourceName> result = mResolver->findName(reference.id);
|
|
if (result) {
|
|
reference.name = result.value();
|
|
|
|
// Add to cache.
|
|
mCache->insert({reference.id, reference.name});
|
|
|
|
reference.id = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
std::shared_ptr<IResolver> mResolver;
|
|
std::map<ResourceId, ResourceName>* mCache;
|
|
};
|
|
|
|
|
|
BinaryResourceParser::BinaryResourceParser(const std::shared_ptr<ResourceTable>& table,
|
|
const std::shared_ptr<IResolver>& resolver,
|
|
const Source& source,
|
|
const std::u16string& defaultPackage,
|
|
const void* data,
|
|
size_t len) :
|
|
mTable(table), mResolver(resolver), mSource(source), mDefaultPackage(defaultPackage),
|
|
mData(data), mDataLen(len) {
|
|
}
|
|
|
|
bool BinaryResourceParser::parse() {
|
|
ResChunkPullParser parser(mData, mDataLen);
|
|
|
|
bool error = false;
|
|
while(ResChunkPullParser::isGoodEvent(parser.next())) {
|
|
if (parser.getChunk()->type != android::RES_TABLE_TYPE) {
|
|
Logger::warn(mSource)
|
|
<< "unknown chunk of type '"
|
|
<< parser.getChunk()->type
|
|
<< "'."
|
|
<< std::endl;
|
|
continue;
|
|
}
|
|
|
|
error |= !parseTable(parser.getChunk());
|
|
}
|
|
|
|
if (parser.getEvent() == ResChunkPullParser::Event::BadDocument) {
|
|
Logger::error(mSource)
|
|
<< "bad document: "
|
|
<< parser.getLastError()
|
|
<< "."
|
|
<< std::endl;
|
|
return false;
|
|
}
|
|
return !error;
|
|
}
|
|
|
|
bool BinaryResourceParser::getSymbol(const void* data, ResourceNameRef* outSymbol) {
|
|
if (!mSymbolEntries || mSymbolEntryCount == 0) {
|
|
return false;
|
|
}
|
|
|
|
if (reinterpret_cast<uintptr_t>(data) < reinterpret_cast<uintptr_t>(mData)) {
|
|
return false;
|
|
}
|
|
|
|
// We only support 32 bit offsets right now.
|
|
const uintptr_t offset = reinterpret_cast<uintptr_t>(data) -
|
|
reinterpret_cast<uintptr_t>(mData);
|
|
if (offset > std::numeric_limits<uint32_t>::max()) {
|
|
return false;
|
|
}
|
|
|
|
for (size_t i = 0; i < mSymbolEntryCount; i++) {
|
|
if (mSymbolEntries[i].offset == offset) {
|
|
// This offset is a symbol!
|
|
const StringPiece16 str = util::getString(mSymbolPool,
|
|
mSymbolEntries[i].stringIndex);
|
|
StringPiece16 typeStr;
|
|
ResourceParser::extractResourceName(str, &outSymbol->package, &typeStr,
|
|
&outSymbol->entry);
|
|
const ResourceType* type = parseResourceType(typeStr);
|
|
if (!type) {
|
|
return false;
|
|
}
|
|
if (outSymbol->package.empty()) {
|
|
outSymbol->package = mTable->getPackage();
|
|
}
|
|
outSymbol->type = *type;
|
|
|
|
// Since we scan the symbol table in order, we can start looking for the
|
|
// next symbol from this point.
|
|
mSymbolEntryCount -= i + 1;
|
|
mSymbolEntries += i + 1;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool BinaryResourceParser::parseSymbolTable(const ResChunk_header* chunk) {
|
|
const SymbolTable_header* symbolTableHeader = convertTo<SymbolTable_header>(chunk);
|
|
if (!symbolTableHeader) {
|
|
Logger::error(mSource)
|
|
<< "could not parse chunk as SymbolTable_header."
|
|
<< std::endl;
|
|
return false;
|
|
}
|
|
|
|
const size_t entrySizeBytes = symbolTableHeader->count * sizeof(SymbolTable_entry);
|
|
if (entrySizeBytes > getChunkDataLen(symbolTableHeader->header)) {
|
|
Logger::error(mSource)
|
|
<< "entries extend beyond chunk."
|
|
<< std::endl;
|
|
return false;
|
|
}
|
|
|
|
mSymbolEntries = reinterpret_cast<const SymbolTable_entry*>(
|
|
getChunkData(symbolTableHeader->header));
|
|
mSymbolEntryCount = symbolTableHeader->count;
|
|
|
|
ResChunkPullParser parser(getChunkData(symbolTableHeader->header) + entrySizeBytes,
|
|
getChunkDataLen(symbolTableHeader->header) - entrySizeBytes);
|
|
if (!ResChunkPullParser::isGoodEvent(parser.next())) {
|
|
Logger::error(mSource)
|
|
<< "failed to parse chunk: "
|
|
<< parser.getLastError()
|
|
<< "."
|
|
<< std::endl;
|
|
return false;
|
|
}
|
|
|
|
if (parser.getChunk()->type != android::RES_STRING_POOL_TYPE) {
|
|
Logger::error(mSource)
|
|
<< "expected Symbol string pool."
|
|
<< std::endl;
|
|
return false;
|
|
}
|
|
|
|
if (mSymbolPool.setTo(parser.getChunk(), parser.getChunk()->size) != NO_ERROR) {
|
|
Logger::error(mSource)
|
|
<< "failed to parse symbol string pool with code: "
|
|
<< mSymbolPool.getError()
|
|
<< "."
|
|
<< std::endl;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool BinaryResourceParser::parseTable(const ResChunk_header* chunk) {
|
|
const ResTable_header* tableHeader = convertTo<ResTable_header>(chunk);
|
|
if (!tableHeader) {
|
|
Logger::error(mSource)
|
|
<< "could not parse chunk as ResTable_header."
|
|
<< std::endl;
|
|
return false;
|
|
}
|
|
|
|
ResChunkPullParser parser(getChunkData(tableHeader->header),
|
|
getChunkDataLen(tableHeader->header));
|
|
while (ResChunkPullParser::isGoodEvent(parser.next())) {
|
|
switch (parser.getChunk()->type) {
|
|
case android::RES_STRING_POOL_TYPE:
|
|
if (mValuePool.getError() == NO_INIT) {
|
|
if (mValuePool.setTo(parser.getChunk(), parser.getChunk()->size) !=
|
|
NO_ERROR) {
|
|
Logger::error(mSource)
|
|
<< "failed to parse value string pool with code: "
|
|
<< mValuePool.getError()
|
|
<< "."
|
|
<< std::endl;
|
|
return false;
|
|
}
|
|
|
|
// Reserve some space for the strings we are going to add.
|
|
mTable->getValueStringPool().hintWillAdd(
|
|
mValuePool.size(), mValuePool.styleCount());
|
|
} else {
|
|
Logger::warn(mSource)
|
|
<< "unexpected string pool."
|
|
<< std::endl;
|
|
}
|
|
break;
|
|
|
|
case RES_TABLE_SYMBOL_TABLE_TYPE:
|
|
if (!parseSymbolTable(parser.getChunk())) {
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case RES_TABLE_SOURCE_POOL_TYPE: {
|
|
if (mSourcePool.setTo(getChunkData(*parser.getChunk()),
|
|
getChunkDataLen(*parser.getChunk())) != NO_ERROR) {
|
|
Logger::error(mSource)
|
|
<< "failed to parse source pool with code: "
|
|
<< mSourcePool.getError()
|
|
<< "."
|
|
<< std::endl;
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case android::RES_TABLE_PACKAGE_TYPE:
|
|
if (!parsePackage(parser.getChunk())) {
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
Logger::warn(mSource)
|
|
<< "unexpected chunk of type "
|
|
<< parser.getChunk()->type
|
|
<< "."
|
|
<< std::endl;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (parser.getEvent() == ResChunkPullParser::Event::BadDocument) {
|
|
Logger::error(mSource)
|
|
<< "bad resource table: " << parser.getLastError()
|
|
<< "."
|
|
<< std::endl;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool BinaryResourceParser::parsePackage(const ResChunk_header* chunk) {
|
|
if (mValuePool.getError() != NO_ERROR) {
|
|
Logger::error(mSource)
|
|
<< "no value string pool for ResTable."
|
|
<< std::endl;
|
|
return false;
|
|
}
|
|
|
|
const ResTable_package* packageHeader = convertTo<ResTable_package>(chunk);
|
|
if (!packageHeader) {
|
|
Logger::error(mSource)
|
|
<< "could not parse chunk as ResTable_header."
|
|
<< std::endl;
|
|
return false;
|
|
}
|
|
|
|
if (mTable->getPackageId() == ResourceTable::kUnsetPackageId) {
|
|
// This is the first time the table has it's package ID set.
|
|
mTable->setPackageId(packageHeader->id);
|
|
} else if (mTable->getPackageId() != packageHeader->id) {
|
|
Logger::error(mSource)
|
|
<< "ResTable_package has package ID "
|
|
<< std::hex << packageHeader->id << std::dec
|
|
<< " but ResourceTable has package ID "
|
|
<< std::hex << mTable->getPackageId() << std::dec
|
|
<< std::endl;
|
|
return false;
|
|
}
|
|
|
|
size_t len = strnlen16(reinterpret_cast<const char16_t*>(packageHeader->name),
|
|
sizeof(packageHeader->name) / sizeof(packageHeader->name[0]));
|
|
if (mTable->getPackage().empty() && len == 0) {
|
|
mTable->setPackage(mDefaultPackage);
|
|
} else if (len > 0) {
|
|
StringPiece16 thisPackage(reinterpret_cast<const char16_t*>(packageHeader->name), len);
|
|
if (mTable->getPackage().empty()) {
|
|
mTable->setPackage(thisPackage);
|
|
} else if (thisPackage != mTable->getPackage()) {
|
|
Logger::error(mSource)
|
|
<< "incompatible packages: "
|
|
<< mTable->getPackage()
|
|
<< " vs. "
|
|
<< thisPackage
|
|
<< std::endl;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
ResChunkPullParser parser(getChunkData(packageHeader->header),
|
|
getChunkDataLen(packageHeader->header));
|
|
while (ResChunkPullParser::isGoodEvent(parser.next())) {
|
|
switch (parser.getChunk()->type) {
|
|
case android::RES_STRING_POOL_TYPE:
|
|
if (mTypePool.getError() == NO_INIT) {
|
|
if (mTypePool.setTo(parser.getChunk(), parser.getChunk()->size) !=
|
|
NO_ERROR) {
|
|
Logger::error(mSource)
|
|
<< "failed to parse type string pool with code "
|
|
<< mTypePool.getError()
|
|
<< "."
|
|
<< std::endl;
|
|
return false;
|
|
}
|
|
} else if (mKeyPool.getError() == NO_INIT) {
|
|
if (mKeyPool.setTo(parser.getChunk(), parser.getChunk()->size) !=
|
|
NO_ERROR) {
|
|
Logger::error(mSource)
|
|
<< "failed to parse key string pool with code "
|
|
<< mKeyPool.getError()
|
|
<< "."
|
|
<< std::endl;
|
|
return false;
|
|
}
|
|
} else {
|
|
Logger::warn(mSource)
|
|
<< "unexpected string pool."
|
|
<< std::endl;
|
|
}
|
|
break;
|
|
|
|
case android::RES_TABLE_TYPE_SPEC_TYPE:
|
|
if (!parseTypeSpec(parser.getChunk())) {
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case android::RES_TABLE_TYPE_TYPE:
|
|
if (!parseType(parser.getChunk())) {
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case RES_TABLE_PUBLIC_TYPE:
|
|
if (!parsePublic(parser.getChunk())) {
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
Logger::warn(mSource)
|
|
<< "unexpected chunk of type "
|
|
<< parser.getChunk()->type
|
|
<< "."
|
|
<< std::endl;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (parser.getEvent() == ResChunkPullParser::Event::BadDocument) {
|
|
Logger::error(mSource)
|
|
<< "bad package: "
|
|
<< parser.getLastError()
|
|
<< "."
|
|
<< std::endl;
|
|
return false;
|
|
}
|
|
|
|
// Now go through the table and change resource ID references to
|
|
// symbolic references.
|
|
|
|
ReferenceIdToNameVisitor visitor(mResolver, &mIdIndex);
|
|
for (auto& type : *mTable) {
|
|
for (auto& entry : type->entries) {
|
|
for (auto& configValue : entry->values) {
|
|
configValue.value->accept(visitor, {});
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool BinaryResourceParser::parsePublic(const ResChunk_header* chunk) {
|
|
const Public_header* header = convertTo<Public_header>(chunk);
|
|
|
|
if (header->typeId == 0) {
|
|
Logger::error(mSource)
|
|
<< "invalid type ID " << header->typeId << std::endl;
|
|
return false;
|
|
}
|
|
|
|
const ResourceType* parsedType = parseResourceType(util::getString(mTypePool,
|
|
header->typeId - 1));
|
|
if (!parsedType) {
|
|
Logger::error(mSource)
|
|
<< "invalid type " << util::getString(mTypePool, header->typeId - 1) << std::endl;
|
|
return false;
|
|
}
|
|
|
|
const uintptr_t chunkEnd = reinterpret_cast<uintptr_t>(chunk) + chunk->size;
|
|
const Public_entry* entry = reinterpret_cast<const Public_entry*>(
|
|
getChunkData(header->header));
|
|
for (uint32_t i = 0; i < header->count; i++) {
|
|
if (reinterpret_cast<uintptr_t>(entry) + sizeof(*entry) > chunkEnd) {
|
|
Logger::error(mSource)
|
|
<< "Public_entry extends beyond chunk."
|
|
<< std::endl;
|
|
return false;
|
|
}
|
|
|
|
const ResourceId resId = { mTable->getPackageId(), header->typeId, entry->entryId };
|
|
const ResourceName name = {
|
|
mTable->getPackage(),
|
|
*parsedType,
|
|
util::getString(mKeyPool, entry->key.index).toString() };
|
|
|
|
SourceLine source;
|
|
if (mSourcePool.getError() == NO_ERROR) {
|
|
source.path = util::utf16ToUtf8(util::getString(mSourcePool, entry->source.index));
|
|
source.line = entry->sourceLine;
|
|
}
|
|
|
|
if (!mTable->markPublicAllowMangled(name, resId, source)) {
|
|
return false;
|
|
}
|
|
|
|
// Add this resource name->id mapping to the index so
|
|
// that we can resolve all ID references to name references.
|
|
auto cacheIter = mIdIndex.find(resId);
|
|
if (cacheIter == mIdIndex.end()) {
|
|
mIdIndex.insert({ resId, name });
|
|
}
|
|
|
|
entry++;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool BinaryResourceParser::parseTypeSpec(const ResChunk_header* chunk) {
|
|
if (mTypePool.getError() != NO_ERROR) {
|
|
Logger::error(mSource)
|
|
<< "no type string pool available for ResTable_typeSpec."
|
|
<< std::endl;
|
|
return false;
|
|
}
|
|
|
|
const ResTable_typeSpec* typeSpec = convertTo<ResTable_typeSpec>(chunk);
|
|
if (!typeSpec) {
|
|
Logger::error(mSource)
|
|
<< "could not parse chunk as ResTable_typeSpec."
|
|
<< std::endl;
|
|
return false;
|
|
}
|
|
|
|
if (typeSpec->id == 0) {
|
|
Logger::error(mSource)
|
|
<< "ResTable_typeSpec has invalid id: "
|
|
<< typeSpec->id
|
|
<< "."
|
|
<< std::endl;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool BinaryResourceParser::parseType(const ResChunk_header* chunk) {
|
|
if (mTypePool.getError() != NO_ERROR) {
|
|
Logger::error(mSource)
|
|
<< "no type string pool available for ResTable_typeSpec."
|
|
<< std::endl;
|
|
return false;
|
|
}
|
|
|
|
if (mKeyPool.getError() != NO_ERROR) {
|
|
Logger::error(mSource)
|
|
<< "no key string pool available for ResTable_type."
|
|
<< std::endl;
|
|
return false;
|
|
}
|
|
|
|
const ResTable_type* type = convertTo<ResTable_type>(chunk);
|
|
if (!type) {
|
|
Logger::error(mSource)
|
|
<< "could not parse chunk as ResTable_type."
|
|
<< std::endl;
|
|
return false;
|
|
}
|
|
|
|
if (type->id == 0) {
|
|
Logger::error(mSource)
|
|
<< "ResTable_type has invalid id: "
|
|
<< type->id
|
|
<< "."
|
|
<< std::endl;
|
|
return false;
|
|
}
|
|
|
|
const ConfigDescription config(type->config);
|
|
const StringPiece16 typeName = util::getString(mTypePool, type->id - 1);
|
|
|
|
const ResourceType* parsedType = parseResourceType(typeName);
|
|
if (!parsedType) {
|
|
Logger::error(mSource)
|
|
<< "invalid type name '"
|
|
<< typeName
|
|
<< "' for type with ID "
|
|
<< uint32_t(type->id)
|
|
<< "." << std::endl;
|
|
return false;
|
|
}
|
|
|
|
android::TypeVariant tv(type);
|
|
for (auto it = tv.beginEntries(); it != tv.endEntries(); ++it) {
|
|
if (!*it) {
|
|
continue;
|
|
}
|
|
|
|
const ResTable_entry* entry = *it;
|
|
const ResourceName name = {
|
|
mTable->getPackage(),
|
|
*parsedType,
|
|
util::getString(mKeyPool, entry->key.index).toString()
|
|
};
|
|
|
|
const ResourceId resId = { mTable->getPackageId(), type->id, it.index() };
|
|
|
|
std::unique_ptr<Value> resourceValue;
|
|
const ResTable_entry_source* sourceBlock = nullptr;
|
|
if (entry->flags & ResTable_entry::FLAG_COMPLEX) {
|
|
const ResTable_map_entry* mapEntry = static_cast<const ResTable_map_entry*>(entry);
|
|
if (mapEntry->size - sizeof(*mapEntry) == sizeof(*sourceBlock)) {
|
|
const uint8_t* data = reinterpret_cast<const uint8_t*>(mapEntry);
|
|
data += mapEntry->size - sizeof(*sourceBlock);
|
|
sourceBlock = reinterpret_cast<const ResTable_entry_source*>(data);
|
|
}
|
|
|
|
// TODO(adamlesinski): Check that the entry count is valid.
|
|
resourceValue = parseMapEntry(name, config, mapEntry);
|
|
} else {
|
|
if (entry->size - sizeof(*entry) == sizeof(*sourceBlock)) {
|
|
const uint8_t* data = reinterpret_cast<const uint8_t*>(entry);
|
|
data += entry->size - sizeof(*sourceBlock);
|
|
sourceBlock = reinterpret_cast<const ResTable_entry_source*>(data);
|
|
}
|
|
|
|
const Res_value* value = reinterpret_cast<const Res_value*>(
|
|
reinterpret_cast<const uint8_t*>(entry) + entry->size);
|
|
resourceValue = parseValue(name, config, value, entry->flags);
|
|
}
|
|
|
|
if (!resourceValue) {
|
|
// TODO(adamlesinski): For now this is ok, but it really shouldn't be.
|
|
continue;
|
|
}
|
|
|
|
SourceLine source = mSource.line(0);
|
|
if (sourceBlock) {
|
|
size_t len;
|
|
const char* str = mSourcePool.string8At(sourceBlock->pathIndex, &len);
|
|
if (str) {
|
|
source.path.assign(str, len);
|
|
}
|
|
source.line = sourceBlock->line;
|
|
}
|
|
|
|
if (!mTable->addResourceAllowMangled(name, config, source, std::move(resourceValue))) {
|
|
return false;
|
|
}
|
|
|
|
if ((entry->flags & ResTable_entry::FLAG_PUBLIC) != 0) {
|
|
if (!mTable->markPublicAllowMangled(name, resId, mSource.line(0))) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Add this resource name->id mapping to the index so
|
|
// that we can resolve all ID references to name references.
|
|
auto cacheIter = mIdIndex.find(resId);
|
|
if (cacheIter == mIdIndex.end()) {
|
|
mIdIndex.insert({ resId, name });
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
std::unique_ptr<Item> BinaryResourceParser::parseValue(const ResourceNameRef& name,
|
|
const ConfigDescription& config,
|
|
const Res_value* value,
|
|
uint16_t flags) {
|
|
if (name.type == ResourceType::kId) {
|
|
return util::make_unique<Id>();
|
|
}
|
|
|
|
if (value->dataType == Res_value::TYPE_STRING) {
|
|
StringPiece16 str = util::getString(mValuePool, value->data);
|
|
|
|
const ResStringPool_span* spans = mValuePool.styleAt(value->data);
|
|
if (spans != nullptr) {
|
|
StyleString styleStr = { str.toString() };
|
|
while (spans->name.index != ResStringPool_span::END) {
|
|
styleStr.spans.push_back(Span{
|
|
util::getString(mValuePool, spans->name.index).toString(),
|
|
spans->firstChar,
|
|
spans->lastChar
|
|
});
|
|
spans++;
|
|
}
|
|
return util::make_unique<StyledString>(
|
|
mTable->getValueStringPool().makeRef(
|
|
styleStr, StringPool::Context{1, config}));
|
|
} else {
|
|
if (name.type != ResourceType::kString &&
|
|
util::stringStartsWith<char16_t>(str, u"res/")) {
|
|
// This must be a FileReference.
|
|
return util::make_unique<FileReference>(mTable->getValueStringPool().makeRef(
|
|
str, StringPool::Context{ 0, config }));
|
|
}
|
|
|
|
// There are no styles associated with this string, so treat it as
|
|
// a simple string.
|
|
return util::make_unique<String>(
|
|
mTable->getValueStringPool().makeRef(
|
|
str, StringPool::Context{1, config}));
|
|
}
|
|
}
|
|
|
|
if (value->dataType == Res_value::TYPE_REFERENCE ||
|
|
value->dataType == Res_value::TYPE_ATTRIBUTE) {
|
|
const Reference::Type type = (value->dataType == Res_value::TYPE_REFERENCE) ?
|
|
Reference::Type::kResource : Reference::Type::kAttribute;
|
|
|
|
if (value->data != 0) {
|
|
// This is a normal reference.
|
|
return util::make_unique<Reference>(value->data, type);
|
|
}
|
|
|
|
// This reference has an invalid ID. Check if it is an unresolved symbol.
|
|
ResourceNameRef symbol;
|
|
if (getSymbol(&value->data, &symbol)) {
|
|
return util::make_unique<Reference>(symbol, type);
|
|
}
|
|
|
|
// This is not an unresolved symbol, so it must be the magic @null reference.
|
|
Res_value nullType = {};
|
|
nullType.dataType = Res_value::TYPE_REFERENCE;
|
|
return util::make_unique<BinaryPrimitive>(nullType);
|
|
}
|
|
|
|
if (value->dataType == ExtendedTypes::TYPE_RAW_STRING) {
|
|
return util::make_unique<RawString>(
|
|
mTable->getValueStringPool().makeRef(util::getString(mValuePool, value->data),
|
|
StringPool::Context{ 1, config }));
|
|
}
|
|
|
|
// Treat this as a raw binary primitive.
|
|
return util::make_unique<BinaryPrimitive>(*value);
|
|
}
|
|
|
|
std::unique_ptr<Value> BinaryResourceParser::parseMapEntry(const ResourceNameRef& name,
|
|
const ConfigDescription& config,
|
|
const ResTable_map_entry* map) {
|
|
switch (name.type) {
|
|
case ResourceType::kStyle:
|
|
return parseStyle(name, config, map);
|
|
case ResourceType::kAttr:
|
|
return parseAttr(name, config, map);
|
|
case ResourceType::kArray:
|
|
return parseArray(name, config, map);
|
|
case ResourceType::kStyleable:
|
|
return parseStyleable(name, config, map);
|
|
case ResourceType::kPlurals:
|
|
return parsePlural(name, config, map);
|
|
default:
|
|
break;
|
|
}
|
|
return {};
|
|
}
|
|
|
|
std::unique_ptr<Style> BinaryResourceParser::parseStyle(const ResourceNameRef& name,
|
|
const ConfigDescription& config,
|
|
const ResTable_map_entry* map) {
|
|
std::unique_ptr<Style> style = util::make_unique<Style>();
|
|
if (map->parent.ident == 0) {
|
|
// The parent is either not set or it is an unresolved symbol.
|
|
// Check to see if it is a symbol.
|
|
ResourceNameRef symbol;
|
|
if (getSymbol(&map->parent.ident, &symbol)) {
|
|
style->parent.name = symbol.toResourceName();
|
|
}
|
|
} else {
|
|
// The parent is a regular reference to a resource.
|
|
style->parent.id = map->parent.ident;
|
|
}
|
|
|
|
for (const ResTable_map& mapEntry : map) {
|
|
style->entries.emplace_back();
|
|
Style::Entry& styleEntry = style->entries.back();
|
|
|
|
if (mapEntry.name.ident == 0) {
|
|
// The map entry's key (attribute) is not set. This must be
|
|
// a symbol reference, so resolve it.
|
|
ResourceNameRef symbol;
|
|
bool result = getSymbol(&mapEntry.name.ident, &symbol);
|
|
assert(result);
|
|
styleEntry.key.name = symbol.toResourceName();
|
|
} else {
|
|
// The map entry's key (attribute) is a regular reference.
|
|
styleEntry.key.id = mapEntry.name.ident;
|
|
}
|
|
|
|
// Parse the attribute's value.
|
|
styleEntry.value = parseValue(name, config, &mapEntry.value, 0);
|
|
assert(styleEntry.value);
|
|
}
|
|
return style;
|
|
}
|
|
|
|
std::unique_ptr<Attribute> BinaryResourceParser::parseAttr(const ResourceNameRef& name,
|
|
const ConfigDescription& config,
|
|
const ResTable_map_entry* map) {
|
|
const bool isWeak = (map->flags & ResTable_entry::FLAG_WEAK) != 0;
|
|
std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(isWeak);
|
|
|
|
// First we must discover what type of attribute this is. Find the type mask.
|
|
auto typeMaskIter = std::find_if(begin(map), end(map), [](const ResTable_map& entry) -> bool {
|
|
return entry.name.ident == ResTable_map::ATTR_TYPE;
|
|
});
|
|
|
|
if (typeMaskIter != end(map)) {
|
|
attr->typeMask = typeMaskIter->value.data;
|
|
}
|
|
|
|
if (attr->typeMask & (ResTable_map::TYPE_ENUM | ResTable_map::TYPE_FLAGS)) {
|
|
for (const ResTable_map& mapEntry : map) {
|
|
if (Res_INTERNALID(mapEntry.name.ident)) {
|
|
continue;
|
|
}
|
|
|
|
Attribute::Symbol symbol;
|
|
symbol.value = mapEntry.value.data;
|
|
if (mapEntry.name.ident == 0) {
|
|
// The map entry's key (id) is not set. This must be
|
|
// a symbol reference, so resolve it.
|
|
ResourceNameRef symbolName;
|
|
bool result = getSymbol(&mapEntry.name.ident, &symbolName);
|
|
assert(result);
|
|
symbol.symbol.name = symbolName.toResourceName();
|
|
} else {
|
|
// The map entry's key (id) is a regular reference.
|
|
symbol.symbol.id = mapEntry.name.ident;
|
|
}
|
|
|
|
attr->symbols.push_back(std::move(symbol));
|
|
}
|
|
}
|
|
|
|
// TODO(adamlesinski): Find min, max, i80n, etc attributes.
|
|
return attr;
|
|
}
|
|
|
|
std::unique_ptr<Array> BinaryResourceParser::parseArray(const ResourceNameRef& name,
|
|
const ConfigDescription& config,
|
|
const ResTable_map_entry* map) {
|
|
std::unique_ptr<Array> array = util::make_unique<Array>();
|
|
for (const ResTable_map& mapEntry : map) {
|
|
array->items.push_back(parseValue(name, config, &mapEntry.value, 0));
|
|
}
|
|
return array;
|
|
}
|
|
|
|
std::unique_ptr<Styleable> BinaryResourceParser::parseStyleable(const ResourceNameRef& name,
|
|
const ConfigDescription& config,
|
|
const ResTable_map_entry* map) {
|
|
std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>();
|
|
for (const ResTable_map& mapEntry : map) {
|
|
if (mapEntry.name.ident == 0) {
|
|
// The map entry's key (attribute) is not set. This must be
|
|
// a symbol reference, so resolve it.
|
|
ResourceNameRef symbol;
|
|
bool result = getSymbol(&mapEntry.name.ident, &symbol);
|
|
assert(result);
|
|
styleable->entries.emplace_back(symbol);
|
|
} else {
|
|
// The map entry's key (attribute) is a regular reference.
|
|
styleable->entries.emplace_back(mapEntry.name.ident);
|
|
}
|
|
}
|
|
return styleable;
|
|
}
|
|
|
|
std::unique_ptr<Plural> BinaryResourceParser::parsePlural(const ResourceNameRef& name,
|
|
const ConfigDescription& config,
|
|
const ResTable_map_entry* map) {
|
|
std::unique_ptr<Plural> plural = util::make_unique<Plural>();
|
|
for (const ResTable_map& mapEntry : map) {
|
|
std::unique_ptr<Item> item = parseValue(name, config, &mapEntry.value, 0);
|
|
|
|
switch (mapEntry.name.ident) {
|
|
case android::ResTable_map::ATTR_ZERO:
|
|
plural->values[Plural::Zero] = std::move(item);
|
|
break;
|
|
case android::ResTable_map::ATTR_ONE:
|
|
plural->values[Plural::One] = std::move(item);
|
|
break;
|
|
case android::ResTable_map::ATTR_TWO:
|
|
plural->values[Plural::Two] = std::move(item);
|
|
break;
|
|
case android::ResTable_map::ATTR_FEW:
|
|
plural->values[Plural::Few] = std::move(item);
|
|
break;
|
|
case android::ResTable_map::ATTR_MANY:
|
|
plural->values[Plural::Many] = std::move(item);
|
|
break;
|
|
case android::ResTable_map::ATTR_OTHER:
|
|
plural->values[Plural::Other] = std::move(item);
|
|
break;
|
|
}
|
|
}
|
|
return plural;
|
|
}
|
|
|
|
} // namespace aapt
|