Adam Lesinski 75f3a55cc5 AAPT2: Change xml file parsing to DOM based
We modify the XML of layouts and AndroidManifest enough
that it warrants we operate on the tree in memory.
These files are never very large so this should be fine.

Change-Id: I5d597abdb3fca2a203cf7c0b40fcd926aecb3137
2015-06-04 11:37:05 -07:00

575 lines
22 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 "BigBuffer.h"
#include "Logger.h"
#include "Maybe.h"
#include "Resolver.h"
#include "Resource.h"
#include "ResourceParser.h"
#include "ResourceValues.h"
#include "SdkConstants.h"
#include "Source.h"
#include "StringPool.h"
#include "Util.h"
#include "XmlFlattener.h"
#include <androidfw/ResourceTypes.h>
#include <limits>
#include <map>
#include <string>
#include <vector>
namespace aapt {
namespace xml {
constexpr uint32_t kLowPriority = 0xffffffffu;
// A vector that maps String refs to their final destination in the out buffer.
using FlatStringRefList = std::vector<std::pair<StringPool::Ref, android::ResStringPool_ref*>>;
struct XmlFlattener : public Visitor {
XmlFlattener(BigBuffer* outBuffer, StringPool* pool, FlatStringRefList* stringRefs,
const std::u16string& defaultPackage) :
mOut(outBuffer), mPool(pool), mStringRefs(stringRefs),
mDefaultPackage(defaultPackage) {
}
// No copying.
XmlFlattener(const XmlFlattener&) = delete;
XmlFlattener& operator=(const XmlFlattener&) = delete;
void writeNamespace(Namespace* node, uint16_t type) {
const size_t startIndex = mOut->size();
android::ResXMLTree_node* flatNode = mOut->nextBlock<android::ResXMLTree_node>();
android::ResXMLTree_namespaceExt* flatNs =
mOut->nextBlock<android::ResXMLTree_namespaceExt>();
mOut->align4();
flatNode->header = { type, sizeof(*flatNode), (uint32_t)(mOut->size() - startIndex) };
flatNode->lineNumber = node->lineNumber;
flatNode->comment.index = -1;
addString(node->namespacePrefix, kLowPriority, &flatNs->prefix);
addString(node->namespaceUri, kLowPriority, &flatNs->uri);
}
virtual void visit(Namespace* node) override {
// Extract the package/prefix from this namespace node.
Maybe<std::u16string> package = util::extractPackageFromNamespace(node->namespaceUri);
if (package) {
mPackageAliases.emplace_back(
node->namespacePrefix,
package.value().empty() ? mDefaultPackage : package.value());
}
writeNamespace(node, android::RES_XML_START_NAMESPACE_TYPE);
for (const auto& child : node->children) {
child->accept(this);
}
writeNamespace(node, android::RES_XML_END_NAMESPACE_TYPE);
if (package) {
mPackageAliases.pop_back();
}
}
virtual void visit(Text* node) override {
if (util::trimWhitespace(node->text).empty()) {
return;
}
const size_t startIndex = mOut->size();
android::ResXMLTree_node* flatNode = mOut->nextBlock<android::ResXMLTree_node>();
android::ResXMLTree_cdataExt* flatText = mOut->nextBlock<android::ResXMLTree_cdataExt>();
mOut->align4();
const uint16_t type = android::RES_XML_CDATA_TYPE;
flatNode->header = { type, sizeof(*flatNode), (uint32_t)(mOut->size() - startIndex) };
flatNode->lineNumber = node->lineNumber;
flatNode->comment.index = -1;
addString(node->text, kLowPriority, &flatText->data);
}
virtual void visit(Element* node) override {
const size_t startIndex = mOut->size();
android::ResXMLTree_node* flatNode = mOut->nextBlock<android::ResXMLTree_node>();
android::ResXMLTree_attrExt* flatElem = mOut->nextBlock<android::ResXMLTree_attrExt>();
const uint16_t type = android::RES_XML_START_ELEMENT_TYPE;
flatNode->header = { type, sizeof(*flatNode), 0 };
flatNode->lineNumber = node->lineNumber;
flatNode->comment.index = -1;
addString(node->namespaceUri, kLowPriority, &flatElem->ns);
addString(node->name, kLowPriority, &flatElem->name);
flatElem->attributeStart = sizeof(*flatElem);
flatElem->attributeSize = sizeof(android::ResXMLTree_attribute);
flatElem->attributeCount = node->attributes.size();
if (!writeAttributes(mOut, node, flatElem)) {
mError = true;
}
mOut->align4();
flatNode->header.size = (uint32_t)(mOut->size() - startIndex);
for (const auto& child : node->children) {
child->accept(this);
}
const size_t startEndIndex = mOut->size();
android::ResXMLTree_node* flatEndNode = mOut->nextBlock<android::ResXMLTree_node>();
android::ResXMLTree_endElementExt* flatEndElem =
mOut->nextBlock<android::ResXMLTree_endElementExt>();
mOut->align4();
const uint16_t endType = android::RES_XML_END_ELEMENT_TYPE;
flatEndNode->header = { endType, sizeof(*flatEndNode),
(uint32_t)(mOut->size() - startEndIndex) };
flatEndNode->lineNumber = node->lineNumber;
flatEndNode->comment.index = -1;
addString(node->namespaceUri, kLowPriority, &flatEndElem->ns);
addString(node->name, kLowPriority, &flatEndElem->name);
}
bool success() const {
return !mError;
}
protected:
void addString(const StringPiece16& str, uint32_t priority, android::ResStringPool_ref* dest) {
if (!str.empty()) {
mStringRefs->emplace_back(mPool->makeRef(str, StringPool::Context{ priority }), dest);
} else {
// The device doesn't think a string of size 0 is the same as null.
dest->index = -1;
}
}
void addString(const StringPool::Ref& ref, android::ResStringPool_ref* dest) {
mStringRefs->emplace_back(ref, dest);
}
Maybe<std::u16string> getPackageAlias(const std::u16string& prefix) {
const auto endIter = mPackageAliases.rend();
for (auto iter = mPackageAliases.rbegin(); iter != endIter; ++iter) {
if (iter->first == prefix) {
return iter->second;
}
}
return {};
}
const std::u16string& getDefaultPackage() const {
return mDefaultPackage;
}
/**
* Subclasses override this to deal with attributes. Attributes can be flattened as
* raw values or as resources.
*/
virtual bool writeAttributes(BigBuffer* out, Element* node,
android::ResXMLTree_attrExt* flatElem) = 0;
private:
BigBuffer* mOut;
StringPool* mPool;
FlatStringRefList* mStringRefs;
std::u16string mDefaultPackage;
bool mError = false;
std::vector<std::pair<std::u16string, std::u16string>> mPackageAliases;
};
/**
* Flattens XML, encoding the attributes as raw strings. This is used in the compile phase.
*/
struct CompileXmlFlattener : public XmlFlattener {
CompileXmlFlattener(BigBuffer* outBuffer, StringPool* pool, FlatStringRefList* stringRefs,
const std::u16string& defaultPackage) :
XmlFlattener(outBuffer, pool, stringRefs, defaultPackage) {
}
virtual bool writeAttributes(BigBuffer* out, Element* node,
android::ResXMLTree_attrExt* flatElem) override {
flatElem->attributeCount = node->attributes.size();
if (node->attributes.empty()) {
return true;
}
android::ResXMLTree_attribute* flatAttrs = out->nextBlock<android::ResXMLTree_attribute>(
node->attributes.size());
for (const Attribute& attr : node->attributes) {
addString(attr.namespaceUri, kLowPriority, &flatAttrs->ns);
addString(attr.name, kLowPriority, &flatAttrs->name);
addString(attr.value, kLowPriority, &flatAttrs->rawValue);
flatAttrs++;
}
return true;
}
};
struct AttributeToFlatten {
uint32_t resourceId = 0;
const Attribute* xmlAttr = nullptr;
const ::aapt::Attribute* resourceAttr = nullptr;
};
static bool lessAttributeId(const AttributeToFlatten& a, uint32_t id) {
return a.resourceId < id;
}
/**
* Flattens XML, encoding the attributes as resources.
*/
struct LinkedXmlFlattener : public XmlFlattener {
LinkedXmlFlattener(BigBuffer* outBuffer, StringPool* pool,
std::map<std::u16string, StringPool>* packagePools,
FlatStringRefList* stringRefs,
const std::u16string& defaultPackage,
const std::shared_ptr<IResolver>& resolver,
SourceLogger* logger,
const FlattenOptions& options) :
XmlFlattener(outBuffer, pool, stringRefs, defaultPackage), mResolver(resolver),
mLogger(logger), mPackagePools(packagePools), mOptions(options) {
}
virtual bool writeAttributes(BigBuffer* out, Element* node,
android::ResXMLTree_attrExt* flatElem) override {
bool error = false;
std::vector<AttributeToFlatten> sortedAttributes;
uint32_t nextAttributeId = 0x80000000u;
// Sort and filter attributes by their resource ID.
for (const Attribute& attr : node->attributes) {
AttributeToFlatten attrToFlatten;
attrToFlatten.xmlAttr = &attr;
Maybe<std::u16string> package = util::extractPackageFromNamespace(attr.namespaceUri);
if (package) {
// Find the Attribute object via our Resolver.
ResourceName attrName = { package.value(), ResourceType::kAttr, attr.name };
if (attrName.package.empty()) {
attrName.package = getDefaultPackage();
}
Maybe<IResolver::Entry> result = mResolver->findAttribute(attrName);
if (!result || !result.value().id.isValid() || !result.value().attr) {
error = true;
mLogger->error(node->lineNumber)
<< "unresolved attribute '" << attrName << "'."
<< std::endl;
} else {
attrToFlatten.resourceId = result.value().id.id;
attrToFlatten.resourceAttr = result.value().attr;
size_t sdk = findAttributeSdkLevel(attrToFlatten.resourceId);
if (mOptions.maxSdkAttribute && sdk > mOptions.maxSdkAttribute.value()) {
// We need to filter this attribute out.
mSmallestFilteredSdk = std::min(mSmallestFilteredSdk, sdk);
continue;
}
}
}
if (attrToFlatten.resourceId == 0) {
// Attributes that have no resource ID (because they don't belong to a
// package) should appear after those that do have resource IDs. Assign
// them some integer value that will appear after.
attrToFlatten.resourceId = nextAttributeId++;
}
// Insert the attribute into the sorted vector.
auto iter = std::lower_bound(sortedAttributes.begin(), sortedAttributes.end(),
attrToFlatten.resourceId, lessAttributeId);
sortedAttributes.insert(iter, std::move(attrToFlatten));
}
flatElem->attributeCount = sortedAttributes.size();
if (sortedAttributes.empty()) {
return true;
}
android::ResXMLTree_attribute* flatAttr = out->nextBlock<android::ResXMLTree_attribute>(
sortedAttributes.size());
// Now that we have sorted the attributes into their final encoded order, it's time
// to actually write them out.
uint16_t attributeIndex = 1;
for (const AttributeToFlatten& attrToFlatten : sortedAttributes) {
Maybe<std::u16string> package = util::extractPackageFromNamespace(
attrToFlatten.xmlAttr->namespaceUri);
// Assign the indices for specific attributes.
if (package && package.value() == u"android" && attrToFlatten.xmlAttr->name == u"id") {
flatElem->idIndex = attributeIndex;
} else if (attrToFlatten.xmlAttr->namespaceUri.empty()) {
if (attrToFlatten.xmlAttr->name == u"class") {
flatElem->classIndex = attributeIndex;
} else if (attrToFlatten.xmlAttr->name == u"style") {
flatElem->styleIndex = attributeIndex;
}
}
attributeIndex++;
// Add the namespaceUri and name to the list of StringRefs to encode.
addString(attrToFlatten.xmlAttr->namespaceUri, kLowPriority, &flatAttr->ns);
flatAttr->rawValue.index = -1;
if (!attrToFlatten.resourceAttr) {
addString(attrToFlatten.xmlAttr->name, kLowPriority, &flatAttr->name);
} else {
// We've already extracted the package successfully before.
assert(package);
// Attribute names are stored without packages, but we use
// their StringPool index to lookup their resource IDs.
// This will cause collisions, so we can't dedupe
// attribute names from different packages. We use separate
// pools that we later combine.
//
// Lookup the StringPool for this package and make the reference there.
StringPool::Ref nameRef = (*mPackagePools)[package.value()].makeRef(
attrToFlatten.xmlAttr->name,
StringPool::Context{ attrToFlatten.resourceId });
// Add it to the list of strings to flatten.
addString(nameRef, &flatAttr->name);
if (mOptions.keepRawValues) {
// Keep raw values (this is for static libraries).
// TODO(with a smarter inflater for binary XML, we can do without this).
addString(attrToFlatten.xmlAttr->value, kLowPriority, &flatAttr->rawValue);
}
}
error |= !flattenItem(node, attrToFlatten.xmlAttr->value, attrToFlatten.resourceAttr,
flatAttr);
flatAttr->typedValue.size = sizeof(flatAttr->typedValue);
flatAttr++;
}
return !error;
}
Maybe<size_t> getSmallestFilteredSdk() const {
if (mSmallestFilteredSdk == std::numeric_limits<size_t>::max()) {
return {};
}
return mSmallestFilteredSdk;
}
private:
bool flattenItem(const Node* el, const std::u16string& value, const ::aapt::Attribute* attr,
android::ResXMLTree_attribute* flatAttr) {
std::unique_ptr<Item> item;
if (!attr) {
bool create = false;
item = ResourceParser::tryParseReference(value, &create);
if (!item) {
flatAttr->typedValue.dataType = android::Res_value::TYPE_STRING;
addString(value, kLowPriority, &flatAttr->rawValue);
addString(value, kLowPriority, reinterpret_cast<android::ResStringPool_ref*>(
&flatAttr->typedValue.data));
return true;
}
} else {
item = ResourceParser::parseItemForAttribute(value, *attr);
if (!item) {
if (!(attr->typeMask & android::ResTable_map::TYPE_STRING)) {
mLogger->error(el->lineNumber)
<< "'"
<< value
<< "' is not compatible with attribute '"
<< *attr
<< "'."
<< std::endl;
return false;
}
flatAttr->typedValue.dataType = android::Res_value::TYPE_STRING;
addString(value, kLowPriority, &flatAttr->rawValue);
addString(value, kLowPriority, reinterpret_cast<android::ResStringPool_ref*>(
&flatAttr->typedValue.data));
return true;
}
}
assert(item);
bool error = false;
// If this is a reference, resolve the name into an ID.
visitFunc<Reference>(*item, [&](Reference& reference) {
// First see if we can convert the package name from a prefix to a real
// package name.
ResourceName realName = reference.name;
if (!realName.package.empty()) {
Maybe<std::u16string> package = getPackageAlias(realName.package);
if (package) {
realName.package = package.value();
}
} else {
realName.package = getDefaultPackage();
}
Maybe<ResourceId> result = mResolver->findId(realName);
if (!result || !result.value().isValid()) {
std::ostream& out = mLogger->error(el->lineNumber)
<< "unresolved reference '"
<< reference.name
<< "'";
if (realName != reference.name) {
out << " (aka '" << realName << "')";
}
out << "'." << std::endl;
error = true;
} else {
reference.id = result.value();
}
});
if (error) {
return false;
}
item->flatten(flatAttr->typedValue);
return true;
}
std::shared_ptr<IResolver> mResolver;
SourceLogger* mLogger;
std::map<std::u16string, StringPool>* mPackagePools;
FlattenOptions mOptions;
size_t mSmallestFilteredSdk = std::numeric_limits<size_t>::max();
};
/**
* The binary XML file expects the StringPool to appear first, but we haven't collected the
* strings yet. We write to a temporary BigBuffer while parsing the input, adding strings
* we encounter to the StringPool. At the end, we write the StringPool to the given BigBuffer and
* then move the data from the temporary BigBuffer into the given one. This incurs no
* copies as the given BigBuffer simply takes ownership of the data.
*/
static void flattenXml(StringPool* pool, FlatStringRefList* stringRefs, BigBuffer* outBuffer,
BigBuffer&& xmlTreeBuffer) {
// Sort the string pool so that attribute resource IDs show up first.
pool->sort([](const StringPool::Entry& a, const StringPool::Entry& b) -> bool {
return a.context.priority < b.context.priority;
});
// Now we flatten the string pool references into the correct places.
for (const auto& refEntry : *stringRefs) {
refEntry.second->index = refEntry.first.getIndex();
}
// Write the XML header.
const size_t beforeXmlTreeIndex = outBuffer->size();
android::ResXMLTree_header* header = outBuffer->nextBlock<android::ResXMLTree_header>();
header->header.type = android::RES_XML_TYPE;
header->header.headerSize = sizeof(*header);
// Flatten the StringPool.
StringPool::flattenUtf16(outBuffer, *pool);
// Write the array of resource IDs, indexed by StringPool order.
const size_t beforeResIdMapIndex = outBuffer->size();
android::ResChunk_header* resIdMapChunk = outBuffer->nextBlock<android::ResChunk_header>();
resIdMapChunk->type = android::RES_XML_RESOURCE_MAP_TYPE;
resIdMapChunk->headerSize = sizeof(*resIdMapChunk);
for (const auto& str : *pool) {
ResourceId id { str->context.priority };
if (id.id == kLowPriority || !id.isValid()) {
// When we see the first non-resource ID,
// we're done.
break;
}
*outBuffer->nextBlock<uint32_t>() = id.id;
}
resIdMapChunk->size = outBuffer->size() - beforeResIdMapIndex;
// Move the temporary BigBuffer into outBuffer.
outBuffer->appendBuffer(std::move(xmlTreeBuffer));
header->header.size = outBuffer->size() - beforeXmlTreeIndex;
}
bool flatten(Node* root, const std::u16string& defaultPackage, BigBuffer* outBuffer) {
StringPool pool;
// This will hold the StringRefs and the location in which to write the index.
// Once we sort the StringPool, we can assign the updated indices
// to the correct data locations.
FlatStringRefList stringRefs;
// Since we don't know the size of the final StringPool, we write to this
// temporary BigBuffer, which we will append to outBuffer later.
BigBuffer out(1024);
CompileXmlFlattener flattener(&out, &pool, &stringRefs, defaultPackage);
root->accept(&flattener);
if (!flattener.success()) {
return false;
}
flattenXml(&pool, &stringRefs, outBuffer, std::move(out));
return true;
};
Maybe<size_t> flattenAndLink(const Source& source, Node* root,
const std::u16string& defaultPackage,
const std::shared_ptr<IResolver>& resolver,
const FlattenOptions& options, BigBuffer* outBuffer) {
SourceLogger logger(source);
StringPool pool;
// Attribute names are stored without packages, but we use
// their StringPool index to lookup their resource IDs.
// This will cause collisions, so we can't dedupe
// attribute names from different packages. We use separate
// pools that we later combine.
std::map<std::u16string, StringPool> packagePools;
FlatStringRefList stringRefs;
// Since we don't know the size of the final StringPool, we write to this
// temporary BigBuffer, which we will append to outBuffer later.
BigBuffer out(1024);
LinkedXmlFlattener flattener(&out, &pool, &packagePools, &stringRefs, defaultPackage, resolver,
&logger, options);
root->accept(&flattener);
if (!flattener.success()) {
return {};
}
// Merge the package pools into the main pool.
for (auto& packagePoolEntry : packagePools) {
pool.merge(std::move(packagePoolEntry.second));
}
flattenXml(&pool, &stringRefs, outBuffer, std::move(out));
if (flattener.getSmallestFilteredSdk()) {
return flattener.getSmallestFilteredSdk();
}
return 0;
}
} // namespace xml
} // namespace aapt