Adam Lesinski | 6f6ceb7 | 2014-11-14 14:48:12 -0800 | [diff] [blame] | 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 | |
Adam Lesinski | 769de98 | 2015-04-10 19:43:55 -0700 | [diff] [blame^] | 38 | constexpr const char16_t* kSchemaAndroid = u"http://schemas.android.com/apk/res/android"; |
| 39 | constexpr const char16_t* kSchemaAuto = u"http://schemas.android.com/apk/res-auto"; |
| 40 | constexpr const char16_t* kSchemaPrefix = u"http://schemas.android.com/apk/res/"; |
| 41 | |
Adam Lesinski | 6f6ceb7 | 2014-11-14 14:48:12 -0800 | [diff] [blame] | 42 | struct AttributeValueFlattener : ValueVisitor { |
| 43 | struct Args : ValueVisitorArgs { |
| 44 | Args(std::shared_ptr<Resolver> r, SourceLogger& s, android::Res_value& oV, |
| 45 | std::shared_ptr<XmlPullParser> p, bool& e, StringPool::Ref& rV, |
| 46 | std::vector<std::pair<StringPool::Ref, android::ResStringPool_ref*>>& sR) : |
| 47 | resolver(r), logger(s), outValue(oV), parser(p), error(e), rawValue(rV), |
| 48 | stringRefs(sR) { |
| 49 | } |
| 50 | |
| 51 | std::shared_ptr<Resolver> resolver; |
| 52 | SourceLogger& logger; |
| 53 | android::Res_value& outValue; |
| 54 | std::shared_ptr<XmlPullParser> parser; |
| 55 | bool& error; |
| 56 | StringPool::Ref& rawValue; |
| 57 | std::vector<std::pair<StringPool::Ref, android::ResStringPool_ref*>>& stringRefs; |
| 58 | }; |
| 59 | |
| 60 | void visit(Reference& reference, ValueVisitorArgs& a) override { |
| 61 | Args& args = static_cast<Args&>(a); |
| 62 | |
| 63 | Maybe<ResourceId> result = args.resolver->findId(reference.name); |
| 64 | if (!result || !result.value().isValid()) { |
| 65 | args.logger.error(args.parser->getLineNumber()) |
| 66 | << "unresolved reference '" |
| 67 | << reference.name |
| 68 | << "'." |
| 69 | << std::endl; |
| 70 | args.error = true; |
| 71 | } else { |
| 72 | reference.id = result.value(); |
| 73 | reference.flatten(args.outValue); |
| 74 | } |
| 75 | } |
| 76 | |
| 77 | void visit(String& string, ValueVisitorArgs& a) override { |
| 78 | Args& args = static_cast<Args&>(a); |
| 79 | |
| 80 | args.outValue.dataType = android::Res_value::TYPE_STRING; |
| 81 | args.stringRefs.emplace_back(args.rawValue, |
| 82 | reinterpret_cast<android::ResStringPool_ref*>(&args.outValue.data)); |
| 83 | } |
| 84 | |
| 85 | void visitItem(Item& item, ValueVisitorArgs& a) override { |
| 86 | Args& args = static_cast<Args&>(a); |
| 87 | item.flatten(args.outValue); |
| 88 | } |
| 89 | }; |
| 90 | |
| 91 | struct XmlAttribute { |
| 92 | uint32_t resourceId; |
| 93 | const XmlPullParser::Attribute* xmlAttr; |
| 94 | const Attribute* attr; |
| 95 | StringPool::Ref nameRef; |
| 96 | }; |
| 97 | |
| 98 | static bool lessAttributeId(const XmlAttribute& a, uint32_t id) { |
| 99 | return a.resourceId < id; |
| 100 | } |
| 101 | |
Adam Lesinski | 769de98 | 2015-04-10 19:43:55 -0700 | [diff] [blame^] | 102 | XmlFlattener::XmlFlattener(const std::shared_ptr<ResourceTable>& table, |
| 103 | const std::shared_ptr<Resolver>& resolver) : |
| 104 | mTable(table), mResolver(resolver) { |
Adam Lesinski | 6f6ceb7 | 2014-11-14 14:48:12 -0800 | [diff] [blame] | 105 | } |
| 106 | |
| 107 | /** |
| 108 | * Reads events from the parser and writes to a BigBuffer. The binary XML file |
| 109 | * expects the StringPool to appear first, but we haven't collected the strings yet. We |
| 110 | * write to a temporary BigBuffer while parsing the input, adding strings we encounter |
| 111 | * to the StringPool. At the end, we write the StringPool to the given BigBuffer and |
| 112 | * then move the data from the temporary BigBuffer into the given one. This incurs no |
| 113 | * copies as the given BigBuffer simply takes ownership of the data. |
| 114 | */ |
| 115 | Maybe<size_t> XmlFlattener::flatten(const Source& source, |
| 116 | const std::shared_ptr<XmlPullParser>& parser, |
| 117 | BigBuffer* outBuffer, Options options) { |
| 118 | SourceLogger logger(source); |
| 119 | StringPool pool; |
| 120 | bool error = false; |
| 121 | |
| 122 | size_t smallestStrippedAttributeSdk = std::numeric_limits<size_t>::max(); |
| 123 | |
| 124 | // Attribute names are stored without packages, but we use |
| 125 | // their StringPool index to lookup their resource IDs. |
| 126 | // This will cause collisions, so we can't dedupe |
| 127 | // attribute names from different packages. We use separate |
| 128 | // pools that we later combine. |
| 129 | std::map<std::u16string, StringPool> packagePools; |
| 130 | |
| 131 | // Attribute resource IDs are stored in the same order |
| 132 | // as the attribute names appear in the StringPool. |
| 133 | // Since the StringPool contains more than just attribute |
| 134 | // names, to maintain a tight packing of resource IDs, |
| 135 | // we must ensure that attribute names appear first |
| 136 | // in our StringPool. For this, we assign a low priority |
| 137 | // (0xffffffff) to non-attribute strings. Attribute |
| 138 | // names will be stored along with a priority equal |
| 139 | // to their resource ID so that they are ordered. |
| 140 | StringPool::Context lowPriority { 0xffffffffu }; |
| 141 | |
| 142 | // Once we sort the StringPool, we can assign the updated indices |
| 143 | // to the correct data locations. |
| 144 | std::vector<std::pair<StringPool::Ref, android::ResStringPool_ref*>> stringRefs; |
| 145 | |
| 146 | // Since we don't know the size of the final StringPool, we write to this |
| 147 | // temporary BigBuffer, which we will append to outBuffer later. |
| 148 | BigBuffer out(1024); |
| 149 | while (XmlPullParser::isGoodEvent(parser->next())) { |
| 150 | XmlPullParser::Event event = parser->getEvent(); |
| 151 | switch (event) { |
| 152 | case XmlPullParser::Event::kStartNamespace: |
| 153 | case XmlPullParser::Event::kEndNamespace: { |
| 154 | const size_t startIndex = out.size(); |
| 155 | android::ResXMLTree_node* node = out.nextBlock<android::ResXMLTree_node>(); |
| 156 | if (event == XmlPullParser::Event::kStartNamespace) { |
| 157 | node->header.type = android::RES_XML_START_NAMESPACE_TYPE; |
| 158 | } else { |
| 159 | node->header.type = android::RES_XML_END_NAMESPACE_TYPE; |
| 160 | } |
| 161 | |
| 162 | node->header.headerSize = sizeof(*node); |
| 163 | node->lineNumber = parser->getLineNumber(); |
| 164 | node->comment.index = -1; |
| 165 | |
| 166 | android::ResXMLTree_namespaceExt* ns = |
| 167 | out.nextBlock<android::ResXMLTree_namespaceExt>(); |
| 168 | stringRefs.emplace_back( |
| 169 | pool.makeRef(parser->getNamespacePrefix(), lowPriority), &ns->prefix); |
| 170 | stringRefs.emplace_back( |
| 171 | pool.makeRef(parser->getNamespaceUri(), lowPriority), &ns->uri); |
| 172 | |
| 173 | out.align4(); |
| 174 | node->header.size = out.size() - startIndex; |
| 175 | break; |
| 176 | } |
| 177 | |
| 178 | case XmlPullParser::Event::kStartElement: { |
| 179 | const size_t startIndex = out.size(); |
| 180 | android::ResXMLTree_node* node = out.nextBlock<android::ResXMLTree_node>(); |
| 181 | node->header.type = android::RES_XML_START_ELEMENT_TYPE; |
| 182 | node->header.headerSize = sizeof(*node); |
| 183 | node->lineNumber = parser->getLineNumber(); |
| 184 | node->comment.index = -1; |
| 185 | |
| 186 | android::ResXMLTree_attrExt* elem = out.nextBlock<android::ResXMLTree_attrExt>(); |
| 187 | stringRefs.emplace_back( |
| 188 | pool.makeRef(parser->getElementNamespace(), lowPriority), &elem->ns); |
| 189 | stringRefs.emplace_back( |
| 190 | pool.makeRef(parser->getElementName(), lowPriority), &elem->name); |
| 191 | elem->attributeStart = sizeof(*elem); |
| 192 | elem->attributeSize = sizeof(android::ResXMLTree_attribute); |
| 193 | |
| 194 | // The resource system expects attributes to be sorted by resource ID. |
| 195 | std::vector<XmlAttribute> sortedAttributes; |
| 196 | uint32_t nextAttributeId = 0; |
| 197 | const auto endAttrIter = parser->endAttributes(); |
| 198 | for (auto attrIter = parser->beginAttributes(); |
Adam Lesinski | 769de98 | 2015-04-10 19:43:55 -0700 | [diff] [blame^] | 199 | attrIter != endAttrIter; |
| 200 | ++attrIter) { |
Adam Lesinski | 6f6ceb7 | 2014-11-14 14:48:12 -0800 | [diff] [blame] | 201 | uint32_t id; |
| 202 | StringPool::Ref nameRef; |
| 203 | const Attribute* attr = nullptr; |
Adam Lesinski | 769de98 | 2015-04-10 19:43:55 -0700 | [diff] [blame^] | 204 | |
| 205 | if (options.maxSdkAttribute && attrIter->namespaceUri == kSchemaAndroid) { |
| 206 | size_t sdkVersion = findAttributeSdkLevel(attrIter->name); |
| 207 | if (sdkVersion > options.maxSdkAttribute.value()) { |
| 208 | // We will silently omit this attribute |
| 209 | smallestStrippedAttributeSdk = |
| 210 | std::min(smallestStrippedAttributeSdk, sdkVersion); |
| 211 | continue; |
| 212 | } |
| 213 | } |
| 214 | |
| 215 | ResourceNameRef genIdName; |
| 216 | bool create = false; |
| 217 | bool privateRef = false; |
| 218 | if (mTable && ResourceParser::tryParseReference(attrIter->value, &genIdName, |
| 219 | &create, &privateRef) && create) { |
| 220 | mTable->addResource(genIdName, {}, source.line(parser->getLineNumber()), |
| 221 | util::make_unique<Id>()); |
| 222 | } |
| 223 | |
| 224 | |
| 225 | StringPiece16 package; |
| 226 | if (util::stringStartsWith<char16_t>(attrIter->namespaceUri, kSchemaPrefix)) { |
| 227 | StringPiece16 schemaPrefix = kSchemaPrefix; |
| 228 | package = attrIter->namespaceUri; |
| 229 | package = package.substr(schemaPrefix.size(), |
| 230 | package.size() - schemaPrefix.size()); |
| 231 | } else if (attrIter->namespaceUri == kSchemaAuto && mResolver) { |
| 232 | package = mResolver->getDefaultPackage(); |
| 233 | } |
| 234 | |
| 235 | if (package.empty() || !mResolver) { |
Adam Lesinski | 6f6ceb7 | 2014-11-14 14:48:12 -0800 | [diff] [blame] | 236 | // Attributes that have no resource ID (because they don't belong to a |
| 237 | // package) should appear after those that do have resource IDs. Assign |
| 238 | // them some/ integer value that will appear after. |
| 239 | id = 0x80000000u | nextAttributeId++; |
| 240 | nameRef = pool.makeRef(attrIter->name, StringPool::Context{ id }); |
Adam Lesinski | 6f6ceb7 | 2014-11-14 14:48:12 -0800 | [diff] [blame] | 241 | |
Adam Lesinski | 769de98 | 2015-04-10 19:43:55 -0700 | [diff] [blame^] | 242 | } else { |
Adam Lesinski | 6f6ceb7 | 2014-11-14 14:48:12 -0800 | [diff] [blame] | 243 | // Find the Attribute object via our Resolver. |
| 244 | ResourceName attrName = { |
| 245 | package.toString(), ResourceType::kAttr, attrIter->name }; |
| 246 | Maybe<Resolver::Entry> result = mResolver->findAttribute(attrName); |
| 247 | if (!result || !result.value().id.isValid()) { |
| 248 | logger.error(parser->getLineNumber()) |
| 249 | << "unresolved attribute '" |
| 250 | << attrName |
| 251 | << "'." |
| 252 | << std::endl; |
| 253 | error = true; |
| 254 | continue; |
| 255 | } |
| 256 | |
| 257 | if (!result.value().attr) { |
| 258 | logger.error(parser->getLineNumber()) |
| 259 | << "not a valid attribute '" |
| 260 | << attrName |
| 261 | << "'." |
| 262 | << std::endl; |
| 263 | error = true; |
| 264 | continue; |
| 265 | } |
| 266 | |
Adam Lesinski | 6f6ceb7 | 2014-11-14 14:48:12 -0800 | [diff] [blame] | 267 | id = result.value().id.id; |
| 268 | attr = result.value().attr; |
| 269 | |
| 270 | // Put the attribute name into a package specific pool, since we don't |
| 271 | // want to collapse names from different packages. |
| 272 | nameRef = packagePools[package.toString()].makeRef( |
| 273 | attrIter->name, StringPool::Context{ id }); |
| 274 | } |
| 275 | |
| 276 | // Insert the attribute into the sorted vector. |
| 277 | auto iter = std::lower_bound(sortedAttributes.begin(), sortedAttributes.end(), |
| 278 | id, lessAttributeId); |
| 279 | sortedAttributes.insert(iter, XmlAttribute{ id, &*attrIter, attr, nameRef }); |
| 280 | } |
| 281 | |
| 282 | if (error) { |
| 283 | break; |
| 284 | } |
| 285 | |
| 286 | // Now that we have filtered out some attributes, get the final count. |
| 287 | elem->attributeCount = sortedAttributes.size(); |
| 288 | |
| 289 | // Flatten the sorted attributes. |
| 290 | for (auto entry : sortedAttributes) { |
| 291 | android::ResXMLTree_attribute* attr = |
| 292 | out.nextBlock<android::ResXMLTree_attribute>(); |
| 293 | stringRefs.emplace_back( |
| 294 | pool.makeRef(entry.xmlAttr->namespaceUri, lowPriority), &attr->ns); |
| 295 | StringPool::Ref rawValueRef = pool.makeRef(entry.xmlAttr->value, lowPriority); |
| 296 | stringRefs.emplace_back(rawValueRef, &attr->rawValue); |
| 297 | stringRefs.emplace_back(entry.nameRef, &attr->name); |
| 298 | |
| 299 | if (entry.attr) { |
| 300 | std::unique_ptr<Item> value = ResourceParser::parseItemForAttribute( |
| 301 | entry.xmlAttr->value, *entry.attr, mResolver->getDefaultPackage()); |
| 302 | if (value) { |
| 303 | AttributeValueFlattener flattener; |
| 304 | value->accept(flattener, AttributeValueFlattener::Args{ |
| 305 | mResolver, |
| 306 | logger, |
| 307 | attr->typedValue, |
| 308 | parser, |
| 309 | error, |
| 310 | rawValueRef, |
| 311 | stringRefs |
| 312 | }); |
| 313 | } else if (!(entry.attr->typeMask & android::ResTable_map::TYPE_STRING)) { |
| 314 | logger.error(parser->getLineNumber()) |
| 315 | << "'" |
| 316 | << *rawValueRef |
| 317 | << "' is not compatible with attribute " |
| 318 | << *entry.attr |
| 319 | << "." |
| 320 | << std::endl; |
| 321 | error = true; |
| 322 | } else { |
| 323 | attr->typedValue.dataType = android::Res_value::TYPE_STRING; |
| 324 | stringRefs.emplace_back(rawValueRef, |
| 325 | reinterpret_cast<android::ResStringPool_ref*>( |
| 326 | &attr->typedValue.data)); |
| 327 | } |
| 328 | } else { |
| 329 | attr->typedValue.dataType = android::Res_value::TYPE_STRING; |
| 330 | stringRefs.emplace_back(rawValueRef, |
| 331 | reinterpret_cast<android::ResStringPool_ref*>( |
| 332 | &attr->typedValue.data)); |
| 333 | } |
| 334 | attr->typedValue.size = sizeof(attr->typedValue); |
| 335 | } |
| 336 | |
| 337 | out.align4(); |
| 338 | node->header.size = out.size() - startIndex; |
| 339 | break; |
| 340 | } |
| 341 | |
| 342 | case XmlPullParser::Event::kEndElement: { |
| 343 | const size_t startIndex = out.size(); |
| 344 | android::ResXMLTree_node* node = out.nextBlock<android::ResXMLTree_node>(); |
| 345 | node->header.type = android::RES_XML_END_ELEMENT_TYPE; |
| 346 | node->header.headerSize = sizeof(*node); |
| 347 | node->lineNumber = parser->getLineNumber(); |
| 348 | node->comment.index = -1; |
| 349 | |
| 350 | android::ResXMLTree_endElementExt* elem = |
| 351 | out.nextBlock<android::ResXMLTree_endElementExt>(); |
| 352 | stringRefs.emplace_back( |
| 353 | pool.makeRef(parser->getElementNamespace(), lowPriority), &elem->ns); |
| 354 | stringRefs.emplace_back( |
| 355 | pool.makeRef(parser->getElementName(), lowPriority), &elem->name); |
| 356 | |
| 357 | out.align4(); |
| 358 | node->header.size = out.size() - startIndex; |
| 359 | break; |
| 360 | } |
| 361 | |
| 362 | case XmlPullParser::Event::kText: { |
| 363 | StringPiece16 text = util::trimWhitespace(parser->getText()); |
| 364 | if (text.empty()) { |
| 365 | break; |
| 366 | } |
| 367 | |
| 368 | const size_t startIndex = out.size(); |
| 369 | android::ResXMLTree_node* node = out.nextBlock<android::ResXMLTree_node>(); |
| 370 | node->header.type = android::RES_XML_CDATA_TYPE; |
| 371 | node->header.headerSize = sizeof(*node); |
| 372 | node->lineNumber = parser->getLineNumber(); |
| 373 | node->comment.index = -1; |
| 374 | |
| 375 | android::ResXMLTree_cdataExt* elem = out.nextBlock<android::ResXMLTree_cdataExt>(); |
| 376 | stringRefs.emplace_back(pool.makeRef(text, lowPriority), &elem->data); |
| 377 | |
| 378 | out.align4(); |
| 379 | node->header.size = out.size() - startIndex; |
| 380 | break; |
| 381 | } |
| 382 | |
| 383 | default: |
| 384 | break; |
| 385 | } |
| 386 | |
| 387 | } |
| 388 | out.align4(); |
| 389 | |
| 390 | if (error) { |
| 391 | return {}; |
| 392 | } |
| 393 | |
| 394 | if (parser->getEvent() == XmlPullParser::Event::kBadDocument) { |
| 395 | logger.error(parser->getLineNumber()) |
| 396 | << parser->getLastError() |
| 397 | << std::endl; |
| 398 | return {}; |
| 399 | } |
| 400 | |
| 401 | // Merge the package pools into the main pool. |
| 402 | for (auto& packagePoolEntry : packagePools) { |
| 403 | pool.merge(std::move(packagePoolEntry.second)); |
| 404 | } |
| 405 | |
| 406 | // Sort so that attribute resource IDs show up first. |
| 407 | pool.sort([](const StringPool::Entry& a, const StringPool::Entry& b) -> bool { |
| 408 | return a.context.priority < b.context.priority; |
| 409 | }); |
| 410 | |
| 411 | // Now we flatten the string pool references into the correct places. |
| 412 | for (const auto& refEntry : stringRefs) { |
| 413 | refEntry.second->index = refEntry.first.getIndex(); |
| 414 | } |
| 415 | |
| 416 | // Write the XML header. |
| 417 | const size_t beforeXmlTreeIndex = outBuffer->size(); |
| 418 | android::ResXMLTree_header* header = outBuffer->nextBlock<android::ResXMLTree_header>(); |
| 419 | header->header.type = android::RES_XML_TYPE; |
| 420 | header->header.headerSize = sizeof(*header); |
| 421 | |
| 422 | // Write the array of resource IDs, indexed by StringPool order. |
| 423 | const size_t beforeResIdMapIndex = outBuffer->size(); |
| 424 | android::ResChunk_header* resIdMapChunk = outBuffer->nextBlock<android::ResChunk_header>(); |
| 425 | resIdMapChunk->type = android::RES_XML_RESOURCE_MAP_TYPE; |
| 426 | resIdMapChunk->headerSize = sizeof(*resIdMapChunk); |
| 427 | for (const auto& str : pool) { |
| 428 | ResourceId id { str->context.priority }; |
| 429 | if (!id.isValid()) { |
| 430 | // When we see the first non-resource ID, |
| 431 | // we're done. |
| 432 | break; |
| 433 | } |
| 434 | |
| 435 | uint32_t* flatId = outBuffer->nextBlock<uint32_t>(); |
| 436 | *flatId = id.id; |
| 437 | } |
| 438 | resIdMapChunk->size = outBuffer->size() - beforeResIdMapIndex; |
| 439 | |
| 440 | // Flatten the StringPool. |
| 441 | StringPool::flattenUtf8(outBuffer, pool); |
| 442 | |
| 443 | // Move the temporary BigBuffer into outBuffer-> |
| 444 | outBuffer->appendBuffer(std::move(out)); |
| 445 | |
| 446 | header->header.size = outBuffer->size() - beforeXmlTreeIndex; |
| 447 | |
| 448 | if (smallestStrippedAttributeSdk == std::numeric_limits<size_t>::max()) { |
| 449 | // Nothing was stripped |
| 450 | return 0u; |
| 451 | } |
| 452 | return smallestStrippedAttributeSdk; |
| 453 | } |
| 454 | |
| 455 | } // namespace aapt |