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 36namespace aapt { 37namespace xml { 38 39constexpr uint32_t kLowPriority = 0xffffffffu; 40 41// A vector that maps String refs to their final destination in the out buffer. 42using FlatStringRefList = std::vector<std::pair<StringPool::Ref, android::ResStringPool_ref*>>; 43 44struct XmlFlattener : public Visitor { 45 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 55 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 69 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 89 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 106 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 149 bool success() const { 150 return !mError; 151 } 152 153protected: 154 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 163 void addString(const StringPool::Ref& ref, android::ResStringPool_ref* dest) { 164 mStringRefs->emplace_back(ref, dest); 165 } 166 167 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 177 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 188private: 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 */ 200struct CompileXmlFlattener : public XmlFlattener { 201 CompileXmlFlattener(BigBuffer* outBuffer, StringPool* pool, FlatStringRefList* stringRefs, 202 const std::u16string& defaultPackage) : 203 XmlFlattener(outBuffer, pool, stringRefs, defaultPackage) { 204 } 205 206 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 225struct AttributeToFlatten { 226 uint32_t resourceId = 0; 227 const Attribute* xmlAttr = nullptr; 228 const ::aapt::Attribute* resourceAttr = nullptr; 229}; 230 231static 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 */ 238struct LinkedXmlFlattener : public XmlFlattener { 239 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 250 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 367 Maybe<size_t> getSmallestFilteredSdk() const { 368 if (mSmallestFilteredSdk == std::numeric_limits<size_t>::max()) { 369 return {}; 370 } 371 return mSmallestFilteredSdk; 372 } 373 374private: 375 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 */ 466static 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 509bool 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 532Maybe<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