TableFlattener.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 "ConfigDescription.h" 19#include "Logger.h" 20#include "ResourceTable.h" 21#include "ResourceTypeExtensions.h" 22#include "ResourceValues.h" 23#include "StringPool.h" 24#include "TableFlattener.h" 25#include "Util.h" 26 27#include <algorithm> 28#include <androidfw/ResourceTypes.h> 29#include <sstream> 30 31namespace aapt { 32 33struct FlatEntry { 34 const ResourceEntry* entry; 35 const Value* value; 36 uint32_t entryKey; 37 uint32_t sourcePathKey; 38 uint32_t sourceLine; 39}; 40 41/** 42 * Visitor that knows how to encode Map values. 43 */ 44class MapFlattener : public ConstValueVisitor { 45public: 46 MapFlattener(BigBuffer* out, const FlatEntry& flatEntry, SymbolEntryVector* symbols) : 47 mOut(out), mSymbols(symbols) { 48 mMap = mOut->nextBlock<android::ResTable_map_entry>(); 49 mMap->key.index = flatEntry.entryKey; 50 mMap->flags = android::ResTable_entry::FLAG_COMPLEX; 51 if (flatEntry.entry->publicStatus.isPublic) { 52 mMap->flags |= android::ResTable_entry::FLAG_PUBLIC; 53 } 54 if (flatEntry.value->isWeak()) { 55 mMap->flags |= android::ResTable_entry::FLAG_WEAK; 56 } 57 58 ResTable_entry_source* sourceBlock = mOut->nextBlock<ResTable_entry_source>(); 59 sourceBlock->pathIndex = flatEntry.sourcePathKey; 60 sourceBlock->line = flatEntry.sourceLine; 61 62 mMap->size = sizeof(*mMap) + sizeof(*sourceBlock); 63 } 64 65 void flattenParent(const Reference& ref) { 66 if (!ref.id.isValid()) { 67 mSymbols->push_back({ 68 ResourceNameRef(ref.name), 69 (mOut->size() - mMap->size) + sizeof(*mMap) - sizeof(android::ResTable_entry) 70 }); 71 } 72 mMap->parent.ident = ref.id.id; 73 } 74 75 void flattenEntry(const Reference& key, const Item& value) { 76 mMap->count++; 77 78 android::ResTable_map* outMapEntry = mOut->nextBlock<android::ResTable_map>(); 79 80 // Write the key. 81 if (!Res_INTERNALID(key.id.id) && !key.id.isValid()) { 82 assert(key.name.isValid()); 83 mSymbols->push_back(std::make_pair(ResourceNameRef(key.name), 84 mOut->size() - sizeof(*outMapEntry))); 85 } 86 outMapEntry->name.ident = key.id.id; 87 88 // Write the value. 89 value.flatten(outMapEntry->value); 90 91 if (outMapEntry->value.data == 0x0) { 92 visitFunc<Reference>(value, [&](const Reference& reference) { 93 mSymbols->push_back(std::make_pair(ResourceNameRef(reference.name), 94 mOut->size() - sizeof(outMapEntry->value.data))); 95 }); 96 } 97 outMapEntry->value.size = sizeof(outMapEntry->value); 98 } 99 100 void flattenValueOnly(const Item& value) { 101 mMap->count++; 102 103 android::ResTable_map* outMapEntry = mOut->nextBlock<android::ResTable_map>(); 104 105 // Write the value. 106 value.flatten(outMapEntry->value); 107 108 if (outMapEntry->value.data == 0x0) { 109 visitFunc<Reference>(value, [&](const Reference& reference) { 110 mSymbols->push_back(std::make_pair(ResourceNameRef(reference.name), 111 mOut->size() - sizeof(outMapEntry->value.data))); 112 }); 113 } 114 outMapEntry->value.size = sizeof(outMapEntry->value); 115 } 116 117 static bool compareStyleEntries(const Style::Entry* lhs, const Style::Entry* rhs) { 118 return lhs->key.id < rhs->key.id; 119 } 120 121 void visit(const Style& style, ValueVisitorArgs&) override { 122 if (style.parent.name.isValid()) { 123 flattenParent(style.parent); 124 } 125 126 // First sort the entries by ID. 127 std::vector<const Style::Entry*> sortedEntries; 128 for (const auto& styleEntry : style.entries) { 129 auto iter = std::lower_bound(sortedEntries.begin(), sortedEntries.end(), 130 &styleEntry, compareStyleEntries); 131 sortedEntries.insert(iter, &styleEntry); 132 } 133 134 for (const Style::Entry* styleEntry : sortedEntries) { 135 flattenEntry(styleEntry->key, *styleEntry->value); 136 } 137 } 138 139 void visit(const Attribute& attr, ValueVisitorArgs&) override { 140 android::Res_value tempVal; 141 tempVal.dataType = android::Res_value::TYPE_INT_DEC; 142 tempVal.data = attr.typeMask; 143 flattenEntry(Reference(ResourceId{android::ResTable_map::ATTR_TYPE}), 144 BinaryPrimitive(tempVal)); 145 146 for (const auto& symbol : attr.symbols) { 147 tempVal.data = symbol.value; 148 flattenEntry(symbol.symbol, BinaryPrimitive(tempVal)); 149 } 150 } 151 152 void visit(const Styleable& styleable, ValueVisitorArgs&) override { 153 for (const auto& attr : styleable.entries) { 154 flattenEntry(attr, BinaryPrimitive(android::Res_value{})); 155 } 156 } 157 158 void visit(const Array& array, ValueVisitorArgs&) override { 159 for (const auto& item : array.items) { 160 flattenValueOnly(*item); 161 } 162 } 163 164 void visit(const Plural& plural, ValueVisitorArgs&) override { 165 const size_t count = plural.values.size(); 166 for (size_t i = 0; i < count; i++) { 167 if (!plural.values[i]) { 168 continue; 169 } 170 171 ResourceId q; 172 switch (i) { 173 case Plural::Zero: 174 q.id = android::ResTable_map::ATTR_ZERO; 175 break; 176 177 case Plural::One: 178 q.id = android::ResTable_map::ATTR_ONE; 179 break; 180 181 case Plural::Two: 182 q.id = android::ResTable_map::ATTR_TWO; 183 break; 184 185 case Plural::Few: 186 q.id = android::ResTable_map::ATTR_FEW; 187 break; 188 189 case Plural::Many: 190 q.id = android::ResTable_map::ATTR_MANY; 191 break; 192 193 case Plural::Other: 194 q.id = android::ResTable_map::ATTR_OTHER; 195 break; 196 197 default: 198 assert(false); 199 break; 200 } 201 202 flattenEntry(Reference(q), *plural.values[i]); 203 } 204 } 205 206private: 207 BigBuffer* mOut; 208 SymbolEntryVector* mSymbols; 209 android::ResTable_map_entry* mMap; 210}; 211 212/** 213 * Flattens a value, with special handling for References. 214 */ 215struct ValueFlattener : ConstValueVisitor { 216 ValueFlattener(BigBuffer* out, SymbolEntryVector* symbols) : 217 result(false), mOut(out), mOutValue(nullptr), mSymbols(symbols) { 218 mOutValue = mOut->nextBlock<android::Res_value>(); 219 } 220 221 virtual void visit(const Reference& ref, ValueVisitorArgs& a) override { 222 visitItem(ref, a); 223 if (mOutValue->data == 0x0) { 224 mSymbols->push_back({ 225 ResourceNameRef(ref.name), 226 mOut->size() - sizeof(mOutValue->data)}); 227 } 228 } 229 230 virtual void visitItem(const Item& item, ValueVisitorArgs&) override { 231 result = item.flatten(*mOutValue); 232 mOutValue->res0 = 0; 233 mOutValue->size = sizeof(*mOutValue); 234 } 235 236 bool result; 237 238private: 239 BigBuffer* mOut; 240 android::Res_value* mOutValue; 241 SymbolEntryVector* mSymbols; 242}; 243 244TableFlattener::TableFlattener(Options options) 245: mOptions(options) { 246} 247 248bool TableFlattener::flattenValue(BigBuffer* out, const FlatEntry& flatEntry, 249 SymbolEntryVector* symbols) { 250 if (flatEntry.value->isItem()) { 251 android::ResTable_entry* entry = out->nextBlock<android::ResTable_entry>(); 252 253 if (flatEntry.entry->publicStatus.isPublic) { 254 entry->flags |= android::ResTable_entry::FLAG_PUBLIC; 255 } 256 257 if (flatEntry.value->isWeak()) { 258 entry->flags |= android::ResTable_entry::FLAG_WEAK; 259 } 260 261 entry->key.index = flatEntry.entryKey; 262 entry->size = sizeof(*entry); 263 264 if (mOptions.useExtendedChunks) { 265 // Write the extra source block. This will be ignored by 266 // the Android runtime. 267 ResTable_entry_source* sourceBlock = out->nextBlock<ResTable_entry_source>(); 268 sourceBlock->pathIndex = flatEntry.sourcePathKey; 269 sourceBlock->line = flatEntry.sourceLine; 270 entry->size += sizeof(*sourceBlock); 271 } 272 273 const Item* item = static_cast<const Item*>(flatEntry.value); 274 ValueFlattener flattener(out, symbols); 275 item->accept(flattener, {}); 276 return flattener.result; 277 } 278 279 MapFlattener flattener(out, flatEntry, symbols); 280 flatEntry.value->accept(flattener, {}); 281 return true; 282} 283 284bool TableFlattener::flatten(BigBuffer* out, const ResourceTable& table) { 285 const size_t beginning = out->size(); 286 287 if (table.getPackage().size() == 0) { 288 Logger::error() 289 << "ResourceTable has no package name." 290 << std::endl; 291 return false; 292 } 293 294 if (table.getPackageId() == ResourceTable::kUnsetPackageId) { 295 Logger::error() 296 << "ResourceTable has no package ID set." 297 << std::endl; 298 return false; 299 } 300 301 SymbolEntryVector symbolEntries; 302 303 StringPool typePool; 304 StringPool keyPool; 305 StringPool sourcePool; 306 307 // Sort the types by their IDs. They will be inserted into the StringPool 308 // in this order. 309 std::vector<ResourceTableType*> sortedTypes; 310 for (const auto& type : table) { 311 if (type->type == ResourceType::kStyleable && !mOptions.useExtendedChunks) { 312 continue; 313 } 314 315 auto iter = std::lower_bound(std::begin(sortedTypes), std::end(sortedTypes), type.get(), 316 [](const ResourceTableType* lhs, const ResourceTableType* rhs) -> bool { 317 return lhs->typeId < rhs->typeId; 318 }); 319 sortedTypes.insert(iter, type.get()); 320 } 321 322 BigBuffer typeBlock(1024); 323 size_t expectedTypeId = 1; 324 for (const ResourceTableType* type : sortedTypes) { 325 if (type->typeId == ResourceTableType::kUnsetTypeId 326 || type->typeId == 0) { 327 Logger::error() 328 << "resource type '" 329 << type->type 330 << "' from package '" 331 << table.getPackage() 332 << "' has no ID." 333 << std::endl; 334 return false; 335 } 336 337 // If there is a gap in the type IDs, fill in the StringPool 338 // with empty values until we reach the ID we expect. 339 while (type->typeId > expectedTypeId) { 340 std::u16string typeName(u"?"); 341 typeName += expectedTypeId; 342 typePool.makeRef(typeName); 343 expectedTypeId++; 344 } 345 expectedTypeId++; 346 typePool.makeRef(toString(type->type)); 347 348 android::ResTable_typeSpec* spec = typeBlock.nextBlock<android::ResTable_typeSpec>(); 349 spec->header.type = android::RES_TABLE_TYPE_SPEC_TYPE; 350 spec->header.headerSize = sizeof(*spec); 351 spec->header.size = spec->header.headerSize + (type->entries.size() * sizeof(uint32_t)); 352 spec->id = type->typeId; 353 spec->entryCount = type->entries.size(); 354 355 if (type->entries.empty()) { 356 continue; 357 } 358 359 // Reserve space for the masks of each resource in this type. These 360 // show for which configuration axis the resource changes. 361 uint32_t* configMasks = typeBlock.nextBlock<uint32_t>(type->entries.size()); 362 363 // Sort the entries by entry ID and write their configuration masks. 364 std::vector<ResourceEntry*> entries; 365 const size_t entryCount = type->entries.size(); 366 for (size_t entryIndex = 0; entryIndex < entryCount; entryIndex++) { 367 const auto& entry = type->entries[entryIndex]; 368 369 if (entry->entryId == ResourceEntry::kUnsetEntryId) { 370 Logger::error() 371 << "resource '" 372 << ResourceName{ table.getPackage(), type->type, entry->name } 373 << "' has no ID." 374 << std::endl; 375 return false; 376 } 377 378 auto iter = std::lower_bound(std::begin(entries), std::end(entries), entry.get(), 379 [](const ResourceEntry* lhs, const ResourceEntry* rhs) -> bool { 380 return lhs->entryId < rhs->entryId; 381 }); 382 entries.insert(iter, entry.get()); 383 384 // Populate the config masks for this entry. 385 if (entry->publicStatus.isPublic) { 386 configMasks[entry->entryId] |= android::ResTable_typeSpec::SPEC_PUBLIC; 387 } 388 389 const size_t configCount = entry->values.size(); 390 for (size_t i = 0; i < configCount; i++) { 391 const ConfigDescription& config = entry->values[i].config; 392 for (size_t j = i + 1; j < configCount; j++) { 393 configMasks[entry->entryId] |= config.diff(entry->values[j].config); 394 } 395 } 396 } 397 398 const size_t beforePublicHeader = typeBlock.size(); 399 Public_header* publicHeader = nullptr; 400 if (mOptions.useExtendedChunks) { 401 publicHeader = typeBlock.nextBlock<Public_header>(); 402 publicHeader->header.type = RES_TABLE_PUBLIC_TYPE; 403 publicHeader->header.headerSize = sizeof(*publicHeader); 404 publicHeader->typeId = type->typeId; 405 } 406 407 // The binary resource table lists resource entries for each configuration. 408 // We store them inverted, where a resource entry lists the values for each 409 // configuration available. Here we reverse this to match the binary table. 410 std::map<ConfigDescription, std::vector<FlatEntry>> data; 411 for (const ResourceEntry* entry : entries) { 412 size_t keyIndex = keyPool.makeRef(entry->name).getIndex(); 413 414 if (keyIndex > std::numeric_limits<uint32_t>::max()) { 415 Logger::error() 416 << "resource key string pool exceeded max size." 417 << std::endl; 418 return false; 419 } 420 421 if (publicHeader && entry->publicStatus.isPublic) { 422 // Write the public status of this entry. 423 Public_entry* publicEntry = typeBlock.nextBlock<Public_entry>(); 424 publicEntry->entryId = static_cast<uint32_t>(entry->entryId); 425 publicEntry->key.index = static_cast<uint32_t>(keyIndex); 426 publicEntry->source.index = static_cast<uint32_t>(sourcePool.makeRef( 427 util::utf8ToUtf16(entry->publicStatus.source.path)).getIndex()); 428 publicEntry->sourceLine = static_cast<uint32_t>(entry->publicStatus.source.line); 429 publicHeader->count += 1; 430 } 431 432 for (const auto& configValue : entry->values) { 433 data[configValue.config].push_back(FlatEntry{ 434 entry, 435 configValue.value.get(), 436 static_cast<uint32_t>(keyIndex), 437 static_cast<uint32_t>(sourcePool.makeRef(util::utf8ToUtf16( 438 configValue.source.path)).getIndex()), 439 static_cast<uint32_t>(configValue.source.line) 440 }); 441 } 442 } 443 444 if (publicHeader) { 445 typeBlock.align4(); 446 publicHeader->header.size = 447 static_cast<uint32_t>(typeBlock.size() - beforePublicHeader); 448 } 449 450 // Begin flattening a configuration for the current type. 451 for (const auto& entry : data) { 452 const size_t typeHeaderStart = typeBlock.size(); 453 android::ResTable_type* typeHeader = typeBlock.nextBlock<android::ResTable_type>(); 454 typeHeader->header.type = android::RES_TABLE_TYPE_TYPE; 455 typeHeader->header.headerSize = sizeof(*typeHeader); 456 typeHeader->id = type->typeId; 457 typeHeader->entryCount = type->entries.size(); 458 typeHeader->entriesStart = typeHeader->header.headerSize 459 + (sizeof(uint32_t) * type->entries.size()); 460 typeHeader->config = entry.first; 461 462 uint32_t* indices = typeBlock.nextBlock<uint32_t>(type->entries.size()); 463 memset(indices, 0xff, type->entries.size() * sizeof(uint32_t)); 464 465 const size_t entryStart = typeBlock.size(); 466 for (const FlatEntry& flatEntry : entry.second) { 467 assert(flatEntry.entry->entryId < type->entries.size()); 468 indices[flatEntry.entry->entryId] = typeBlock.size() - entryStart; 469 if (!flattenValue(&typeBlock, flatEntry, &symbolEntries)) { 470 Logger::error() 471 << "failed to flatten resource '" 472 << ResourceNameRef { 473 table.getPackage(), type->type, flatEntry.entry->name } 474 << "' for configuration '" 475 << entry.first 476 << "'." 477 << std::endl; 478 return false; 479 } 480 } 481 482 typeBlock.align4(); 483 typeHeader->header.size = typeBlock.size() - typeHeaderStart; 484 } 485 } 486 487 const size_t beforeTable = out->size(); 488 android::ResTable_header* header = out->nextBlock<android::ResTable_header>(); 489 header->header.type = android::RES_TABLE_TYPE; 490 header->header.headerSize = sizeof(*header); 491 header->packageCount = 1; 492 493 SymbolTable_entry* symbolEntryData = nullptr; 494 if (!symbolEntries.empty() && mOptions.useExtendedChunks) { 495 const size_t beforeSymbolTable = out->size(); 496 StringPool symbolPool; 497 SymbolTable_header* symbolHeader = out->nextBlock<SymbolTable_header>(); 498 symbolHeader->header.type = RES_TABLE_SYMBOL_TABLE_TYPE; 499 symbolHeader->header.headerSize = sizeof(*symbolHeader); 500 symbolHeader->count = symbolEntries.size(); 501 502 symbolEntryData = out->nextBlock<SymbolTable_entry>(symbolHeader->count); 503 504 size_t i = 0; 505 for (const auto& entry : symbolEntries) { 506 symbolEntryData[i].offset = entry.second; 507 StringPool::Ref ref = symbolPool.makeRef( 508 entry.first.package.toString() + u":" + 509 toString(entry.first.type).toString() + u"/" + 510 entry.first.entry.toString()); 511 symbolEntryData[i].stringIndex = ref.getIndex(); 512 i++; 513 } 514 515 StringPool::flattenUtf8(out, symbolPool); 516 out->align4(); 517 symbolHeader->header.size = out->size() - beforeSymbolTable; 518 } 519 520 if (sourcePool.size() > 0 && mOptions.useExtendedChunks) { 521 const size_t beforeSourcePool = out->size(); 522 android::ResChunk_header* sourceHeader = out->nextBlock<android::ResChunk_header>(); 523 sourceHeader->type = RES_TABLE_SOURCE_POOL_TYPE; 524 sourceHeader->headerSize = sizeof(*sourceHeader); 525 StringPool::flattenUtf8(out, sourcePool); 526 out->align4(); 527 sourceHeader->size = out->size() - beforeSourcePool; 528 } 529 530 StringPool::flattenUtf8(out, table.getValueStringPool()); 531 532 const size_t beforePackageIndex = out->size(); 533 android::ResTable_package* package = out->nextBlock<android::ResTable_package>(); 534 package->header.type = android::RES_TABLE_PACKAGE_TYPE; 535 package->header.headerSize = sizeof(*package); 536 537 if (table.getPackageId() > std::numeric_limits<uint8_t>::max()) { 538 Logger::error() 539 << "package ID 0x'" 540 << std::hex << table.getPackageId() << std::dec 541 << "' is invalid." 542 << std::endl; 543 return false; 544 } 545 package->id = table.getPackageId(); 546 547 if (table.getPackage().size() >= sizeof(package->name) / sizeof(package->name[0])) { 548 Logger::error() 549 << "package name '" 550 << table.getPackage() 551 << "' is too long." 552 << std::endl; 553 return false; 554 } 555 memcpy(package->name, reinterpret_cast<const uint16_t*>(table.getPackage().data()), 556 table.getPackage().length() * sizeof(char16_t)); 557 package->name[table.getPackage().length()] = 0; 558 559 package->typeStrings = package->header.headerSize; 560 StringPool::flattenUtf16(out, typePool); 561 package->keyStrings = out->size() - beforePackageIndex; 562 StringPool::flattenUtf16(out, keyPool); 563 564 if (symbolEntryData != nullptr) { 565 for (size_t i = 0; i < symbolEntries.size(); i++) { 566 symbolEntryData[i].offset += out->size() - beginning; 567 } 568 } 569 570 out->appendBuffer(std::move(typeBlock)); 571 572 package->header.size = out->size() - beforePackageIndex; 573 header->header.size = out->size() - beforeTable; 574 return true; 575} 576 577} // namespace aapt 578