blob: 406e5065340cd881933ea4a136737560c6ed3cf3 [file] [log] [blame]
Adam Lesinski6f6ceb72014-11-14 14:48:12 -08001/*
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
Adam Lesinskica2fc352015-04-03 12:08:26 -070027#include <algorithm>
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080028#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:
Adam Lesinskid5c4f872015-04-21 13:56:10 -070046 MapFlattener(BigBuffer* out, const FlatEntry& flatEntry, SymbolEntryVector* symbols) :
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080047 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()) {
Adam Lesinskid5c4f872015-04-21 13:56:10 -070067 mSymbols->push_back({
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080068 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()) {
Adam Lesinskid5c4f872015-04-21 13:56:10 -070082 mSymbols->push_back(std::make_pair(ResourceNameRef(key.name),
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080083 mOut->size() - sizeof(*outMapEntry)));
84 }
85 outMapEntry->name.ident = key.id.id;
86
87 // Write the value.
88 value.flatten(outMapEntry->value);
89
90 if (outMapEntry->value.data == 0x0) {
91 visitFunc<Reference>(value, [&](const Reference& reference) {
Adam Lesinskid5c4f872015-04-21 13:56:10 -070092 mSymbols->push_back(std::make_pair(ResourceNameRef(reference.name),
Adam Lesinski6f6ceb72014-11-14 14:48:12 -080093 mOut->size() - sizeof(outMapEntry->value.data)));
94 });
95 }
96 outMapEntry->value.size = sizeof(outMapEntry->value);
97 }
98
99 static bool compareStyleEntries(const Style::Entry* lhs, const Style::Entry* rhs) {
100 return lhs->key.id < rhs->key.id;
101 }
102
103 void visit(const Style& style, ValueVisitorArgs&) override {
104 if (style.parent.name.isValid()) {
105 flattenParent(style.parent);
106 }
107
108 // First sort the entries by ID.
109 std::vector<const Style::Entry*> sortedEntries;
110 for (const auto& styleEntry : style.entries) {
111 auto iter = std::lower_bound(sortedEntries.begin(), sortedEntries.end(),
112 &styleEntry, compareStyleEntries);
113 sortedEntries.insert(iter, &styleEntry);
114 }
115
116 for (const Style::Entry* styleEntry : sortedEntries) {
117 flattenEntry(styleEntry->key, *styleEntry->value);
118 }
119 }
120
121 void visit(const Attribute& attr, ValueVisitorArgs&) override {
122 android::Res_value tempVal;
123 tempVal.dataType = android::Res_value::TYPE_INT_DEC;
124 tempVal.data = attr.typeMask;
125 flattenEntry(Reference(ResourceId{android::ResTable_map::ATTR_TYPE}),
126 BinaryPrimitive(tempVal));
127
128 for (const auto& symbol : attr.symbols) {
129 tempVal.data = symbol.value;
130 flattenEntry(symbol.symbol, BinaryPrimitive(tempVal));
131 }
132 }
133
134 void visit(const Styleable& styleable, ValueVisitorArgs&) override {
135 for (const auto& attr : styleable.entries) {
136 flattenEntry(attr, BinaryPrimitive(android::Res_value{}));
137 }
138 }
139
140 void visit(const Array& array, ValueVisitorArgs&) override {
141 for (const auto& item : array.items) {
142 flattenEntry({}, *item);
143 }
144 }
145
146 void visit(const Plural& plural, ValueVisitorArgs&) override {
147 const size_t count = plural.values.size();
148 for (size_t i = 0; i < count; i++) {
149 if (!plural.values[i]) {
150 continue;
151 }
152
153 ResourceId q;
154 switch (i) {
155 case Plural::Zero:
156 q.id = android::ResTable_map::ATTR_ZERO;
157 break;
158
159 case Plural::One:
160 q.id = android::ResTable_map::ATTR_ONE;
161 break;
162
163 case Plural::Two:
164 q.id = android::ResTable_map::ATTR_TWO;
165 break;
166
167 case Plural::Few:
168 q.id = android::ResTable_map::ATTR_FEW;
169 break;
170
171 case Plural::Many:
172 q.id = android::ResTable_map::ATTR_MANY;
173 break;
174
175 case Plural::Other:
176 q.id = android::ResTable_map::ATTR_OTHER;
177 break;
178
179 default:
180 assert(false);
181 break;
182 }
183
184 flattenEntry(Reference(q), *plural.values[i]);
185 }
186 }
187
188private:
189 BigBuffer* mOut;
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700190 SymbolEntryVector* mSymbols;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800191 android::ResTable_map_entry* mMap;
192};
193
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700194/**
195 * Flattens a value, with special handling for References.
196 */
197struct ValueFlattener : ConstValueVisitor {
198 ValueFlattener(BigBuffer* out, SymbolEntryVector* symbols) :
199 result(false), mOut(out), mOutValue(nullptr), mSymbols(symbols) {
200 mOutValue = mOut->nextBlock<android::Res_value>();
201 }
202
203 virtual void visit(const Reference& ref, ValueVisitorArgs& a) override {
204 visitItem(ref, a);
205 if (mOutValue->data == 0x0) {
206 mSymbols->push_back({
207 ResourceNameRef(ref.name),
208 mOut->size() - sizeof(mOutValue->data)});
209 }
210 }
211
212 virtual void visitItem(const Item& item, ValueVisitorArgs&) override {
213 result = item.flatten(*mOutValue);
Adam Lesinski24aad162015-04-24 19:19:30 -0700214 mOutValue->res0 = 0;
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700215 mOutValue->size = sizeof(*mOutValue);
216 }
217
218 bool result;
219
220private:
221 BigBuffer* mOut;
222 android::Res_value* mOutValue;
223 SymbolEntryVector* mSymbols;
224};
225
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800226TableFlattener::TableFlattener(Options options)
227: mOptions(options) {
228}
229
230bool TableFlattener::flattenValue(BigBuffer* out, const FlatEntry& flatEntry,
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700231 SymbolEntryVector* symbols) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800232 if (flatEntry.value.isItem()) {
233 android::ResTable_entry* entry = out->nextBlock<android::ResTable_entry>();
234
235 if (flatEntry.entry.publicStatus.isPublic) {
236 entry->flags |= android::ResTable_entry::FLAG_PUBLIC;
237 }
238
239 if (flatEntry.value.isWeak()) {
240 entry->flags |= android::ResTable_entry::FLAG_WEAK;
241 }
242
243 entry->key.index = flatEntry.entryKey;
244 entry->size = sizeof(*entry);
245
246 if (mOptions.useExtendedChunks) {
247 // Write the extra source block. This will be ignored by
248 // the Android runtime.
249 ResTable_entry_source* sourceBlock = out->nextBlock<ResTable_entry_source>();
250 sourceBlock->pathIndex = flatEntry.sourcePathKey;
251 sourceBlock->line = flatEntry.sourceLine;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800252 entry->size += sizeof(*sourceBlock);
253 }
254
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700255 const Item* item = static_cast<const Item*>(&flatEntry.value);
256 ValueFlattener flattener(out, symbols);
257 item->accept(flattener, {});
258 return flattener.result;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800259 }
260
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700261 MapFlattener flattener(out, flatEntry, symbols);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800262 flatEntry.value.accept(flattener, {});
263 return true;
264}
265
266bool TableFlattener::flatten(BigBuffer* out, const ResourceTable& table) {
267 const size_t beginning = out->size();
268
269 if (table.getPackage().size() == 0) {
270 Logger::error()
271 << "ResourceTable has no package name."
272 << std::endl;
273 return false;
274 }
275
276 if (table.getPackageId() == ResourceTable::kUnsetPackageId) {
277 Logger::error()
278 << "ResourceTable has no package ID set."
279 << std::endl;
280 return false;
281 }
282
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700283 SymbolEntryVector symbolEntries;
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800284
285 StringPool typePool;
286 StringPool keyPool;
287 StringPool sourcePool;
288
289 // Sort the types by their IDs. They will be inserted into the StringPool
290 // in this order.
291 std::vector<ResourceTableType*> sortedTypes;
292 for (const auto& type : table) {
293 if (type->type == ResourceType::kStyleable && !mOptions.useExtendedChunks) {
294 continue;
295 }
296
297 auto iter = std::lower_bound(std::begin(sortedTypes), std::end(sortedTypes), type.get(),
298 [](const ResourceTableType* lhs, const ResourceTableType* rhs) -> bool {
299 return lhs->typeId < rhs->typeId;
300 });
301 sortedTypes.insert(iter, type.get());
302 }
303
304 BigBuffer typeBlock(1024);
305 size_t expectedTypeId = 1;
306 for (const ResourceTableType* type : sortedTypes) {
307 if (type->typeId == ResourceTableType::kUnsetTypeId
308 || type->typeId == 0) {
309 Logger::error()
310 << "resource type '"
311 << type->type
312 << "' from package '"
313 << table.getPackage()
314 << "' has no ID."
315 << std::endl;
316 return false;
317 }
318
319 // If there is a gap in the type IDs, fill in the StringPool
320 // with empty values until we reach the ID we expect.
321 while (type->typeId > expectedTypeId) {
322 std::u16string typeName(u"?");
323 typeName += expectedTypeId;
324 typePool.makeRef(typeName);
325 expectedTypeId++;
326 }
327 expectedTypeId++;
328 typePool.makeRef(toString(type->type));
329
330 android::ResTable_typeSpec* spec = typeBlock.nextBlock<android::ResTable_typeSpec>();
331 spec->header.type = android::RES_TABLE_TYPE_SPEC_TYPE;
332 spec->header.headerSize = sizeof(*spec);
333 spec->header.size = spec->header.headerSize + (type->entries.size() * sizeof(uint32_t));
334 spec->id = type->typeId;
335 spec->entryCount = type->entries.size();
336
337 // Reserve space for the masks of each resource in this type. These
338 // show for which configuration axis the resource changes.
339 uint32_t* configMasks = typeBlock.nextBlock<uint32_t>(type->entries.size());
340
341 // Sort the entries by entry ID and write their configuration masks.
342 std::vector<ResourceEntry*> entries;
343 const size_t entryCount = type->entries.size();
344 for (size_t entryIndex = 0; entryIndex < entryCount; entryIndex++) {
345 const auto& entry = type->entries[entryIndex];
346
347 if (entry->entryId == ResourceEntry::kUnsetEntryId) {
348 Logger::error()
349 << "resource '"
350 << ResourceName{ table.getPackage(), type->type, entry->name }
351 << "' has no ID."
352 << std::endl;
353 return false;
354 }
355
356 auto iter = std::lower_bound(std::begin(entries), std::end(entries), entry.get(),
357 [](const ResourceEntry* lhs, const ResourceEntry* rhs) -> bool {
358 return lhs->entryId < rhs->entryId;
359 });
360 entries.insert(iter, entry.get());
361
362 // Populate the config masks for this entry.
363 if (entry->publicStatus.isPublic) {
364 configMasks[entry->entryId] |= android::ResTable_typeSpec::SPEC_PUBLIC;
365 }
366
367 const size_t configCount = entry->values.size();
368 for (size_t i = 0; i < configCount; i++) {
369 const ConfigDescription& config = entry->values[i].config;
370 for (size_t j = i + 1; j < configCount; j++) {
371 configMasks[entry->entryId] |= config.diff(entry->values[j].config);
372 }
373 }
374 }
375
376 // The binary resource table lists resource entries for each configuration.
377 // We store them inverted, where a resource entry lists the values for each
378 // configuration available. Here we reverse this to match the binary table.
379 std::map<ConfigDescription, std::vector<FlatEntry>> data;
380 for (const ResourceEntry* entry : entries) {
381 size_t keyIndex = keyPool.makeRef(entry->name).getIndex();
382
383 if (keyIndex > std::numeric_limits<uint32_t>::max()) {
384 Logger::error()
385 << "resource key string pool exceeded max size."
386 << std::endl;
387 return false;
388 }
389
390 for (const auto& configValue : entry->values) {
391 data[configValue.config].push_back(FlatEntry{
392 *entry,
393 *configValue.value,
394 static_cast<uint32_t>(keyIndex),
395 static_cast<uint32_t>(sourcePool.makeRef(util::utf8ToUtf16(
396 configValue.source.path)).getIndex()),
397 static_cast<uint32_t>(configValue.source.line)
398 });
399 }
400 }
401
402 // Begin flattening a configuration for the current type.
403 for (const auto& entry : data) {
404 const size_t typeHeaderStart = typeBlock.size();
405 android::ResTable_type* typeHeader = typeBlock.nextBlock<android::ResTable_type>();
406 typeHeader->header.type = android::RES_TABLE_TYPE_TYPE;
407 typeHeader->header.headerSize = sizeof(*typeHeader);
408 typeHeader->id = type->typeId;
409 typeHeader->entryCount = type->entries.size();
410 typeHeader->entriesStart = typeHeader->header.headerSize
411 + (sizeof(uint32_t) * type->entries.size());
412 typeHeader->config = entry.first;
413
414 uint32_t* indices = typeBlock.nextBlock<uint32_t>(type->entries.size());
415 memset(indices, 0xff, type->entries.size() * sizeof(uint32_t));
416
417 const size_t entryStart = typeBlock.size();
418 for (const FlatEntry& flatEntry : entry.second) {
419 assert(flatEntry.entry.entryId < type->entries.size());
420 indices[flatEntry.entry.entryId] = typeBlock.size() - entryStart;
Adam Lesinskid5c4f872015-04-21 13:56:10 -0700421 if (!flattenValue(&typeBlock, flatEntry, &symbolEntries)) {
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800422 Logger::error()
423 << "failed to flatten resource '"
424 << ResourceNameRef {
425 table.getPackage(), type->type, flatEntry.entry.name }
426 << "' for configuration '"
427 << entry.first
428 << "'."
429 << std::endl;
430 return false;
431 }
432 }
433
434 typeBlock.align4();
435 typeHeader->header.size = typeBlock.size() - typeHeaderStart;
436 }
437 }
438
439 const size_t beforeTable = out->size();
440 android::ResTable_header* header = out->nextBlock<android::ResTable_header>();
441 header->header.type = android::RES_TABLE_TYPE;
442 header->header.headerSize = sizeof(*header);
443 header->packageCount = 1;
444
445 SymbolTable_entry* symbolEntryData = nullptr;
446 if (!symbolEntries.empty() && mOptions.useExtendedChunks) {
447 const size_t beforeSymbolTable = out->size();
448 StringPool symbolPool;
449 SymbolTable_header* symbolHeader = out->nextBlock<SymbolTable_header>();
450 symbolHeader->header.type = RES_TABLE_SYMBOL_TABLE_TYPE;
451 symbolHeader->header.headerSize = sizeof(*symbolHeader);
452 symbolHeader->count = symbolEntries.size();
453
454 symbolEntryData = out->nextBlock<SymbolTable_entry>(symbolHeader->count);
455
456 size_t i = 0;
457 for (const auto& entry : symbolEntries) {
458 symbolEntryData[i].offset = entry.second;
459 StringPool::Ref ref = symbolPool.makeRef(
460 entry.first.package.toString() + u":" +
461 toString(entry.first.type).toString() + u"/" +
462 entry.first.entry.toString());
463 symbolEntryData[i].stringIndex = ref.getIndex();
464 i++;
465 }
466
467 StringPool::flattenUtf8(out, symbolPool);
468 out->align4();
469 symbolHeader->header.size = out->size() - beforeSymbolTable;
470 }
471
472 if (sourcePool.size() > 0 && mOptions.useExtendedChunks) {
473 const size_t beforeSourcePool = out->size();
474 android::ResChunk_header* sourceHeader = out->nextBlock<android::ResChunk_header>();
475 sourceHeader->type = RES_TABLE_SOURCE_POOL_TYPE;
476 sourceHeader->headerSize = sizeof(*sourceHeader);
477 StringPool::flattenUtf8(out, sourcePool);
478 out->align4();
479 sourceHeader->size = out->size() - beforeSourcePool;
480 }
481
482 StringPool::flattenUtf8(out, table.getValueStringPool());
483
484 const size_t beforePackageIndex = out->size();
485 android::ResTable_package* package = out->nextBlock<android::ResTable_package>();
486 package->header.type = android::RES_TABLE_PACKAGE_TYPE;
487 package->header.headerSize = sizeof(*package);
488
489 if (table.getPackageId() > std::numeric_limits<uint8_t>::max()) {
490 Logger::error()
491 << "package ID 0x'"
492 << std::hex << table.getPackageId() << std::dec
493 << "' is invalid."
494 << std::endl;
495 return false;
496 }
497 package->id = table.getPackageId();
498
499 if (table.getPackage().size() >= sizeof(package->name) / sizeof(package->name[0])) {
500 Logger::error()
501 << "package name '"
502 << table.getPackage()
503 << "' is too long."
504 << std::endl;
505 return false;
506 }
507 memcpy(package->name, reinterpret_cast<const uint16_t*>(table.getPackage().data()),
508 table.getPackage().length() * sizeof(char16_t));
509 package->name[table.getPackage().length()] = 0;
510
511 package->typeStrings = package->header.headerSize;
Adam Lesinski24aad162015-04-24 19:19:30 -0700512 StringPool::flattenUtf16(out, typePool);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800513 package->keyStrings = out->size() - beforePackageIndex;
Adam Lesinski24aad162015-04-24 19:19:30 -0700514 StringPool::flattenUtf16(out, keyPool);
Adam Lesinski6f6ceb72014-11-14 14:48:12 -0800515
516 if (symbolEntryData != nullptr) {
517 for (size_t i = 0; i < symbolEntries.size(); i++) {
518 symbolEntryData[i].offset += out->size() - beginning;
519 }
520 }
521
522 out->appendBuffer(std::move(typeBlock));
523
524 package->header.size = out->size() - beforePackageIndex;
525 header->header.size = out->size() - beforeTable;
526 return true;
527}
528
529} // namespace aapt