XmlFlattener.cpp revision 330edcdf1316ed599fe0eb16a64330821fd92f18
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 { 37 38constexpr const char16_t* kSchemaAndroid = u"http://schemas.android.com/apk/res/android"; 39 40struct AttributeValueFlattener : ValueVisitor { 41 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; 61 } 62 63 Maybe<ResourceId> result = mResolver->findId(reference.name); 64 if (!result || !result.value().isValid()) { 65 std::ostream& out = mLogger->error(mParser->getLineNumber()) 66 << "unresolved reference '" 67 << aliasedName 68 << "'"; 69 if (aliasedName != reference.name) { 70 out << " (aka '" << reference.name << "')"; 71 } 72 out << "'." << std::endl; 73 *mError = true; 74 } else { 75 reference.id = result.value(); 76 reference.flatten(*mOutValue); 77 } 78 } 79 80 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)); 85 } 86 87 void visitItem(Item& item, ValueVisitorArgs&) override { 88 item.flatten(*mOutValue); 89 } 90 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; 100}; 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 113XmlFlattener::XmlFlattener(const std::shared_ptr<ResourceTable>& table, 114 const std::shared_ptr<IResolver>& resolver) : 115 mTable(table), mResolver(resolver) { 116} 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>(); 198 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 } 205 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(); 215 attrIter != endAttrIter; 216 ++attrIter) { 217 uint32_t id; 218 StringPool::Ref nameRef; 219 const Attribute* attr = nullptr; 220 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 241 Maybe<std::u16string> package = util::extractPackageFromNamespace( 242 attrIter->namespaceUri); 243 if (!package || !mResolver) { 244 // 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 246 // them some integer value that will appear after. 247 id = 0x80000000u | nextAttributeId++; 248 nameRef = pool.makeRef(attrIter->name, StringPool::Context{ id }); 249 250 } else { 251 // Find the Attribute object via our Resolver. 252 ResourceName attrName = { 253 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); 260 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 280 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. 285 nameRef = packagePools[package.value()].makeRef( 286 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 uint16_t attributeIndex = 1; 304 for (auto entry : sortedAttributes) { 305 android::ResXMLTree_attribute* attr = 306 out.nextBlock<android::ResXMLTree_attribute>(); 307 if (!entry.xmlAttr->namespaceUri.empty()) { 308 stringRefs.emplace_back( 309 pool.makeRef(entry.xmlAttr->namespaceUri, lowPriority), &attr->ns); 310 } else { 311 attr->ns.index = -1; 312 } 313 314 StringPool::Ref rawValueRef = pool.makeRef(entry.xmlAttr->value, lowPriority); 315 316 stringRefs.emplace_back(entry.nameRef, &attr->name); 317 318 if (options.keepRawValues) { 319 stringRefs.emplace_back(rawValueRef, &attr->rawValue); 320 } else { 321 attr->rawValue.index = -1; 322 } 323 324 // Assign the indices for specific attributes. 325 if (entry.xmlAttr->namespaceUri == kSchemaAndroid && 326 entry.xmlAttr->name == u"id") { 327 elem->idIndex = attributeIndex; 328 } else if (entry.xmlAttr->namespaceUri.empty()) { 329 if (entry.xmlAttr->name == u"class") { 330 elem->classIndex = attributeIndex; 331 } else if (entry.xmlAttr->name == u"style") { 332 elem->styleIndex = attributeIndex; 333 } 334 } 335 336 std::unique_ptr<Item> value; 337 if (entry.attr) { 338 value = ResourceParser::parseItemForAttribute(entry.xmlAttr->value, 339 *entry.attr); 340 } else { 341 bool create = false; 342 value = ResourceParser::tryParseReference(entry.xmlAttr->value, &create); 343 } 344 345 if (mResolver && value) { 346 AttributeValueFlattener flattener( 347 mResolver, 348 &logger, 349 &attr->typedValue, 350 parser, 351 &error, 352 rawValueRef, 353 &options.defaultPackage, 354 &stringRefs); 355 value->accept(flattener, {}); 356 } else if (!value && entry.attr && 357 !(entry.attr->typeMask & android::ResTable_map::TYPE_STRING)) { 358 logger.error(parser->getLineNumber()) 359 << "'" 360 << *rawValueRef 361 << "' is not compatible with attribute " 362 << *entry.attr 363 << "." 364 << std::endl; 365 error = true; 366 } else { 367 attr->typedValue.dataType = android::Res_value::TYPE_STRING; 368 if (!options.keepRawValues) { 369 // Don't set the string twice. 370 stringRefs.emplace_back(rawValueRef, &attr->rawValue); 371 } 372 stringRefs.emplace_back(rawValueRef, 373 reinterpret_cast<android::ResStringPool_ref*>( 374 &attr->typedValue.data)); 375 } 376 attr->typedValue.size = sizeof(attr->typedValue); 377 } 378 379 out.align4(); 380 node->header.size = out.size() - startIndex; 381 break; 382 } 383 384 case XmlPullParser::Event::kEndElement: { 385 const size_t startIndex = out.size(); 386 android::ResXMLTree_node* node = out.nextBlock<android::ResXMLTree_node>(); 387 node->header.type = android::RES_XML_END_ELEMENT_TYPE; 388 node->header.headerSize = sizeof(*node); 389 node->lineNumber = parser->getLineNumber(); 390 node->comment.index = -1; 391 392 android::ResXMLTree_endElementExt* elem = 393 out.nextBlock<android::ResXMLTree_endElementExt>(); 394 stringRefs.emplace_back( 395 pool.makeRef(parser->getElementNamespace(), lowPriority), &elem->ns); 396 stringRefs.emplace_back( 397 pool.makeRef(parser->getElementName(), lowPriority), &elem->name); 398 399 out.align4(); 400 node->header.size = out.size() - startIndex; 401 break; 402 } 403 404 case XmlPullParser::Event::kText: { 405 StringPiece16 text = util::trimWhitespace(parser->getText()); 406 if (text.empty()) { 407 break; 408 } 409 410 const size_t startIndex = out.size(); 411 android::ResXMLTree_node* node = out.nextBlock<android::ResXMLTree_node>(); 412 node->header.type = android::RES_XML_CDATA_TYPE; 413 node->header.headerSize = sizeof(*node); 414 node->lineNumber = parser->getLineNumber(); 415 node->comment.index = -1; 416 417 android::ResXMLTree_cdataExt* elem = out.nextBlock<android::ResXMLTree_cdataExt>(); 418 stringRefs.emplace_back(pool.makeRef(text, lowPriority), &elem->data); 419 420 out.align4(); 421 node->header.size = out.size() - startIndex; 422 break; 423 } 424 425 default: 426 break; 427 } 428 429 } 430 out.align4(); 431 432 if (error) { 433 return {}; 434 } 435 436 if (parser->getEvent() == XmlPullParser::Event::kBadDocument) { 437 logger.error(parser->getLineNumber()) 438 << parser->getLastError() 439 << std::endl; 440 return {}; 441 } 442 443 // Merge the package pools into the main pool. 444 for (auto& packagePoolEntry : packagePools) { 445 pool.merge(std::move(packagePoolEntry.second)); 446 } 447 448 // Sort so that attribute resource IDs show up first. 449 pool.sort([](const StringPool::Entry& a, const StringPool::Entry& b) -> bool { 450 return a.context.priority < b.context.priority; 451 }); 452 453 // Now we flatten the string pool references into the correct places. 454 for (const auto& refEntry : stringRefs) { 455 refEntry.second->index = refEntry.first.getIndex(); 456 } 457 458 // Write the XML header. 459 const size_t beforeXmlTreeIndex = outBuffer->size(); 460 android::ResXMLTree_header* header = outBuffer->nextBlock<android::ResXMLTree_header>(); 461 header->header.type = android::RES_XML_TYPE; 462 header->header.headerSize = sizeof(*header); 463 464 // Flatten the StringPool. 465 StringPool::flattenUtf16(outBuffer, pool); 466 467 // Write the array of resource IDs, indexed by StringPool order. 468 const size_t beforeResIdMapIndex = outBuffer->size(); 469 android::ResChunk_header* resIdMapChunk = outBuffer->nextBlock<android::ResChunk_header>(); 470 resIdMapChunk->type = android::RES_XML_RESOURCE_MAP_TYPE; 471 resIdMapChunk->headerSize = sizeof(*resIdMapChunk); 472 for (const auto& str : pool) { 473 ResourceId id { str->context.priority }; 474 if (!id.isValid()) { 475 // When we see the first non-resource ID, 476 // we're done. 477 break; 478 } 479 480 uint32_t* flatId = outBuffer->nextBlock<uint32_t>(); 481 *flatId = id.id; 482 } 483 resIdMapChunk->size = outBuffer->size() - beforeResIdMapIndex; 484 485 // Move the temporary BigBuffer into outBuffer. 486 outBuffer->appendBuffer(std::move(out)); 487 488 header->header.size = outBuffer->size() - beforeXmlTreeIndex; 489 490 if (smallestStrippedAttributeSdk == std::numeric_limits<size_t>::max()) { 491 // Nothing was stripped 492 return 0u; 493 } 494 return smallestStrippedAttributeSdk; 495} 496 497} // namespace aapt 498