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