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