blob: 650e624323cdf7be6ae59ad8de81448b9c447028 [file] [log] [blame]
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include "BigBuffer.h"
18#include "Logger.h"
19#include "Maybe.h"
20#include "Resolver.h"
21#include "Resource.h"
22#include "ResourceParser.h"
23#include "ResourceValues.h"
24#include "SdkConstants.h"
25#include "Source.h"
26#include "StringPool.h"
27#include "Util.h"
28#include "XmlFlattener.h"
29
30#include <androidfw/ResourceTypes.h>
31#include <limits>
32#include <map>
33#include <string>
34#include <vector>
35
36namespace aapt {
37
Adam Lesinski769de982015-04-10 19:43:55 -070038constexpr const char16_t* kSchemaAndroid = u"http://schemas.android.com/apk/res/android";
Adam Lesinski769de982015-04-10 19:43:55 -070039
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080040struct AttributeValueFlattener : ValueVisitor {
Adam Lesinski24aad162015-04-24 19:19:30 -070041 AttributeValueFlattener(
42 std::shared_ptr<IResolver> resolver, SourceLogger* logger,
43 android::Res_value* outValue, std::shared_ptr<XmlPullParser> parser, bool* outError,
44 StringPool::Ref rawValue, std::u16string* defaultPackage,
45 std::vector<std::pair<StringPool::Ref, android::ResStringPool_ref*>>* outStringRefs) :
46 mResolver(resolver), mLogger(logger), mOutValue(outValue), mParser(parser),
47 mError(outError), mRawValue(rawValue), mDefaultPackage(defaultPackage),
48 mStringRefs(outStringRefs) {
49 }
50
51 void visit(Reference& reference, ValueVisitorArgs&) override {
52 // First see if we can convert the package name from a prefix to a real
53 // package name.
54 ResourceName aliasedName = reference.name;
55
56 if (!reference.name.package.empty()) {
57 // Only if we specified a package do we look for its alias.
58 mParser->applyPackageAlias(&reference.name.package, *mDefaultPackage);
59 } else {
60 reference.name.package = *mDefaultPackage;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080061 }
62
Adam Lesinski24aad162015-04-24 19:19:30 -070063 Maybe<ResourceId> result = mResolver->findId(reference.name);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080064 if (!result || !result.value().isValid()) {
Adam Lesinski24aad162015-04-24 19:19:30 -070065 std::ostream& out = mLogger->error(mParser->getLineNumber())
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080066 << "unresolved reference '"
Adam Lesinski24aad162015-04-24 19:19:30 -070067 << aliasedName
68 << "'";
69 if (aliasedName != reference.name) {
70 out << " (aka '" << reference.name << "')";
71 }
72 out << "'." << std::endl;
73 *mError = true;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080074 } else {
75 reference.id = result.value();
Adam Lesinski24aad162015-04-24 19:19:30 -070076 reference.flatten(*mOutValue);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080077 }
78 }
79
Adam Lesinski24aad162015-04-24 19:19:30 -070080 void visit(String& string, ValueVisitorArgs&) override {
81 mOutValue->dataType = android::Res_value::TYPE_STRING;
82 mStringRefs->emplace_back(
83 mRawValue,
84 reinterpret_cast<android::ResStringPool_ref*>(mOutValue->data));
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080085 }
86
Adam Lesinski24aad162015-04-24 19:19:30 -070087 void visitItem(Item& item, ValueVisitorArgs&) override {
88 item.flatten(*mOutValue);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080089 }
Adam Lesinski24aad162015-04-24 19:19:30 -070090
91private:
92 std::shared_ptr<IResolver> mResolver;
93 SourceLogger* mLogger;
94 android::Res_value* mOutValue;
95 std::shared_ptr<XmlPullParser> mParser;
96 bool* mError;
97 StringPool::Ref mRawValue;
98 std::u16string* mDefaultPackage;
99 std::vector<std::pair<StringPool::Ref, android::ResStringPool_ref*>>* mStringRefs;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800100};
101
102struct XmlAttribute {
103 uint32_t resourceId;
104 const XmlPullParser::Attribute* xmlAttr;
105 const Attribute* attr;
106 StringPool::Ref nameRef;
107};
108
109static bool lessAttributeId(const XmlAttribute& a, uint32_t id) {
110 return a.resourceId < id;
111}
112
Adam Lesinski769de982015-04-10 19:43:55 -0700113XmlFlattener::XmlFlattener(const std::shared_ptr<ResourceTable>& table,
Adam Lesinski24aad162015-04-24 19:19:30 -0700114 const std::shared_ptr<IResolver>& resolver) :
Adam Lesinski769de982015-04-10 19:43:55 -0700115 mTable(table), mResolver(resolver) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800116}
117
118/**
119 * Reads events from the parser and writes to a BigBuffer. The binary XML file
120 * expects the StringPool to appear first, but we haven't collected the strings yet. We
121 * write to a temporary BigBuffer while parsing the input, adding strings we encounter
122 * to the StringPool. At the end, we write the StringPool to the given BigBuffer and
123 * then move the data from the temporary BigBuffer into the given one. This incurs no
124 * copies as the given BigBuffer simply takes ownership of the data.
125 */
126Maybe<size_t> XmlFlattener::flatten(const Source& source,
127 const std::shared_ptr<XmlPullParser>& parser,
128 BigBuffer* outBuffer, Options options) {
129 SourceLogger logger(source);
130 StringPool pool;
131 bool error = false;
132
133 size_t smallestStrippedAttributeSdk = std::numeric_limits<size_t>::max();
134
135 // Attribute names are stored without packages, but we use
136 // their StringPool index to lookup their resource IDs.
137 // This will cause collisions, so we can't dedupe
138 // attribute names from different packages. We use separate
139 // pools that we later combine.
140 std::map<std::u16string, StringPool> packagePools;
141
142 // Attribute resource IDs are stored in the same order
143 // as the attribute names appear in the StringPool.
144 // Since the StringPool contains more than just attribute
145 // names, to maintain a tight packing of resource IDs,
146 // we must ensure that attribute names appear first
147 // in our StringPool. For this, we assign a low priority
148 // (0xffffffff) to non-attribute strings. Attribute
149 // names will be stored along with a priority equal
150 // to their resource ID so that they are ordered.
151 StringPool::Context lowPriority { 0xffffffffu };
152
153 // Once we sort the StringPool, we can assign the updated indices
154 // to the correct data locations.
155 std::vector<std::pair<StringPool::Ref, android::ResStringPool_ref*>> stringRefs;
156
157 // Since we don't know the size of the final StringPool, we write to this
158 // temporary BigBuffer, which we will append to outBuffer later.
159 BigBuffer out(1024);
160 while (XmlPullParser::isGoodEvent(parser->next())) {
161 XmlPullParser::Event event = parser->getEvent();
162 switch (event) {
163 case XmlPullParser::Event::kStartNamespace:
164 case XmlPullParser::Event::kEndNamespace: {
165 const size_t startIndex = out.size();
166 android::ResXMLTree_node* node = out.nextBlock<android::ResXMLTree_node>();
167 if (event == XmlPullParser::Event::kStartNamespace) {
168 node->header.type = android::RES_XML_START_NAMESPACE_TYPE;
169 } else {
170 node->header.type = android::RES_XML_END_NAMESPACE_TYPE;
171 }
172
173 node->header.headerSize = sizeof(*node);
174 node->lineNumber = parser->getLineNumber();
175 node->comment.index = -1;
176
177 android::ResXMLTree_namespaceExt* ns =
178 out.nextBlock<android::ResXMLTree_namespaceExt>();
179 stringRefs.emplace_back(
180 pool.makeRef(parser->getNamespacePrefix(), lowPriority), &ns->prefix);
181 stringRefs.emplace_back(
182 pool.makeRef(parser->getNamespaceUri(), lowPriority), &ns->uri);
183
184 out.align4();
185 node->header.size = out.size() - startIndex;
186 break;
187 }
188
189 case XmlPullParser::Event::kStartElement: {
190 const size_t startIndex = out.size();
191 android::ResXMLTree_node* node = out.nextBlock<android::ResXMLTree_node>();
192 node->header.type = android::RES_XML_START_ELEMENT_TYPE;
193 node->header.headerSize = sizeof(*node);
194 node->lineNumber = parser->getLineNumber();
195 node->comment.index = -1;
196
197 android::ResXMLTree_attrExt* elem = out.nextBlock<android::ResXMLTree_attrExt>();
Adam Lesinski24aad162015-04-24 19:19:30 -0700198 if (!parser->getElementNamespace().empty()) {
199 stringRefs.emplace_back(
200 pool.makeRef(parser->getElementNamespace(), lowPriority), &elem->ns);
201 } else {
202 // The device doesn't think a string of size 0 is the same as null.
203 elem->ns.index = -1;
204 }
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800205 stringRefs.emplace_back(
206 pool.makeRef(parser->getElementName(), lowPriority), &elem->name);
207 elem->attributeStart = sizeof(*elem);
208 elem->attributeSize = sizeof(android::ResXMLTree_attribute);
209
210 // The resource system expects attributes to be sorted by resource ID.
211 std::vector<XmlAttribute> sortedAttributes;
212 uint32_t nextAttributeId = 0;
213 const auto endAttrIter = parser->endAttributes();
214 for (auto attrIter = parser->beginAttributes();
Adam Lesinski769de982015-04-10 19:43:55 -0700215 attrIter != endAttrIter;
216 ++attrIter) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800217 uint32_t id;
218 StringPool::Ref nameRef;
219 const Attribute* attr = nullptr;
Adam Lesinski769de982015-04-10 19:43:55 -0700220
221 if (options.maxSdkAttribute && attrIter->namespaceUri == kSchemaAndroid) {
222 size_t sdkVersion = findAttributeSdkLevel(attrIter->name);
223 if (sdkVersion > options.maxSdkAttribute.value()) {
224 // We will silently omit this attribute
225 smallestStrippedAttributeSdk =
226 std::min(smallestStrippedAttributeSdk, sdkVersion);
227 continue;
228 }
229 }
230
231 ResourceNameRef genIdName;
232 bool create = false;
233 bool privateRef = false;
234 if (mTable && ResourceParser::tryParseReference(attrIter->value, &genIdName,
235 &create, &privateRef) && create) {
236 mTable->addResource(genIdName, {}, source.line(parser->getLineNumber()),
237 util::make_unique<Id>());
238 }
239
240
Adam Lesinski24aad162015-04-24 19:19:30 -0700241 Maybe<std::u16string> package = util::extractPackageFromNamespace(
242 attrIter->namespaceUri);
243 if (!package || !mResolver) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800244 // Attributes that have no resource ID (because they don't belong to a
245 // package) should appear after those that do have resource IDs. Assign
Adam Lesinski24aad162015-04-24 19:19:30 -0700246 // them some integer value that will appear after.
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800247 id = 0x80000000u | nextAttributeId++;
248 nameRef = pool.makeRef(attrIter->name, StringPool::Context{ id });
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800249
Adam Lesinski769de982015-04-10 19:43:55 -0700250 } else {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800251 // Find the Attribute object via our Resolver.
252 ResourceName attrName = {
Adam Lesinski24aad162015-04-24 19:19:30 -0700253 package.value(), ResourceType::kAttr, attrIter->name };
254
255 if (attrName.package.empty()) {
256 attrName.package = options.defaultPackage;
257 }
258
259 Maybe<IResolver::Entry> result = mResolver->findAttribute(attrName);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800260 if (!result || !result.value().id.isValid()) {
261 logger.error(parser->getLineNumber())
262 << "unresolved attribute '"
263 << attrName
264 << "'."
265 << std::endl;
266 error = true;
267 continue;
268 }
269
270 if (!result.value().attr) {
271 logger.error(parser->getLineNumber())
272 << "not a valid attribute '"
273 << attrName
274 << "'."
275 << std::endl;
276 error = true;
277 continue;
278 }
279
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800280 id = result.value().id.id;
281 attr = result.value().attr;
282
283 // Put the attribute name into a package specific pool, since we don't
284 // want to collapse names from different packages.
Adam Lesinski24aad162015-04-24 19:19:30 -0700285 nameRef = packagePools[package.value()].makeRef(
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800286 attrIter->name, StringPool::Context{ id });
287 }
288
289 // Insert the attribute into the sorted vector.
290 auto iter = std::lower_bound(sortedAttributes.begin(), sortedAttributes.end(),
291 id, lessAttributeId);
292 sortedAttributes.insert(iter, XmlAttribute{ id, &*attrIter, attr, nameRef });
293 }
294
295 if (error) {
296 break;
297 }
298
299 // Now that we have filtered out some attributes, get the final count.
300 elem->attributeCount = sortedAttributes.size();
301
302 // Flatten the sorted attributes.
303 for (auto entry : sortedAttributes) {
304 android::ResXMLTree_attribute* attr =
305 out.nextBlock<android::ResXMLTree_attribute>();
Adam Lesinski24aad162015-04-24 19:19:30 -0700306 if (!entry.xmlAttr->namespaceUri.empty()) {
307 stringRefs.emplace_back(
308 pool.makeRef(entry.xmlAttr->namespaceUri, lowPriority), &attr->ns);
309 } else {
310 attr->ns.index = -1;
311 }
312
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800313 stringRefs.emplace_back(entry.nameRef, &attr->name);
Adam Lesinski24aad162015-04-24 19:19:30 -0700314 attr->rawValue.index = -1;
315
316 StringPool::Ref rawValueRef = pool.makeRef(entry.xmlAttr->value, lowPriority);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800317
318 if (entry.attr) {
319 std::unique_ptr<Item> value = ResourceParser::parseItemForAttribute(
Adam Lesinski24aad162015-04-24 19:19:30 -0700320 entry.xmlAttr->value, *entry.attr);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800321 if (value) {
Adam Lesinski24aad162015-04-24 19:19:30 -0700322 AttributeValueFlattener flattener(
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800323 mResolver,
Adam Lesinski24aad162015-04-24 19:19:30 -0700324 &logger,
325 &attr->typedValue,
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800326 parser,
Adam Lesinski24aad162015-04-24 19:19:30 -0700327 &error,
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800328 rawValueRef,
Adam Lesinski24aad162015-04-24 19:19:30 -0700329 &options.defaultPackage,
330 &stringRefs);
331 value->accept(flattener, {});
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800332 } else if (!(entry.attr->typeMask & android::ResTable_map::TYPE_STRING)) {
333 logger.error(parser->getLineNumber())
334 << "'"
335 << *rawValueRef
336 << "' is not compatible with attribute "
337 << *entry.attr
338 << "."
339 << std::endl;
340 error = true;
341 } else {
342 attr->typedValue.dataType = android::Res_value::TYPE_STRING;
Adam Lesinski24aad162015-04-24 19:19:30 -0700343 stringRefs.emplace_back(rawValueRef, &attr->rawValue);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800344 stringRefs.emplace_back(rawValueRef,
345 reinterpret_cast<android::ResStringPool_ref*>(
346 &attr->typedValue.data));
347 }
348 } else {
349 attr->typedValue.dataType = android::Res_value::TYPE_STRING;
Adam Lesinski24aad162015-04-24 19:19:30 -0700350 stringRefs.emplace_back(rawValueRef, &attr->rawValue);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800351 stringRefs.emplace_back(rawValueRef,
352 reinterpret_cast<android::ResStringPool_ref*>(
353 &attr->typedValue.data));
354 }
355 attr->typedValue.size = sizeof(attr->typedValue);
356 }
357
358 out.align4();
359 node->header.size = out.size() - startIndex;
360 break;
361 }
362
363 case XmlPullParser::Event::kEndElement: {
364 const size_t startIndex = out.size();
365 android::ResXMLTree_node* node = out.nextBlock<android::ResXMLTree_node>();
366 node->header.type = android::RES_XML_END_ELEMENT_TYPE;
367 node->header.headerSize = sizeof(*node);
368 node->lineNumber = parser->getLineNumber();
369 node->comment.index = -1;
370
371 android::ResXMLTree_endElementExt* elem =
372 out.nextBlock<android::ResXMLTree_endElementExt>();
373 stringRefs.emplace_back(
374 pool.makeRef(parser->getElementNamespace(), lowPriority), &elem->ns);
375 stringRefs.emplace_back(
376 pool.makeRef(parser->getElementName(), lowPriority), &elem->name);
377
378 out.align4();
379 node->header.size = out.size() - startIndex;
380 break;
381 }
382
383 case XmlPullParser::Event::kText: {
384 StringPiece16 text = util::trimWhitespace(parser->getText());
385 if (text.empty()) {
386 break;
387 }
388
389 const size_t startIndex = out.size();
390 android::ResXMLTree_node* node = out.nextBlock<android::ResXMLTree_node>();
391 node->header.type = android::RES_XML_CDATA_TYPE;
392 node->header.headerSize = sizeof(*node);
393 node->lineNumber = parser->getLineNumber();
394 node->comment.index = -1;
395
396 android::ResXMLTree_cdataExt* elem = out.nextBlock<android::ResXMLTree_cdataExt>();
397 stringRefs.emplace_back(pool.makeRef(text, lowPriority), &elem->data);
398
399 out.align4();
400 node->header.size = out.size() - startIndex;
401 break;
402 }
403
404 default:
405 break;
406 }
407
408 }
409 out.align4();
410
411 if (error) {
412 return {};
413 }
414
415 if (parser->getEvent() == XmlPullParser::Event::kBadDocument) {
416 logger.error(parser->getLineNumber())
417 << parser->getLastError()
418 << std::endl;
419 return {};
420 }
421
422 // Merge the package pools into the main pool.
423 for (auto& packagePoolEntry : packagePools) {
424 pool.merge(std::move(packagePoolEntry.second));
425 }
426
427 // Sort so that attribute resource IDs show up first.
428 pool.sort([](const StringPool::Entry& a, const StringPool::Entry& b) -> bool {
429 return a.context.priority < b.context.priority;
430 });
431
432 // Now we flatten the string pool references into the correct places.
433 for (const auto& refEntry : stringRefs) {
434 refEntry.second->index = refEntry.first.getIndex();
435 }
436
437 // Write the XML header.
438 const size_t beforeXmlTreeIndex = outBuffer->size();
439 android::ResXMLTree_header* header = outBuffer->nextBlock<android::ResXMLTree_header>();
440 header->header.type = android::RES_XML_TYPE;
441 header->header.headerSize = sizeof(*header);
442
443 // Write the array of resource IDs, indexed by StringPool order.
444 const size_t beforeResIdMapIndex = outBuffer->size();
445 android::ResChunk_header* resIdMapChunk = outBuffer->nextBlock<android::ResChunk_header>();
446 resIdMapChunk->type = android::RES_XML_RESOURCE_MAP_TYPE;
447 resIdMapChunk->headerSize = sizeof(*resIdMapChunk);
448 for (const auto& str : pool) {
449 ResourceId id { str->context.priority };
450 if (!id.isValid()) {
451 // When we see the first non-resource ID,
452 // we're done.
453 break;
454 }
455
456 uint32_t* flatId = outBuffer->nextBlock<uint32_t>();
457 *flatId = id.id;
458 }
459 resIdMapChunk->size = outBuffer->size() - beforeResIdMapIndex;
460
461 // Flatten the StringPool.
Adam Lesinski24aad162015-04-24 19:19:30 -0700462 StringPool::flattenUtf16(outBuffer, pool);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800463
464 // Move the temporary BigBuffer into outBuffer->
465 outBuffer->appendBuffer(std::move(out));
466
467 header->header.size = outBuffer->size() - beforeXmlTreeIndex;
468
469 if (smallestStrippedAttributeSdk == std::numeric_limits<size_t>::max()) {
470 // Nothing was stripped
471 return 0u;
472 }
473 return smallestStrippedAttributeSdk;
474}
475
476} // namespace aapt