1 /*
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
36 namespace aapt {
37 namespace xml {
38
39 constexpr uint32_t kLowPriority = 0xffffffffu;
40
41 // A vector that maps String refs to their final destination in the out buffer.
42 using FlatStringRefList = std::vector<std::pair<StringPool::Ref, android::ResStringPool_ref*>>;
43
44 struct XmlFlattener : public Visitor {
XmlFlatteneraapt::xml::XmlFlattener45 XmlFlattener(BigBuffer* outBuffer, StringPool* pool, FlatStringRefList* stringRefs,
46 const std::u16string& defaultPackage) :
47 mOut(outBuffer), mPool(pool), mStringRefs(stringRefs),
48 mDefaultPackage(defaultPackage) {
49 }
50
51 // No copying.
52 XmlFlattener(const XmlFlattener&) = delete;
53 XmlFlattener& operator=(const XmlFlattener&) = delete;
54
writeNamespaceaapt::xml::XmlFlattener55 void writeNamespace(Namespace* node, uint16_t type) {
56 const size_t startIndex = mOut->size();
57 android::ResXMLTree_node* flatNode = mOut->nextBlock<android::ResXMLTree_node>();
58 android::ResXMLTree_namespaceExt* flatNs =
59 mOut->nextBlock<android::ResXMLTree_namespaceExt>();
60 mOut->align4();
61
62 flatNode->header = { type, sizeof(*flatNode), (uint32_t)(mOut->size() - startIndex) };
63 flatNode->lineNumber = node->lineNumber;
64 flatNode->comment.index = -1;
65 addString(node->namespacePrefix, kLowPriority, &flatNs->prefix);
66 addString(node->namespaceUri, kLowPriority, &flatNs->uri);
67 }
68
visitaapt::xml::XmlFlattener69 virtual void visit(Namespace* node) override {
70 // Extract the package/prefix from this namespace node.
71 Maybe<std::u16string> package = util::extractPackageFromNamespace(node->namespaceUri);
72 if (package) {
73 mPackageAliases.emplace_back(
74 node->namespacePrefix,
75 package.value().empty() ? mDefaultPackage : package.value());
76 }
77
78 writeNamespace(node, android::RES_XML_START_NAMESPACE_TYPE);
79 for (const auto& child : node->children) {
80 child->accept(this);
81 }
82 writeNamespace(node, android::RES_XML_END_NAMESPACE_TYPE);
83
84 if (package) {
85 mPackageAliases.pop_back();
86 }
87 }
88
visitaapt::xml::XmlFlattener89 virtual void visit(Text* node) override {
90 if (util::trimWhitespace(node->text).empty()) {
91 return;
92 }
93
94 const size_t startIndex = mOut->size();
95 android::ResXMLTree_node* flatNode = mOut->nextBlock<android::ResXMLTree_node>();
96 android::ResXMLTree_cdataExt* flatText = mOut->nextBlock<android::ResXMLTree_cdataExt>();
97 mOut->align4();
98
99 const uint16_t type = android::RES_XML_CDATA_TYPE;
100 flatNode->header = { type, sizeof(*flatNode), (uint32_t)(mOut->size() - startIndex) };
101 flatNode->lineNumber = node->lineNumber;
102 flatNode->comment.index = -1;
103 addString(node->text, kLowPriority, &flatText->data);
104 }
105
visitaapt::xml::XmlFlattener106 virtual void visit(Element* node) override {
107 const size_t startIndex = mOut->size();
108 android::ResXMLTree_node* flatNode = mOut->nextBlock<android::ResXMLTree_node>();
109 android::ResXMLTree_attrExt* flatElem = mOut->nextBlock<android::ResXMLTree_attrExt>();
110
111 const uint16_t type = android::RES_XML_START_ELEMENT_TYPE;
112 flatNode->header = { type, sizeof(*flatNode), 0 };
113 flatNode->lineNumber = node->lineNumber;
114 flatNode->comment.index = -1;
115
116 addString(node->namespaceUri, kLowPriority, &flatElem->ns);
117 addString(node->name, kLowPriority, &flatElem->name);
118 flatElem->attributeStart = sizeof(*flatElem);
119 flatElem->attributeSize = sizeof(android::ResXMLTree_attribute);
120 flatElem->attributeCount = node->attributes.size();
121
122 if (!writeAttributes(mOut, node, flatElem)) {
123 mError = true;
124 }
125
126 mOut->align4();
127 flatNode->header.size = (uint32_t)(mOut->size() - startIndex);
128
129 for (const auto& child : node->children) {
130 child->accept(this);
131 }
132
133 const size_t startEndIndex = mOut->size();
134 android::ResXMLTree_node* flatEndNode = mOut->nextBlock<android::ResXMLTree_node>();
135 android::ResXMLTree_endElementExt* flatEndElem =
136 mOut->nextBlock<android::ResXMLTree_endElementExt>();
137 mOut->align4();
138
139 const uint16_t endType = android::RES_XML_END_ELEMENT_TYPE;
140 flatEndNode->header = { endType, sizeof(*flatEndNode),
141 (uint32_t)(mOut->size() - startEndIndex) };
142 flatEndNode->lineNumber = node->lineNumber;
143 flatEndNode->comment.index = -1;
144
145 addString(node->namespaceUri, kLowPriority, &flatEndElem->ns);
146 addString(node->name, kLowPriority, &flatEndElem->name);
147 }
148
successaapt::xml::XmlFlattener149 bool success() const {
150 return !mError;
151 }
152
153 protected:
addStringaapt::xml::XmlFlattener154 void addString(const StringPiece16& str, uint32_t priority, android::ResStringPool_ref* dest) {
155 if (!str.empty()) {
156 mStringRefs->emplace_back(mPool->makeRef(str, StringPool::Context{ priority }), dest);
157 } else {
158 // The device doesn't think a string of size 0 is the same as null.
159 dest->index = -1;
160 }
161 }
162
addStringaapt::xml::XmlFlattener163 void addString(const StringPool::Ref& ref, android::ResStringPool_ref* dest) {
164 mStringRefs->emplace_back(ref, dest);
165 }
166
getPackageAliasaapt::xml::XmlFlattener167 Maybe<std::u16string> getPackageAlias(const std::u16string& prefix) {
168 const auto endIter = mPackageAliases.rend();
169 for (auto iter = mPackageAliases.rbegin(); iter != endIter; ++iter) {
170 if (iter->first == prefix) {
171 return iter->second;
172 }
173 }
174 return {};
175 }
176
getDefaultPackageaapt::xml::XmlFlattener177 const std::u16string& getDefaultPackage() const {
178 return mDefaultPackage;
179 }
180
181 /**
182 * Subclasses override this to deal with attributes. Attributes can be flattened as
183 * raw values or as resources.
184 */
185 virtual bool writeAttributes(BigBuffer* out, Element* node,
186 android::ResXMLTree_attrExt* flatElem) = 0;
187
188 private:
189 BigBuffer* mOut;
190 StringPool* mPool;
191 FlatStringRefList* mStringRefs;
192 std::u16string mDefaultPackage;
193 bool mError = false;
194 std::vector<std::pair<std::u16string, std::u16string>> mPackageAliases;
195 };
196
197 /**
198 * Flattens XML, encoding the attributes as raw strings. This is used in the compile phase.
199 */
200 struct CompileXmlFlattener : public XmlFlattener {
CompileXmlFlatteneraapt::xml::CompileXmlFlattener201 CompileXmlFlattener(BigBuffer* outBuffer, StringPool* pool, FlatStringRefList* stringRefs,
202 const std::u16string& defaultPackage) :
203 XmlFlattener(outBuffer, pool, stringRefs, defaultPackage) {
204 }
205
writeAttributesaapt::xml::CompileXmlFlattener206 virtual bool writeAttributes(BigBuffer* out, Element* node,
207 android::ResXMLTree_attrExt* flatElem) override {
208 flatElem->attributeCount = node->attributes.size();
209 if (node->attributes.empty()) {
210 return true;
211 }
212
213 android::ResXMLTree_attribute* flatAttrs = out->nextBlock<android::ResXMLTree_attribute>(
214 node->attributes.size());
215 for (const Attribute& attr : node->attributes) {
216 addString(attr.namespaceUri, kLowPriority, &flatAttrs->ns);
217 addString(attr.name, kLowPriority, &flatAttrs->name);
218 addString(attr.value, kLowPriority, &flatAttrs->rawValue);
219 flatAttrs++;
220 }
221 return true;
222 }
223 };
224
225 struct AttributeToFlatten {
226 uint32_t resourceId = 0;
227 const Attribute* xmlAttr = nullptr;
228 const ::aapt::Attribute* resourceAttr = nullptr;
229 };
230
lessAttributeId(const AttributeToFlatten & a,uint32_t id)231 static bool lessAttributeId(const AttributeToFlatten& a, uint32_t id) {
232 return a.resourceId < id;
233 }
234
235 /**
236 * Flattens XML, encoding the attributes as resources.
237 */
238 struct LinkedXmlFlattener : public XmlFlattener {
LinkedXmlFlatteneraapt::xml::LinkedXmlFlattener239 LinkedXmlFlattener(BigBuffer* outBuffer, StringPool* pool,
240 std::map<std::u16string, StringPool>* packagePools,
241 FlatStringRefList* stringRefs,
242 const std::u16string& defaultPackage,
243 const std::shared_ptr<IResolver>& resolver,
244 SourceLogger* logger,
245 const FlattenOptions& options) :
246 XmlFlattener(outBuffer, pool, stringRefs, defaultPackage), mResolver(resolver),
247 mLogger(logger), mPackagePools(packagePools), mOptions(options) {
248 }
249
writeAttributesaapt::xml::LinkedXmlFlattener250 virtual bool writeAttributes(BigBuffer* out, Element* node,
251 android::ResXMLTree_attrExt* flatElem) override {
252 bool error = false;
253 std::vector<AttributeToFlatten> sortedAttributes;
254 uint32_t nextAttributeId = 0x80000000u;
255
256 // Sort and filter attributes by their resource ID.
257 for (const Attribute& attr : node->attributes) {
258 AttributeToFlatten attrToFlatten;
259 attrToFlatten.xmlAttr = &attr;
260
261 Maybe<std::u16string> package = util::extractPackageFromNamespace(attr.namespaceUri);
262 if (package) {
263 // Find the Attribute object via our Resolver.
264 ResourceName attrName = { package.value(), ResourceType::kAttr, attr.name };
265 if (attrName.package.empty()) {
266 attrName.package = getDefaultPackage();
267 }
268
269 Maybe<IResolver::Entry> result = mResolver->findAttribute(attrName);
270 if (!result || !result.value().id.isValid() || !result.value().attr) {
271 error = true;
272 mLogger->error(node->lineNumber)
273 << "unresolved attribute '" << attrName << "'."
274 << std::endl;
275 } else {
276 attrToFlatten.resourceId = result.value().id.id;
277 attrToFlatten.resourceAttr = result.value().attr;
278
279 size_t sdk = findAttributeSdkLevel(attrToFlatten.resourceId);
280 if (mOptions.maxSdkAttribute && sdk > mOptions.maxSdkAttribute.value()) {
281 // We need to filter this attribute out.
282 mSmallestFilteredSdk = std::min(mSmallestFilteredSdk, sdk);
283 continue;
284 }
285 }
286 }
287
288 if (attrToFlatten.resourceId == 0) {
289 // Attributes that have no resource ID (because they don't belong to a
290 // package) should appear after those that do have resource IDs. Assign
291 // them some integer value that will appear after.
292 attrToFlatten.resourceId = nextAttributeId++;
293 }
294
295 // Insert the attribute into the sorted vector.
296 auto iter = std::lower_bound(sortedAttributes.begin(), sortedAttributes.end(),
297 attrToFlatten.resourceId, lessAttributeId);
298 sortedAttributes.insert(iter, std::move(attrToFlatten));
299 }
300
301 flatElem->attributeCount = sortedAttributes.size();
302 if (sortedAttributes.empty()) {
303 return true;
304 }
305
306 android::ResXMLTree_attribute* flatAttr = out->nextBlock<android::ResXMLTree_attribute>(
307 sortedAttributes.size());
308
309 // Now that we have sorted the attributes into their final encoded order, it's time
310 // to actually write them out.
311 uint16_t attributeIndex = 1;
312 for (const AttributeToFlatten& attrToFlatten : sortedAttributes) {
313 Maybe<std::u16string> package = util::extractPackageFromNamespace(
314 attrToFlatten.xmlAttr->namespaceUri);
315
316 // Assign the indices for specific attributes.
317 if (package && package.value() == u"android" && attrToFlatten.xmlAttr->name == u"id") {
318 flatElem->idIndex = attributeIndex;
319 } else if (attrToFlatten.xmlAttr->namespaceUri.empty()) {
320 if (attrToFlatten.xmlAttr->name == u"class") {
321 flatElem->classIndex = attributeIndex;
322 } else if (attrToFlatten.xmlAttr->name == u"style") {
323 flatElem->styleIndex = attributeIndex;
324 }
325 }
326 attributeIndex++;
327
328 // Add the namespaceUri and name to the list of StringRefs to encode.
329 addString(attrToFlatten.xmlAttr->namespaceUri, kLowPriority, &flatAttr->ns);
330 flatAttr->rawValue.index = -1;
331
332 if (!attrToFlatten.resourceAttr) {
333 addString(attrToFlatten.xmlAttr->name, kLowPriority, &flatAttr->name);
334 } else {
335 // We've already extracted the package successfully before.
336 assert(package);
337
338 // Attribute names are stored without packages, but we use
339 // their StringPool index to lookup their resource IDs.
340 // This will cause collisions, so we can't dedupe
341 // attribute names from different packages. We use separate
342 // pools that we later combine.
343 //
344 // Lookup the StringPool for this package and make the reference there.
345 StringPool::Ref nameRef = (*mPackagePools)[package.value()].makeRef(
346 attrToFlatten.xmlAttr->name,
347 StringPool::Context{ attrToFlatten.resourceId });
348
349 // Add it to the list of strings to flatten.
350 addString(nameRef, &flatAttr->name);
351
352 if (mOptions.keepRawValues) {
353 // Keep raw values (this is for static libraries).
354 // TODO(with a smarter inflater for binary XML, we can do without this).
355 addString(attrToFlatten.xmlAttr->value, kLowPriority, &flatAttr->rawValue);
356 }
357 }
358
359 error |= !flattenItem(node, attrToFlatten.xmlAttr->value, attrToFlatten.resourceAttr,
360 flatAttr);
361 flatAttr->typedValue.size = sizeof(flatAttr->typedValue);
362 flatAttr++;
363 }
364 return !error;
365 }
366
getSmallestFilteredSdkaapt::xml::LinkedXmlFlattener367 Maybe<size_t> getSmallestFilteredSdk() const {
368 if (mSmallestFilteredSdk == std::numeric_limits<size_t>::max()) {
369 return {};
370 }
371 return mSmallestFilteredSdk;
372 }
373
374 private:
flattenItemaapt::xml::LinkedXmlFlattener375 bool flattenItem(const Node* el, const std::u16string& value, const ::aapt::Attribute* attr,
376 android::ResXMLTree_attribute* flatAttr) {
377 std::unique_ptr<Item> item;
378 if (!attr) {
379 bool create = false;
380 item = ResourceParser::tryParseReference(value, &create);
381 if (!item) {
382 flatAttr->typedValue.dataType = android::Res_value::TYPE_STRING;
383 addString(value, kLowPriority, &flatAttr->rawValue);
384 addString(value, kLowPriority, reinterpret_cast<android::ResStringPool_ref*>(
385 &flatAttr->typedValue.data));
386 return true;
387 }
388 } else {
389 item = ResourceParser::parseItemForAttribute(value, *attr);
390 if (!item) {
391 if (!(attr->typeMask & android::ResTable_map::TYPE_STRING)) {
392 mLogger->error(el->lineNumber)
393 << "'"
394 << value
395 << "' is not compatible with attribute '"
396 << *attr
397 << "'."
398 << std::endl;
399 return false;
400 }
401
402 flatAttr->typedValue.dataType = android::Res_value::TYPE_STRING;
403 addString(value, kLowPriority, &flatAttr->rawValue);
404 addString(value, kLowPriority, reinterpret_cast<android::ResStringPool_ref*>(
405 &flatAttr->typedValue.data));
406 return true;
407 }
408 }
409
410 assert(item);
411
412 bool error = false;
413
414 // If this is a reference, resolve the name into an ID.
415 visitFunc<Reference>(*item, [&](Reference& reference) {
416 // First see if we can convert the package name from a prefix to a real
417 // package name.
418 ResourceName realName = reference.name;
419 if (!realName.package.empty()) {
420 Maybe<std::u16string> package = getPackageAlias(realName.package);
421 if (package) {
422 realName.package = package.value();
423 }
424 } else {
425 realName.package = getDefaultPackage();
426 }
427
428 Maybe<ResourceId> result = mResolver->findId(realName);
429 if (!result || !result.value().isValid()) {
430 std::ostream& out = mLogger->error(el->lineNumber)
431 << "unresolved reference '"
432 << reference.name
433 << "'";
434 if (realName != reference.name) {
435 out << " (aka '" << realName << "')";
436 }
437 out << "'." << std::endl;
438 error = true;
439 } else {
440 reference.id = result.value();
441 }
442 });
443
444 if (error) {
445 return false;
446 }
447
448 item->flatten(flatAttr->typedValue);
449 return true;
450 }
451
452 std::shared_ptr<IResolver> mResolver;
453 SourceLogger* mLogger;
454 std::map<std::u16string, StringPool>* mPackagePools;
455 FlattenOptions mOptions;
456 size_t mSmallestFilteredSdk = std::numeric_limits<size_t>::max();
457 };
458
459 /**
460 * The binary XML file expects the StringPool to appear first, but we haven't collected the
461 * strings yet. We write to a temporary BigBuffer while parsing the input, adding strings
462 * we encounter to the StringPool. At the end, we write the StringPool to the given BigBuffer and
463 * then move the data from the temporary BigBuffer into the given one. This incurs no
464 * copies as the given BigBuffer simply takes ownership of the data.
465 */
flattenXml(StringPool * pool,FlatStringRefList * stringRefs,BigBuffer * outBuffer,BigBuffer && xmlTreeBuffer)466 static void flattenXml(StringPool* pool, FlatStringRefList* stringRefs, BigBuffer* outBuffer,
467 BigBuffer&& xmlTreeBuffer) {
468 // Sort the string pool so that attribute resource IDs show up first.
469 pool->sort([](const StringPool::Entry& a, const StringPool::Entry& b) -> bool {
470 return a.context.priority < b.context.priority;
471 });
472
473 // Now we flatten the string pool references into the correct places.
474 for (const auto& refEntry : *stringRefs) {
475 refEntry.second->index = refEntry.first.getIndex();
476 }
477
478 // Write the XML header.
479 const size_t beforeXmlTreeIndex = outBuffer->size();
480 android::ResXMLTree_header* header = outBuffer->nextBlock<android::ResXMLTree_header>();
481 header->header.type = android::RES_XML_TYPE;
482 header->header.headerSize = sizeof(*header);
483
484 // Flatten the StringPool.
485 StringPool::flattenUtf16(outBuffer, *pool);
486
487 // Write the array of resource IDs, indexed by StringPool order.
488 const size_t beforeResIdMapIndex = outBuffer->size();
489 android::ResChunk_header* resIdMapChunk = outBuffer->nextBlock<android::ResChunk_header>();
490 resIdMapChunk->type = android::RES_XML_RESOURCE_MAP_TYPE;
491 resIdMapChunk->headerSize = sizeof(*resIdMapChunk);
492 for (const auto& str : *pool) {
493 ResourceId id { str->context.priority };
494 if (id.id == kLowPriority || !id.isValid()) {
495 // When we see the first non-resource ID,
496 // we're done.
497 break;
498 }
499
500 *outBuffer->nextBlock<uint32_t>() = id.id;
501 }
502 resIdMapChunk->size = outBuffer->size() - beforeResIdMapIndex;
503
504 // Move the temporary BigBuffer into outBuffer.
505 outBuffer->appendBuffer(std::move(xmlTreeBuffer));
506 header->header.size = outBuffer->size() - beforeXmlTreeIndex;
507 }
508
flatten(Node * root,const std::u16string & defaultPackage,BigBuffer * outBuffer)509 bool flatten(Node* root, const std::u16string& defaultPackage, BigBuffer* outBuffer) {
510 StringPool pool;
511
512 // This will hold the StringRefs and the location in which to write the index.
513 // Once we sort the StringPool, we can assign the updated indices
514 // to the correct data locations.
515 FlatStringRefList stringRefs;
516
517 // Since we don't know the size of the final StringPool, we write to this
518 // temporary BigBuffer, which we will append to outBuffer later.
519 BigBuffer out(1024);
520
521 CompileXmlFlattener flattener(&out, &pool, &stringRefs, defaultPackage);
522 root->accept(&flattener);
523
524 if (!flattener.success()) {
525 return false;
526 }
527
528 flattenXml(&pool, &stringRefs, outBuffer, std::move(out));
529 return true;
530 };
531
flattenAndLink(const Source & source,Node * root,const std::u16string & defaultPackage,const std::shared_ptr<IResolver> & resolver,const FlattenOptions & options,BigBuffer * outBuffer)532 Maybe<size_t> flattenAndLink(const Source& source, Node* root,
533 const std::u16string& defaultPackage,
534 const std::shared_ptr<IResolver>& resolver,
535 const FlattenOptions& options, BigBuffer* outBuffer) {
536 SourceLogger logger(source);
537 StringPool pool;
538
539 // Attribute names are stored without packages, but we use
540 // their StringPool index to lookup their resource IDs.
541 // This will cause collisions, so we can't dedupe
542 // attribute names from different packages. We use separate
543 // pools that we later combine.
544 std::map<std::u16string, StringPool> packagePools;
545
546 FlatStringRefList stringRefs;
547
548 // Since we don't know the size of the final StringPool, we write to this
549 // temporary BigBuffer, which we will append to outBuffer later.
550 BigBuffer out(1024);
551
552 LinkedXmlFlattener flattener(&out, &pool, &packagePools, &stringRefs, defaultPackage, resolver,
553 &logger, options);
554 root->accept(&flattener);
555
556 if (!flattener.success()) {
557 return {};
558 }
559
560 // Merge the package pools into the main pool.
561 for (auto& packagePoolEntry : packagePools) {
562 pool.merge(std::move(packagePoolEntry.second));
563 }
564
565 flattenXml(&pool, &stringRefs, outBuffer, std::move(out));
566
567 if (flattener.getSmallestFilteredSdk()) {
568 return flattener.getSmallestFilteredSdk();
569 }
570 return 0;
571 }
572
573 } // namespace xml
574 } // namespace aapt
575