blob: f78e38dd8638161ab466e435718ff831fa0341ce [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 "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 {
constexpr const char16_t* kSchemaAndroid = u"http://schemas.android.com/apk/res/android";
struct AttributeValueFlattener : ValueVisitor {
AttributeValueFlattener(
std::shared_ptr<IResolver> resolver, SourceLogger* logger,
android::Res_value* outValue, std::shared_ptr<XmlPullParser> parser, bool* outError,
StringPool::Ref rawValue, std::u16string* defaultPackage,
std::vector<std::pair<StringPool::Ref, android::ResStringPool_ref*>>* outStringRefs) :
mResolver(resolver), mLogger(logger), mOutValue(outValue), mParser(parser),
mError(outError), mRawValue(rawValue), mDefaultPackage(defaultPackage),
mStringRefs(outStringRefs) {
}
void visit(Reference& reference, ValueVisitorArgs&) override {
// First see if we can convert the package name from a prefix to a real
// package name.
ResourceName aliasedName = reference.name;
if (!reference.name.package.empty()) {
// Only if we specified a package do we look for its alias.
mParser->applyPackageAlias(&reference.name.package, *mDefaultPackage);
} else {
reference.name.package = *mDefaultPackage;
}
Maybe<ResourceId> result = mResolver->findId(reference.name);
if (!result || !result.value().isValid()) {
std::ostream& out = mLogger->error(mParser->getLineNumber())
<< "unresolved reference '"
<< aliasedName
<< "'";
if (aliasedName != reference.name) {
out << " (aka '" << reference.name << "')";
}
out << "'." << std::endl;
*mError = true;
} else {
reference.id = result.value();
reference.flatten(*mOutValue);
}
}
void visit(String& string, ValueVisitorArgs&) override {
mOutValue->dataType = android::Res_value::TYPE_STRING;
mStringRefs->emplace_back(
mRawValue,
reinterpret_cast<android::ResStringPool_ref*>(mOutValue->data));
}
void visitItem(Item& item, ValueVisitorArgs&) override {
item.flatten(*mOutValue);
}
private:
std::shared_ptr<IResolver> mResolver;
SourceLogger* mLogger;
android::Res_value* mOutValue;
std::shared_ptr<XmlPullParser> mParser;
bool* mError;
StringPool::Ref mRawValue;
std::u16string* mDefaultPackage;
std::vector<std::pair<StringPool::Ref, android::ResStringPool_ref*>>* mStringRefs;
};
struct XmlAttribute {
uint32_t resourceId;
const XmlPullParser::Attribute* xmlAttr;
const Attribute* attr;
StringPool::Ref nameRef;
};
static bool lessAttributeId(const XmlAttribute& a, uint32_t id) {
return a.resourceId < id;
}
XmlFlattener::XmlFlattener(const std::shared_ptr<ResourceTable>& table,
const std::shared_ptr<IResolver>& resolver) :
mTable(table), mResolver(resolver) {
}
/**
* Reads events from the parser and writes to a BigBuffer. 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.
*/
Maybe<size_t> XmlFlattener::flatten(const Source& source,
const std::shared_ptr<XmlPullParser>& parser,
BigBuffer* outBuffer, Options options) {
SourceLogger logger(source);
StringPool pool;
bool error = false;
size_t smallestStrippedAttributeSdk = std::numeric_limits<size_t>::max();
// 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;
// Attribute resource IDs are stored in the same order
// as the attribute names appear in the StringPool.
// Since the StringPool contains more than just attribute
// names, to maintain a tight packing of resource IDs,
// we must ensure that attribute names appear first
// in our StringPool. For this, we assign a low priority
// (0xffffffff) to non-attribute strings. Attribute
// names will be stored along with a priority equal
// to their resource ID so that they are ordered.
StringPool::Context lowPriority { 0xffffffffu };
// Once we sort the StringPool, we can assign the updated indices
// to the correct data locations.
std::vector<std::pair<StringPool::Ref, android::ResStringPool_ref*>> 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);
while (XmlPullParser::isGoodEvent(parser->next())) {
XmlPullParser::Event event = parser->getEvent();
switch (event) {
case XmlPullParser::Event::kStartNamespace:
case XmlPullParser::Event::kEndNamespace: {
const size_t startIndex = out.size();
android::ResXMLTree_node* node = out.nextBlock<android::ResXMLTree_node>();
if (event == XmlPullParser::Event::kStartNamespace) {
node->header.type = android::RES_XML_START_NAMESPACE_TYPE;
} else {
node->header.type = android::RES_XML_END_NAMESPACE_TYPE;
}
node->header.headerSize = sizeof(*node);
node->lineNumber = parser->getLineNumber();
node->comment.index = -1;
android::ResXMLTree_namespaceExt* ns =
out.nextBlock<android::ResXMLTree_namespaceExt>();
stringRefs.emplace_back(
pool.makeRef(parser->getNamespacePrefix(), lowPriority), &ns->prefix);
stringRefs.emplace_back(
pool.makeRef(parser->getNamespaceUri(), lowPriority), &ns->uri);
out.align4();
node->header.size = out.size() - startIndex;
break;
}
case XmlPullParser::Event::kStartElement: {
const size_t startIndex = out.size();
android::ResXMLTree_node* node = out.nextBlock<android::ResXMLTree_node>();
node->header.type = android::RES_XML_START_ELEMENT_TYPE;
node->header.headerSize = sizeof(*node);
node->lineNumber = parser->getLineNumber();
node->comment.index = -1;
android::ResXMLTree_attrExt* elem = out.nextBlock<android::ResXMLTree_attrExt>();
if (!parser->getElementNamespace().empty()) {
stringRefs.emplace_back(
pool.makeRef(parser->getElementNamespace(), lowPriority), &elem->ns);
} else {
// The device doesn't think a string of size 0 is the same as null.
elem->ns.index = -1;
}
stringRefs.emplace_back(
pool.makeRef(parser->getElementName(), lowPriority), &elem->name);
elem->attributeStart = sizeof(*elem);
elem->attributeSize = sizeof(android::ResXMLTree_attribute);
// The resource system expects attributes to be sorted by resource ID.
std::vector<XmlAttribute> sortedAttributes;
uint32_t nextAttributeId = 0;
const auto endAttrIter = parser->endAttributes();
for (auto attrIter = parser->beginAttributes();
attrIter != endAttrIter;
++attrIter) {
uint32_t id;
StringPool::Ref nameRef;
const Attribute* attr = nullptr;
if (options.maxSdkAttribute && attrIter->namespaceUri == kSchemaAndroid) {
size_t sdkVersion = findAttributeSdkLevel(attrIter->name);
if (sdkVersion > options.maxSdkAttribute.value()) {
// We will silently omit this attribute
smallestStrippedAttributeSdk =
std::min(smallestStrippedAttributeSdk, sdkVersion);
continue;
}
}
ResourceNameRef genIdName;
bool create = false;
bool privateRef = false;
if (mTable && ResourceParser::tryParseReference(attrIter->value, &genIdName,
&create, &privateRef) && create) {
mTable->addResource(genIdName, {}, source.line(parser->getLineNumber()),
util::make_unique<Id>());
}
Maybe<std::u16string> package = util::extractPackageFromNamespace(
attrIter->namespaceUri);
if (!package || !mResolver) {
// 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.
id = 0x80000000u | nextAttributeId++;
nameRef = pool.makeRef(attrIter->name, StringPool::Context{ id });
} else {
// Find the Attribute object via our Resolver.
ResourceName attrName = {
package.value(), ResourceType::kAttr, attrIter->name };
if (attrName.package.empty()) {
attrName.package = options.defaultPackage;
}
Maybe<IResolver::Entry> result = mResolver->findAttribute(attrName);
if (!result || !result.value().id.isValid()) {
logger.error(parser->getLineNumber())
<< "unresolved attribute '"
<< attrName
<< "'."
<< std::endl;
error = true;
continue;
}
if (!result.value().attr) {
logger.error(parser->getLineNumber())
<< "not a valid attribute '"
<< attrName
<< "'."
<< std::endl;
error = true;
continue;
}
id = result.value().id.id;
attr = result.value().attr;
// Put the attribute name into a package specific pool, since we don't
// want to collapse names from different packages.
nameRef = packagePools[package.value()].makeRef(
attrIter->name, StringPool::Context{ id });
}
// Insert the attribute into the sorted vector.
auto iter = std::lower_bound(sortedAttributes.begin(), sortedAttributes.end(),
id, lessAttributeId);
sortedAttributes.insert(iter, XmlAttribute{ id, &*attrIter, attr, nameRef });
}
if (error) {
break;
}
// Now that we have filtered out some attributes, get the final count.
elem->attributeCount = sortedAttributes.size();
// Flatten the sorted attributes.
uint16_t attributeIndex = 1;
for (auto entry : sortedAttributes) {
android::ResXMLTree_attribute* attr =
out.nextBlock<android::ResXMLTree_attribute>();
if (!entry.xmlAttr->namespaceUri.empty()) {
stringRefs.emplace_back(
pool.makeRef(entry.xmlAttr->namespaceUri, lowPriority), &attr->ns);
} else {
attr->ns.index = -1;
}
StringPool::Ref rawValueRef = pool.makeRef(entry.xmlAttr->value, lowPriority);
stringRefs.emplace_back(entry.nameRef, &attr->name);
if (options.keepRawValues) {
stringRefs.emplace_back(rawValueRef, &attr->rawValue);
} else {
attr->rawValue.index = -1;
}
// Assign the indices for specific attributes.
if (entry.xmlAttr->namespaceUri == kSchemaAndroid &&
entry.xmlAttr->name == u"id") {
elem->idIndex = attributeIndex;
} else if (entry.xmlAttr->namespaceUri.empty()) {
if (entry.xmlAttr->name == u"class") {
elem->classIndex = attributeIndex;
} else if (entry.xmlAttr->name == u"style") {
elem->styleIndex = attributeIndex;
}
}
attributeIndex++;
std::unique_ptr<Item> value;
if (entry.attr) {
value = ResourceParser::parseItemForAttribute(entry.xmlAttr->value,
*entry.attr);
} else {
bool create = false;
value = ResourceParser::tryParseReference(entry.xmlAttr->value, &create);
}
if (mResolver && value) {
AttributeValueFlattener flattener(
mResolver,
&logger,
&attr->typedValue,
parser,
&error,
rawValueRef,
&options.defaultPackage,
&stringRefs);
value->accept(flattener, {});
} else if (!value && entry.attr &&
!(entry.attr->typeMask & android::ResTable_map::TYPE_STRING)) {
logger.error(parser->getLineNumber())
<< "'"
<< *rawValueRef
<< "' is not compatible with attribute "
<< *entry.attr
<< "."
<< std::endl;
error = true;
} else {
attr->typedValue.dataType = android::Res_value::TYPE_STRING;
if (!options.keepRawValues) {
// Don't set the string twice.
stringRefs.emplace_back(rawValueRef, &attr->rawValue);
}
stringRefs.emplace_back(rawValueRef,
reinterpret_cast<android::ResStringPool_ref*>(
&attr->typedValue.data));
}
attr->typedValue.size = sizeof(attr->typedValue);
}
out.align4();
node->header.size = out.size() - startIndex;
break;
}
case XmlPullParser::Event::kEndElement: {
const size_t startIndex = out.size();
android::ResXMLTree_node* node = out.nextBlock<android::ResXMLTree_node>();
node->header.type = android::RES_XML_END_ELEMENT_TYPE;
node->header.headerSize = sizeof(*node);
node->lineNumber = parser->getLineNumber();
node->comment.index = -1;
android::ResXMLTree_endElementExt* elem =
out.nextBlock<android::ResXMLTree_endElementExt>();
stringRefs.emplace_back(
pool.makeRef(parser->getElementNamespace(), lowPriority), &elem->ns);
stringRefs.emplace_back(
pool.makeRef(parser->getElementName(), lowPriority), &elem->name);
out.align4();
node->header.size = out.size() - startIndex;
break;
}
case XmlPullParser::Event::kText: {
StringPiece16 text = util::trimWhitespace(parser->getText());
if (text.empty()) {
break;
}
const size_t startIndex = out.size();
android::ResXMLTree_node* node = out.nextBlock<android::ResXMLTree_node>();
node->header.type = android::RES_XML_CDATA_TYPE;
node->header.headerSize = sizeof(*node);
node->lineNumber = parser->getLineNumber();
node->comment.index = -1;
android::ResXMLTree_cdataExt* elem = out.nextBlock<android::ResXMLTree_cdataExt>();
stringRefs.emplace_back(pool.makeRef(text, lowPriority), &elem->data);
out.align4();
node->header.size = out.size() - startIndex;
break;
}
default:
break;
}
}
out.align4();
if (error) {
return {};
}
if (parser->getEvent() == XmlPullParser::Event::kBadDocument) {
logger.error(parser->getLineNumber())
<< parser->getLastError()
<< std::endl;
return {};
}
// Merge the package pools into the main pool.
for (auto& packagePoolEntry : packagePools) {
pool.merge(std::move(packagePoolEntry.second));
}
// Sort 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.isValid()) {
// When we see the first non-resource ID,
// we're done.
break;
}
uint32_t* flatId = outBuffer->nextBlock<uint32_t>();
*flatId = id.id;
}
resIdMapChunk->size = outBuffer->size() - beforeResIdMapIndex;
// Move the temporary BigBuffer into outBuffer.
outBuffer->appendBuffer(std::move(out));
header->header.size = outBuffer->size() - beforeXmlTreeIndex;
if (smallestStrippedAttributeSdk == std::numeric_limits<size_t>::max()) {
// Nothing was stripped
return 0u;
}
return smallestStrippedAttributeSdk;
}
} // namespace aapt