blob: 3eb96bcaaa57a024e280c668a35b88bc9921d0f6 [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 "BinaryResourceParser.h"
#include "Logger.h"
#include "ResChunkPullParser.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;
template <typename T>
inline static const T* convertTo(const ResChunk_header* chunk) {
if (chunk->headerSize < sizeof(T)) {
return nullptr;
}
return reinterpret_cast<const T*>(chunk);
}
inline static const uint8_t* getChunkData(const ResChunk_header& chunk) {
return reinterpret_cast<const uint8_t*>(&chunk) + chunk.headerSize;
}
inline static size_t getChunkDataLen(const ResChunk_header& chunk) {
return chunk.size - chunk.headerSize;
}
/*
* 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::map<ResourceId, ResourceName>& cache) : 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 == std::end(mCache)) {
Logger::note() << "failed to find " << reference.id << std::endl;
} else {
reference.name = cacheIter->second;
reference.id = 0;
}
}
const std::map<ResourceId, ResourceName>& mCache;
};
BinaryResourceParser::BinaryResourceParser(std::shared_ptr<ResourceTable> table,
const Source& source,
const void* data,
size_t len) :
mTable(table), mSource(source), 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;
}
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]));
mTable->setPackage(StringPiece16(reinterpret_cast<const char16_t*>(packageHeader->name), len));
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;
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(mIdIndex);
for (auto& type : *mTable) {
for (auto& entry : type->entries) {
for (auto& configValue : entry->values) {
configValue.value->accept(visitor, {});
}
}
}
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->addResource(name, config, source, std::move(resourceValue))) {
return false;
}
if ((entry->flags & ResTable_entry::FLAG_PUBLIC) != 0) {
if (!mTable->markPublic(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 (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 {
// 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_NULL;
nullType.data = Res_value::DATA_NULL_UNDEFINED;
return util::make_unique<BinaryPrimitive>(nullType);
}
if (value->dataType == ExtendedTypes::TYPE_SENTINEL) {
return util::make_unique<Sentinel>();
}
if (value->dataType == ExtendedTypes::TYPE_RAW_STRING) {
return util::make_unique<RawString>(
mTable->getValueStringPool().makeRef(util::getString(mValuePool, value->data),
StringPool::Context{ 1, config }));
}
if (name.type == ResourceType::kId ||
(value->dataType == Res_value::TYPE_NULL &&
value->data == Res_value::DATA_NULL_UNDEFINED &&
(flags & ResTable_entry::FLAG_WEAK) != 0)) {
return util::make_unique<Id>();
}
// 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;
}
attr->symbols.push_back(Attribute::Symbol{
Reference(mapEntry.name.ident),
mapEntry.value.data
});
}
}
// 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) {
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