/*
* 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