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 { |
Adam Lesinski | 75f3a55 | 2015-06-03 14:54:23 -0700 | [diff] [blame] | 37 | namespace xml { |
Adam Lesinski | 6f6ceb7 | 2014-11-14 14:48:12 -0800 | [diff] [blame] | 38 | |
Adam Lesinski | 75f3a55 | 2015-06-03 14:54:23 -0700 | [diff] [blame] | 39 | constexpr uint32_t kLowPriority = 0xffffffffu; |
Adam Lesinski | 769de98 | 2015-04-10 19:43:55 -0700 | [diff] [blame] | 40 | |
Adam Lesinski | 75f3a55 | 2015-06-03 14:54:23 -0700 | [diff] [blame] | 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 { |
| 45 | XmlFlattener(BigBuffer* outBuffer, StringPool* pool, FlatStringRefList* stringRefs, |
| 46 | const std::u16string& defaultPackage) : |
| 47 | mOut(outBuffer), mPool(pool), mStringRefs(stringRefs), |
| 48 | mDefaultPackage(defaultPackage) { |
Adam Lesinski | 24aad16 | 2015-04-24 19:19:30 -0700 | [diff] [blame] | 49 | } |
| 50 | |
Adam Lesinski | 75f3a55 | 2015-06-03 14:54:23 -0700 | [diff] [blame] | 51 | // No copying. |
| 52 | XmlFlattener(const XmlFlattener&) = delete; |
| 53 | XmlFlattener& operator=(const XmlFlattener&) = delete; |
Adam Lesinski | 24aad16 | 2015-04-24 19:19:30 -0700 | [diff] [blame] | 54 | |
Adam Lesinski | 75f3a55 | 2015-06-03 14:54:23 -0700 | [diff] [blame] | 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()); |
Adam Lesinski | 6f6ceb7 | 2014-11-14 14:48:12 -0800 | [diff] [blame] | 76 | } |
| 77 | |
Adam Lesinski | 75f3a55 | 2015-06-03 14:54:23 -0700 | [diff] [blame] | 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 | |
| 153 | protected: |
| 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; |
Adam Lesinski | 24aad16 | 2015-04-24 19:19:30 -0700 | [diff] [blame] | 172 | } |
Adam Lesinski | 6f6ceb7 | 2014-11-14 14:48:12 -0800 | [diff] [blame] | 173 | } |
Adam Lesinski | 75f3a55 | 2015-06-03 14:54:23 -0700 | [diff] [blame] | 174 | return {}; |
Adam Lesinski | 6f6ceb7 | 2014-11-14 14:48:12 -0800 | [diff] [blame] | 175 | } |
| 176 | |
Adam Lesinski | 75f3a55 | 2015-06-03 14:54:23 -0700 | [diff] [blame] | 177 | const std::u16string& getDefaultPackage() const { |
| 178 | return mDefaultPackage; |
Adam Lesinski | 6f6ceb7 | 2014-11-14 14:48:12 -0800 | [diff] [blame] | 179 | } |
| 180 | |
Adam Lesinski | 75f3a55 | 2015-06-03 14:54:23 -0700 | [diff] [blame] | 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; |
Adam Lesinski | 24aad16 | 2015-04-24 19:19:30 -0700 | [diff] [blame] | 187 | |
| 188 | private: |
Adam Lesinski | 75f3a55 | 2015-06-03 14:54:23 -0700 | [diff] [blame] | 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; |
Adam Lesinski | 6f6ceb7 | 2014-11-14 14:48:12 -0800 | [diff] [blame] | 195 | }; |
| 196 | |
Adam Lesinski | 75f3a55 | 2015-06-03 14:54:23 -0700 | [diff] [blame] | 197 | /** |
| 198 | * Flattens XML, encoding the attributes as raw strings. This is used in the compile phase. |
| 199 | */ |
| 200 | struct 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 | } |
Adam Lesinski | 6f6ceb7 | 2014-11-14 14:48:12 -0800 | [diff] [blame] | 223 | }; |
| 224 | |
Adam Lesinski | 75f3a55 | 2015-06-03 14:54:23 -0700 | [diff] [blame] | 225 | struct AttributeToFlatten { |
| 226 | uint32_t resourceId = 0; |
| 227 | const Attribute* xmlAttr = nullptr; |
| 228 | const ::aapt::Attribute* resourceAttr = nullptr; |
| 229 | }; |
| 230 | |
| 231 | static bool lessAttributeId(const AttributeToFlatten& a, uint32_t id) { |
Adam Lesinski | 6f6ceb7 | 2014-11-14 14:48:12 -0800 | [diff] [blame] | 232 | return a.resourceId < id; |
| 233 | } |
| 234 | |
Adam Lesinski | 75f3a55 | 2015-06-03 14:54:23 -0700 | [diff] [blame] | 235 | /** |
| 236 | * Flattens XML, encoding the attributes as resources. |
| 237 | */ |
| 238 | struct 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 | |
| 374 | private: |
| 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 | }; |
Adam Lesinski | 6f6ceb7 | 2014-11-14 14:48:12 -0800 | [diff] [blame] | 458 | |
| 459 | /** |
Adam Lesinski | 75f3a55 | 2015-06-03 14:54:23 -0700 | [diff] [blame] | 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 |
Adam Lesinski | 6f6ceb7 | 2014-11-14 14:48:12 -0800 | [diff] [blame] | 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 | */ |
Adam Lesinski | 75f3a55 | 2015-06-03 14:54:23 -0700 | [diff] [blame] | 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 { |
Adam Lesinski | 6f6ceb7 | 2014-11-14 14:48:12 -0800 | [diff] [blame] | 470 | return a.context.priority < b.context.priority; |
| 471 | }); |
| 472 | |
| 473 | // Now we flatten the string pool references into the correct places. |
Adam Lesinski | 75f3a55 | 2015-06-03 14:54:23 -0700 | [diff] [blame] | 474 | for (const auto& refEntry : *stringRefs) { |
Adam Lesinski | 6f6ceb7 | 2014-11-14 14:48:12 -0800 | [diff] [blame] | 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 | |
Adam Lesinski | 330edcd | 2015-05-04 17:40:56 -0700 | [diff] [blame] | 484 | // Flatten the StringPool. |
Adam Lesinski | 75f3a55 | 2015-06-03 14:54:23 -0700 | [diff] [blame] | 485 | StringPool::flattenUtf16(outBuffer, *pool); |
Adam Lesinski | 330edcd | 2015-05-04 17:40:56 -0700 | [diff] [blame] | 486 | |
Adam Lesinski | 6f6ceb7 | 2014-11-14 14:48:12 -0800 | [diff] [blame] | 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); |
Adam Lesinski | 75f3a55 | 2015-06-03 14:54:23 -0700 | [diff] [blame] | 492 | for (const auto& str : *pool) { |
Adam Lesinski | 6f6ceb7 | 2014-11-14 14:48:12 -0800 | [diff] [blame] | 493 | ResourceId id { str->context.priority }; |
Adam Lesinski | 75f3a55 | 2015-06-03 14:54:23 -0700 | [diff] [blame] | 494 | if (id.id == kLowPriority || !id.isValid()) { |
Adam Lesinski | 6f6ceb7 | 2014-11-14 14:48:12 -0800 | [diff] [blame] | 495 | // When we see the first non-resource ID, |
| 496 | // we're done. |
| 497 | break; |
| 498 | } |
| 499 | |
Adam Lesinski | 75f3a55 | 2015-06-03 14:54:23 -0700 | [diff] [blame] | 500 | *outBuffer->nextBlock<uint32_t>() = id.id; |
Adam Lesinski | 6f6ceb7 | 2014-11-14 14:48:12 -0800 | [diff] [blame] | 501 | } |
| 502 | resIdMapChunk->size = outBuffer->size() - beforeResIdMapIndex; |
| 503 | |
Adam Lesinski | 330edcd | 2015-05-04 17:40:56 -0700 | [diff] [blame] | 504 | // Move the temporary BigBuffer into outBuffer. |
Adam Lesinski | 75f3a55 | 2015-06-03 14:54:23 -0700 | [diff] [blame] | 505 | outBuffer->appendBuffer(std::move(xmlTreeBuffer)); |
Adam Lesinski | 6f6ceb7 | 2014-11-14 14:48:12 -0800 | [diff] [blame] | 506 | header->header.size = outBuffer->size() - beforeXmlTreeIndex; |
Adam Lesinski | 6f6ceb7 | 2014-11-14 14:48:12 -0800 | [diff] [blame] | 507 | } |
| 508 | |
Adam Lesinski | 75f3a55 | 2015-06-03 14:54:23 -0700 | [diff] [blame] | 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 | |
| 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 |
Adam Lesinski | 6f6ceb7 | 2014-11-14 14:48:12 -0800 | [diff] [blame] | 574 | } // namespace aapt |