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