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
31 namespace aapt {
32
33 struct 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 */
44 class MapFlattener : public ConstValueVisitor {
45 public:
MapFlattener(BigBuffer * out,const FlatEntry & flatEntry,SymbolEntryVector * symbols)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
flattenParent(const Reference & ref)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
flattenEntry(const Reference & key,const Item & value)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.entry.empty());
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
flattenValueOnly(const Item & value)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
compareStyleEntries(const Style::Entry * lhs,const Style::Entry * rhs)117 static bool compareStyleEntries(const Style::Entry* lhs, const Style::Entry* rhs) {
118 return lhs->key.id < rhs->key.id;
119 }
120
visit(const Style & style,ValueVisitorArgs &)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
visit(const Attribute & attr,ValueVisitorArgs &)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
visit(const Styleable & styleable,ValueVisitorArgs &)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
visit(const Array & array,ValueVisitorArgs &)158 void visit(const Array& array, ValueVisitorArgs&) override {
159 for (const auto& item : array.items) {
160 flattenValueOnly(*item);
161 }
162 }
163
visit(const Plural & plural,ValueVisitorArgs &)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
206 private:
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 */
215 struct ValueFlattener : ConstValueVisitor {
ValueFlatteneraapt::ValueFlattener216 ValueFlattener(BigBuffer* out, SymbolEntryVector* symbols) :
217 result(false), mOut(out), mOutValue(nullptr), mSymbols(symbols) {
218 mOutValue = mOut->nextBlock<android::Res_value>();
219 }
220
visitaapt::ValueFlattener221 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
visitItemaapt::ValueFlattener230 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
238 private:
239 BigBuffer* mOut;
240 android::Res_value* mOutValue;
241 SymbolEntryVector* mSymbols;
242 };
243
TableFlattener(Options options)244 TableFlattener::TableFlattener(Options options)
245 : mOptions(options) {
246 }
247
flattenValue(BigBuffer * out,const FlatEntry & flatEntry,SymbolEntryVector * symbols)248 bool 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
flatten(BigBuffer * out,const ResourceTable & table)284 bool TableFlattener::flatten(BigBuffer* out, const ResourceTable& table) {
285 const size_t beginning = out->size();
286
287 if (table.getPackageId() == ResourceTable::kUnsetPackageId) {
288 Logger::error()
289 << "ResourceTable has no package ID set."
290 << std::endl;
291 return false;
292 }
293
294 SymbolEntryVector symbolEntries;
295
296 StringPool typePool;
297 StringPool keyPool;
298 StringPool sourcePool;
299
300 // Sort the types by their IDs. They will be inserted into the StringPool
301 // in this order.
302 std::vector<ResourceTableType*> sortedTypes;
303 for (const auto& type : table) {
304 if (type->type == ResourceType::kStyleable && !mOptions.useExtendedChunks) {
305 continue;
306 }
307
308 auto iter = std::lower_bound(std::begin(sortedTypes), std::end(sortedTypes), type.get(),
309 [](const ResourceTableType* lhs, const ResourceTableType* rhs) -> bool {
310 return lhs->typeId < rhs->typeId;
311 });
312 sortedTypes.insert(iter, type.get());
313 }
314
315 BigBuffer typeBlock(1024);
316 size_t expectedTypeId = 1;
317 for (const ResourceTableType* type : sortedTypes) {
318 if (type->typeId == ResourceTableType::kUnsetTypeId
319 || type->typeId == 0) {
320 Logger::error()
321 << "resource type '"
322 << type->type
323 << "' from package '"
324 << table.getPackage()
325 << "' has no ID."
326 << std::endl;
327 return false;
328 }
329
330 // If there is a gap in the type IDs, fill in the StringPool
331 // with empty values until we reach the ID we expect.
332 while (type->typeId > expectedTypeId) {
333 std::u16string typeName(u"?");
334 typeName += expectedTypeId;
335 typePool.makeRef(typeName);
336 expectedTypeId++;
337 }
338 expectedTypeId++;
339 typePool.makeRef(toString(type->type));
340
341 android::ResTable_typeSpec* spec = typeBlock.nextBlock<android::ResTable_typeSpec>();
342 spec->header.type = android::RES_TABLE_TYPE_SPEC_TYPE;
343 spec->header.headerSize = sizeof(*spec);
344 spec->header.size = spec->header.headerSize + (type->entries.size() * sizeof(uint32_t));
345 spec->id = type->typeId;
346 spec->entryCount = type->entries.size();
347
348 if (type->entries.empty()) {
349 continue;
350 }
351
352 // Reserve space for the masks of each resource in this type. These
353 // show for which configuration axis the resource changes.
354 uint32_t* configMasks = typeBlock.nextBlock<uint32_t>(type->entries.size());
355
356 // Sort the entries by entry ID and write their configuration masks.
357 std::vector<ResourceEntry*> entries;
358 const size_t entryCount = type->entries.size();
359 for (size_t entryIndex = 0; entryIndex < entryCount; entryIndex++) {
360 const auto& entry = type->entries[entryIndex];
361
362 if (entry->entryId == ResourceEntry::kUnsetEntryId) {
363 Logger::error()
364 << "resource '"
365 << ResourceName{ table.getPackage(), type->type, entry->name }
366 << "' has no ID."
367 << std::endl;
368 return false;
369 }
370
371 auto iter = std::lower_bound(std::begin(entries), std::end(entries), entry.get(),
372 [](const ResourceEntry* lhs, const ResourceEntry* rhs) -> bool {
373 return lhs->entryId < rhs->entryId;
374 });
375 entries.insert(iter, entry.get());
376
377 // Populate the config masks for this entry.
378 if (entry->publicStatus.isPublic) {
379 configMasks[entry->entryId] |= android::ResTable_typeSpec::SPEC_PUBLIC;
380 }
381
382 const size_t configCount = entry->values.size();
383 for (size_t i = 0; i < configCount; i++) {
384 const ConfigDescription& config = entry->values[i].config;
385 for (size_t j = i + 1; j < configCount; j++) {
386 configMasks[entry->entryId] |= config.diff(entry->values[j].config);
387 }
388 }
389 }
390
391 const size_t beforePublicHeader = typeBlock.size();
392 Public_header* publicHeader = nullptr;
393 if (mOptions.useExtendedChunks) {
394 publicHeader = typeBlock.nextBlock<Public_header>();
395 publicHeader->header.type = RES_TABLE_PUBLIC_TYPE;
396 publicHeader->header.headerSize = sizeof(*publicHeader);
397 publicHeader->typeId = type->typeId;
398 }
399
400 // The binary resource table lists resource entries for each configuration.
401 // We store them inverted, where a resource entry lists the values for each
402 // configuration available. Here we reverse this to match the binary table.
403 std::map<ConfigDescription, std::vector<FlatEntry>> data;
404 for (const ResourceEntry* entry : entries) {
405 size_t keyIndex = keyPool.makeRef(entry->name).getIndex();
406
407 if (keyIndex > std::numeric_limits<uint32_t>::max()) {
408 Logger::error()
409 << "resource key string pool exceeded max size."
410 << std::endl;
411 return false;
412 }
413
414 if (publicHeader && entry->publicStatus.isPublic) {
415 // Write the public status of this entry.
416 Public_entry* publicEntry = typeBlock.nextBlock<Public_entry>();
417 publicEntry->entryId = static_cast<uint32_t>(entry->entryId);
418 publicEntry->key.index = static_cast<uint32_t>(keyIndex);
419 publicEntry->source.index = static_cast<uint32_t>(sourcePool.makeRef(
420 util::utf8ToUtf16(entry->publicStatus.source.path)).getIndex());
421 publicEntry->sourceLine = static_cast<uint32_t>(entry->publicStatus.source.line);
422 publicHeader->count += 1;
423 }
424
425 for (const auto& configValue : entry->values) {
426 data[configValue.config].push_back(FlatEntry{
427 entry,
428 configValue.value.get(),
429 static_cast<uint32_t>(keyIndex),
430 static_cast<uint32_t>(sourcePool.makeRef(util::utf8ToUtf16(
431 configValue.source.path)).getIndex()),
432 static_cast<uint32_t>(configValue.source.line)
433 });
434 }
435 }
436
437 if (publicHeader) {
438 typeBlock.align4();
439 publicHeader->header.size =
440 static_cast<uint32_t>(typeBlock.size() - beforePublicHeader);
441 }
442
443 // Begin flattening a configuration for the current type.
444 for (const auto& entry : data) {
445 const size_t typeHeaderStart = typeBlock.size();
446 android::ResTable_type* typeHeader = typeBlock.nextBlock<android::ResTable_type>();
447 typeHeader->header.type = android::RES_TABLE_TYPE_TYPE;
448 typeHeader->header.headerSize = sizeof(*typeHeader);
449 typeHeader->id = type->typeId;
450 typeHeader->entryCount = type->entries.size();
451 typeHeader->entriesStart = typeHeader->header.headerSize
452 + (sizeof(uint32_t) * type->entries.size());
453 typeHeader->config = entry.first;
454
455 uint32_t* indices = typeBlock.nextBlock<uint32_t>(type->entries.size());
456 memset(indices, 0xff, type->entries.size() * sizeof(uint32_t));
457
458 const size_t entryStart = typeBlock.size();
459 for (const FlatEntry& flatEntry : entry.second) {
460 assert(flatEntry.entry->entryId < type->entries.size());
461 indices[flatEntry.entry->entryId] = typeBlock.size() - entryStart;
462 if (!flattenValue(&typeBlock, flatEntry, &symbolEntries)) {
463 Logger::error()
464 << "failed to flatten resource '"
465 << ResourceNameRef {
466 table.getPackage(), type->type, flatEntry.entry->name }
467 << "' for configuration '"
468 << entry.first
469 << "'."
470 << std::endl;
471 return false;
472 }
473 }
474
475 typeBlock.align4();
476 typeHeader->header.size = typeBlock.size() - typeHeaderStart;
477 }
478 }
479
480 const size_t beforeTable = out->size();
481 android::ResTable_header* header = out->nextBlock<android::ResTable_header>();
482 header->header.type = android::RES_TABLE_TYPE;
483 header->header.headerSize = sizeof(*header);
484 header->packageCount = 1;
485
486 SymbolTable_entry* symbolEntryData = nullptr;
487 if (!symbolEntries.empty() && mOptions.useExtendedChunks) {
488 const size_t beforeSymbolTable = out->size();
489 StringPool symbolPool;
490 SymbolTable_header* symbolHeader = out->nextBlock<SymbolTable_header>();
491 symbolHeader->header.type = RES_TABLE_SYMBOL_TABLE_TYPE;
492 symbolHeader->header.headerSize = sizeof(*symbolHeader);
493 symbolHeader->count = symbolEntries.size();
494
495 symbolEntryData = out->nextBlock<SymbolTable_entry>(symbolHeader->count);
496
497 size_t i = 0;
498 for (const auto& entry : symbolEntries) {
499 symbolEntryData[i].offset = entry.second;
500 StringPool::Ref ref = symbolPool.makeRef(
501 entry.first.package.toString() + u":" +
502 toString(entry.first.type).toString() + u"/" +
503 entry.first.entry.toString());
504 symbolEntryData[i].stringIndex = ref.getIndex();
505 i++;
506 }
507
508 StringPool::flattenUtf8(out, symbolPool);
509 out->align4();
510 symbolHeader->header.size = out->size() - beforeSymbolTable;
511 }
512
513 if (sourcePool.size() > 0 && mOptions.useExtendedChunks) {
514 const size_t beforeSourcePool = out->size();
515 android::ResChunk_header* sourceHeader = out->nextBlock<android::ResChunk_header>();
516 sourceHeader->type = RES_TABLE_SOURCE_POOL_TYPE;
517 sourceHeader->headerSize = sizeof(*sourceHeader);
518 StringPool::flattenUtf8(out, sourcePool);
519 out->align4();
520 sourceHeader->size = out->size() - beforeSourcePool;
521 }
522
523 StringPool::flattenUtf8(out, table.getValueStringPool());
524
525 const size_t beforePackageIndex = out->size();
526 android::ResTable_package* package = out->nextBlock<android::ResTable_package>();
527 package->header.type = android::RES_TABLE_PACKAGE_TYPE;
528 package->header.headerSize = sizeof(*package);
529
530 if (table.getPackageId() > std::numeric_limits<uint8_t>::max()) {
531 Logger::error()
532 << "package ID 0x'"
533 << std::hex << table.getPackageId() << std::dec
534 << "' is invalid."
535 << std::endl;
536 return false;
537 }
538 package->id = table.getPackageId();
539
540 if (table.getPackage().size() >= sizeof(package->name) / sizeof(package->name[0])) {
541 Logger::error()
542 << "package name '"
543 << table.getPackage()
544 << "' is too long."
545 << std::endl;
546 return false;
547 }
548 memcpy(package->name, reinterpret_cast<const uint16_t*>(table.getPackage().data()),
549 table.getPackage().length() * sizeof(char16_t));
550 package->name[table.getPackage().length()] = 0;
551
552 package->typeStrings = package->header.headerSize;
553 StringPool::flattenUtf16(out, typePool);
554 package->keyStrings = out->size() - beforePackageIndex;
555 StringPool::flattenUtf16(out, keyPool);
556
557 if (symbolEntryData != nullptr) {
558 for (size_t i = 0; i < symbolEntries.size(); i++) {
559 symbolEntryData[i].offset += out->size() - beginning;
560 }
561 }
562
563 out->appendBuffer(std::move(typeBlock));
564
565 package->header.size = out->size() - beforePackageIndex;
566 header->header.size = out->size() - beforeTable;
567 return true;
568 }
569
570 } // namespace aapt
571