blob: 427ab18567bdcb14468257e6c6c92eafa4cfa07a [file] [log] [blame]
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001/*
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 "ResourceTable.h"
18#include "ResourceValues.h"
19#include "ValueVisitor.h"
20
21#include "flatten/ChunkWriter.h"
22#include "flatten/ResourceTypeExtensions.h"
23#include "flatten/TableFlattener.h"
24#include "util/BigBuffer.h"
25
26#include <type_traits>
27#include <numeric>
28#include <utils/misc.h>
29
30using namespace android;
31
32namespace aapt {
33
34namespace {
35
36template <typename T>
37static bool cmpIds(const T* a, const T* b) {
38 return a->id.value() < b->id.value();
39}
40
41static void strcpy16_htod(uint16_t* dst, size_t len, const StringPiece16& src) {
42 if (len == 0) {
43 return;
44 }
45
46 size_t i;
47 const char16_t* srcData = src.data();
48 for (i = 0; i < len - 1 && i < src.size(); i++) {
49 dst[i] = util::hostToDevice16((uint16_t) srcData[i]);
50 }
51 dst[i] = 0;
52}
53
54struct FlatEntry {
55 ResourceEntry* entry;
56 Value* value;
57 uint32_t entryKey;
58 uint32_t sourcePathKey;
59 uint32_t sourceLine;
60};
61
62struct SymbolWriter {
63 struct Entry {
64 StringPool::Ref name;
65 size_t offset;
66 };
67
68 StringPool pool;
69 std::vector<Entry> symbols;
70
71 void addSymbol(const ResourceNameRef& name, size_t offset) {
72 symbols.push_back(Entry{ pool.makeRef(name.package.toString() + u":" +
73 toString(name.type).toString() + u"/" +
74 name.entry.toString()), offset });
75 }
76};
77
78struct MapFlattenVisitor : public RawValueVisitor {
79 using RawValueVisitor::visit;
80
81 SymbolWriter* mSymbols;
82 FlatEntry* mEntry;
83 BigBuffer* mBuffer;
84 size_t mEntryCount = 0;
85 Maybe<uint32_t> mParentIdent;
86 Maybe<ResourceNameRef> mParentName;
87
88 MapFlattenVisitor(SymbolWriter* symbols, FlatEntry* entry, BigBuffer* buffer) :
89 mSymbols(symbols), mEntry(entry), mBuffer(buffer) {
90 }
91
92 void flattenKey(Reference* key, ResTable_map* outEntry) {
93 if (!key->id) {
94 assert(key->name && "reference must have a name");
95
96 outEntry->name.ident = util::hostToDevice32(0);
97 mSymbols->addSymbol(key->name.value(), (mBuffer->size() - sizeof(ResTable_map)) +
98 offsetof(ResTable_map, name));
99 } else {
100 outEntry->name.ident = util::hostToDevice32(key->id.value().id);
101 }
102 }
103
104 void flattenValue(Item* value, ResTable_map* outEntry) {
105 if (Reference* ref = valueCast<Reference>(value)) {
106 if (!ref->id) {
107 assert(ref->name && "reference must have a name");
108
109 mSymbols->addSymbol(ref->name.value(), (mBuffer->size() - sizeof(ResTable_map)) +
110 offsetof(ResTable_map, value) + offsetof(Res_value, data));
111 }
112 }
113
114 bool result = value->flatten(&outEntry->value);
115 assert(result && "flatten failed");
116 }
117
118 void flattenEntry(Reference* key, Item* value) {
119 ResTable_map* outEntry = mBuffer->nextBlock<ResTable_map>();
120 flattenKey(key, outEntry);
121 flattenValue(value, outEntry);
122 outEntry->value.size = util::hostToDevice16(sizeof(outEntry->value));
123 mEntryCount++;
124 }
125
126 void visit(Attribute* attr) override {
127 {
128 Reference key(ResourceId{ ResTable_map::ATTR_TYPE });
129 BinaryPrimitive val(Res_value::TYPE_INT_DEC, attr->typeMask);
130 flattenEntry(&key, &val);
131 }
132
133 for (Attribute::Symbol& s : attr->symbols) {
134 BinaryPrimitive val(Res_value::TYPE_INT_DEC, s.value);
135 flattenEntry(&s.symbol, &val);
136 }
137 }
138
139 static bool cmpStyleEntries(const Style::Entry& a, const Style::Entry& b) {
140 if (a.key.id) {
141 if (b.key.id) {
142 return a.key.id.value() < b.key.id.value();
143 }
144 return true;
145 } else if (!b.key.id) {
146 return a.key.name.value() < b.key.name.value();
147 }
148 return false;
149 }
150
151 void visit(Style* style) override {
152 if (style->parent) {
153 if (!style->parent.value().id) {
154 assert(style->parent.value().name && "reference must have a name");
155 mParentName = style->parent.value().name;
156 } else {
157 mParentIdent = style->parent.value().id.value().id;
158 }
159 }
160
161 // Sort the style.
162 std::sort(style->entries.begin(), style->entries.end(), cmpStyleEntries);
163
164 for (Style::Entry& entry : style->entries) {
165 flattenEntry(&entry.key, entry.value.get());
166 }
167 }
168
169 void visit(Styleable* styleable) override {
170 for (auto& attrRef : styleable->entries) {
171 BinaryPrimitive val(Res_value{});
172 flattenEntry(&attrRef, &val);
173 }
174 }
175
176 void visit(Array* array) override {
177 for (auto& item : array->items) {
178 ResTable_map* outEntry = mBuffer->nextBlock<ResTable_map>();
179 flattenValue(item.get(), outEntry);
180 outEntry->value.size = util::hostToDevice16(sizeof(outEntry->value));
181 mEntryCount++;
182 }
183 }
184
185 void visit(Plural* plural) override {
186 const size_t count = plural->values.size();
187 for (size_t i = 0; i < count; i++) {
188 if (!plural->values[i]) {
189 continue;
190 }
191
192 ResourceId q;
193 switch (i) {
194 case Plural::Zero:
195 q.id = android::ResTable_map::ATTR_ZERO;
196 break;
197
198 case Plural::One:
199 q.id = android::ResTable_map::ATTR_ONE;
200 break;
201
202 case Plural::Two:
203 q.id = android::ResTable_map::ATTR_TWO;
204 break;
205
206 case Plural::Few:
207 q.id = android::ResTable_map::ATTR_FEW;
208 break;
209
210 case Plural::Many:
211 q.id = android::ResTable_map::ATTR_MANY;
212 break;
213
214 case Plural::Other:
215 q.id = android::ResTable_map::ATTR_OTHER;
216 break;
217
218 default:
219 assert(false);
220 break;
221 }
222
223 Reference key(q);
224 flattenEntry(&key, plural->values[i].get());
225 }
226 }
227};
228
229struct PackageFlattener {
230 IDiagnostics* mDiag;
231 TableFlattenerOptions mOptions;
232 ResourceTable* mTable;
233 ResourceTablePackage* mPackage;
234 SymbolWriter mSymbols;
235 StringPool mTypePool;
236 StringPool mKeyPool;
237 StringPool mSourcePool;
238
239 template <typename T>
240 T* writeEntry(FlatEntry* entry, BigBuffer* buffer) {
241 static_assert(std::is_same<ResTable_entry, T>::value ||
242 std::is_same<ResTable_entry_ext, T>::value,
243 "T must be ResTable_entry or ResTable_entry_ext");
244
245 T* result = buffer->nextBlock<T>();
246 ResTable_entry* outEntry = (ResTable_entry*)(result);
247 if (entry->entry->publicStatus.isPublic) {
248 outEntry->flags |= ResTable_entry::FLAG_PUBLIC;
249 }
250
251 if (entry->value->isWeak()) {
252 outEntry->flags |= ResTable_entry::FLAG_WEAK;
253 }
254
255 if (!entry->value->isItem()) {
256 outEntry->flags |= ResTable_entry::FLAG_COMPLEX;
257 }
258
259 outEntry->key.index = util::hostToDevice32(entry->entryKey);
260 outEntry->size = sizeof(T);
261
262 if (mOptions.useExtendedChunks) {
263 // Write the extra source block. This will be ignored by the Android runtime.
264 ResTable_entry_source* sourceBlock = buffer->nextBlock<ResTable_entry_source>();
265 sourceBlock->pathIndex = util::hostToDevice32(entry->sourcePathKey);
266 sourceBlock->line = util::hostToDevice32(entry->sourceLine);
267 outEntry->size += sizeof(*sourceBlock);
268 }
269
270 outEntry->flags = util::hostToDevice16(outEntry->flags);
271 outEntry->size = util::hostToDevice16(outEntry->size);
272 return result;
273 }
274
275 bool flattenValue(FlatEntry* entry, BigBuffer* buffer) {
276 if (entry->value->isItem()) {
277 writeEntry<ResTable_entry>(entry, buffer);
278 if (Reference* ref = valueCast<Reference>(entry->value)) {
279 if (!ref->id) {
280 assert(ref->name && "reference must have at least a name");
281 mSymbols.addSymbol(ref->name.value(),
282 buffer->size() + offsetof(Res_value, data));
283 }
284 }
285 Res_value* outValue = buffer->nextBlock<Res_value>();
286 bool result = static_cast<Item*>(entry->value)->flatten(outValue);
287 assert(result && "flatten failed");
288 outValue->size = util::hostToDevice16(sizeof(*outValue));
289 } else {
290 const size_t beforeEntry = buffer->size();
291 ResTable_entry_ext* outEntry = writeEntry<ResTable_entry_ext>(entry, buffer);
292 MapFlattenVisitor visitor(&mSymbols, entry, buffer);
293 entry->value->accept(&visitor);
294 outEntry->count = util::hostToDevice32(visitor.mEntryCount);
295 if (visitor.mParentName) {
296 mSymbols.addSymbol(visitor.mParentName.value(),
297 beforeEntry + offsetof(ResTable_entry_ext, parent));
298 } else if (visitor.mParentIdent) {
299 outEntry->parent.ident = util::hostToDevice32(visitor.mParentIdent.value());
300 }
301 }
302 return true;
303 }
304
305 bool flattenConfig(const ResourceTableType* type, const ConfigDescription& config,
306 std::vector<FlatEntry>* entries, BigBuffer* buffer) {
307 ChunkWriter typeWriter(buffer);
308 ResTable_type* typeHeader = typeWriter.startChunk<ResTable_type>(RES_TABLE_TYPE_TYPE);
309 typeHeader->id = type->id.value();
310 typeHeader->config = config;
311 typeHeader->config.swapHtoD();
312
313 auto maxAccum = [](uint32_t max, const std::unique_ptr<ResourceEntry>& a) -> uint32_t {
314 return std::max(max, (uint32_t) a->id.value());
315 };
316
317 // Find the largest entry ID. That is how many entries we will have.
318 const uint32_t entryCount =
319 std::accumulate(type->entries.begin(), type->entries.end(), 0, maxAccum) + 1;
320
321 typeHeader->entryCount = util::hostToDevice32(entryCount);
322 uint32_t* indices = typeWriter.nextBlock<uint32_t>(entryCount);
323
324 assert((size_t) entryCount <= std::numeric_limits<uint16_t>::max() + 1);
325 memset(indices, 0xff, entryCount * sizeof(uint32_t));
326
327 typeHeader->entriesStart = util::hostToDevice32(typeWriter.size());
328
329 const size_t entryStart = typeWriter.getBuffer()->size();
330 for (FlatEntry& flatEntry : *entries) {
331 assert(flatEntry.entry->id.value() < entryCount);
332 indices[flatEntry.entry->id.value()] = util::hostToDevice32(
333 typeWriter.getBuffer()->size() - entryStart);
334 if (!flattenValue(&flatEntry, typeWriter.getBuffer())) {
335 mDiag->error(DiagMessage()
336 << "failed to flatten resource '"
337 << ResourceNameRef(mPackage->name, type->type, flatEntry.entry->name)
338 << "' for configuration '" << config << "'");
339 return false;
340 }
341 }
342 typeWriter.finish();
343 return true;
344 }
345
346 std::vector<ResourceTableType*> collectAndSortTypes() {
347 std::vector<ResourceTableType*> sortedTypes;
348 for (auto& type : mPackage->types) {
349 if (type->type == ResourceType::kStyleable && !mOptions.useExtendedChunks) {
350 // Styleables aren't real Resource Types, they are represented in the R.java
351 // file.
352 continue;
353 }
354
355 assert(type->id && "type must have an ID set");
356
357 sortedTypes.push_back(type.get());
358 }
359 std::sort(sortedTypes.begin(), sortedTypes.end(), cmpIds<ResourceTableType>);
360 return sortedTypes;
361 }
362
363 std::vector<ResourceEntry*> collectAndSortEntries(ResourceTableType* type) {
364 // Sort the entries by entry ID.
365 std::vector<ResourceEntry*> sortedEntries;
366 for (auto& entry : type->entries) {
367 assert(entry->id && "entry must have an ID set");
368 sortedEntries.push_back(entry.get());
369 }
370 std::sort(sortedEntries.begin(), sortedEntries.end(), cmpIds<ResourceEntry>);
371 return sortedEntries;
372 }
373
374 bool flattenTypeSpec(ResourceTableType* type, std::vector<ResourceEntry*>* sortedEntries,
375 BigBuffer* buffer) {
376 ChunkWriter typeSpecWriter(buffer);
377 ResTable_typeSpec* specHeader = typeSpecWriter.startChunk<ResTable_typeSpec>(
378 RES_TABLE_TYPE_SPEC_TYPE);
379 specHeader->id = type->id.value();
380
381 if (sortedEntries->empty()) {
382 typeSpecWriter.finish();
383 return true;
384 }
385
386 // We can't just take the size of the vector. There may be holes in the entry ID space.
387 // Since the entries are sorted by ID, the last one will be the biggest.
388 const size_t numEntries = sortedEntries->back()->id.value() + 1;
389
390 specHeader->entryCount = util::hostToDevice32(numEntries);
391
392 // Reserve space for the masks of each resource in this type. These
393 // show for which configuration axis the resource changes.
394 uint32_t* configMasks = typeSpecWriter.nextBlock<uint32_t>(numEntries);
395
396 const size_t actualNumEntries = sortedEntries->size();
397 for (size_t entryIndex = 0; entryIndex < actualNumEntries; entryIndex++) {
398 ResourceEntry* entry = sortedEntries->at(entryIndex);
399
400 // Populate the config masks for this entry.
401
402 if (entry->publicStatus.isPublic) {
403 configMasks[entry->id.value()] |=
404 util::hostToDevice32(ResTable_typeSpec::SPEC_PUBLIC);
405 }
406
407 const size_t configCount = entry->values.size();
408 for (size_t i = 0; i < configCount; i++) {
409 const ConfigDescription& config = entry->values[i].config;
410 for (size_t j = i + 1; j < configCount; j++) {
411 configMasks[entry->id.value()] |= util::hostToDevice32(
412 config.diff(entry->values[j].config));
413 }
414 }
415 }
416 typeSpecWriter.finish();
417 return true;
418 }
419
420 bool flattenPublic(ResourceTableType* type, std::vector<ResourceEntry*>* sortedEntries,
421 BigBuffer* buffer) {
422 ChunkWriter publicWriter(buffer);
423 Public_header* publicHeader = publicWriter.startChunk<Public_header>(RES_TABLE_PUBLIC_TYPE);
424 publicHeader->typeId = type->id.value();
425
426 for (ResourceEntry* entry : *sortedEntries) {
427 if (entry->publicStatus.isPublic) {
428 // Write the public status of this entry.
429 Public_entry* publicEntry = publicWriter.nextBlock<Public_entry>();
430 publicEntry->entryId = util::hostToDevice32(entry->id.value());
431 publicEntry->key.index = util::hostToDevice32(mKeyPool.makeRef(
432 entry->name).getIndex());
433 publicEntry->source.index = util::hostToDevice32(mSourcePool.makeRef(
434 util::utf8ToUtf16(entry->publicStatus.source.path)).getIndex());
435 if (entry->publicStatus.source.line) {
436 publicEntry->sourceLine = util::hostToDevice32(
437 entry->publicStatus.source.line.value());
438 }
439
440 // Don't hostToDevice until the last step.
441 publicHeader->count += 1;
442 }
443 }
444
445 publicHeader->count = util::hostToDevice32(publicHeader->count);
446 publicWriter.finish();
447 return true;
448 }
449
450 bool flattenTypes(BigBuffer* buffer) {
451 // Sort the types by their IDs. They will be inserted into the StringPool in this order.
452 std::vector<ResourceTableType*> sortedTypes = collectAndSortTypes();
453
454 size_t expectedTypeId = 1;
455 for (ResourceTableType* type : sortedTypes) {
456 // If there is a gap in the type IDs, fill in the StringPool
457 // with empty values until we reach the ID we expect.
458 while (type->id.value() > expectedTypeId) {
459 std::u16string typeName(u"?");
460 typeName += expectedTypeId;
461 mTypePool.makeRef(typeName);
462 expectedTypeId++;
463 }
464 expectedTypeId++;
465 mTypePool.makeRef(toString(type->type));
466
467 std::vector<ResourceEntry*> sortedEntries = collectAndSortEntries(type);
468
469 if (!flattenTypeSpec(type, &sortedEntries, buffer)) {
470 return false;
471 }
472
473 if (mOptions.useExtendedChunks) {
474 if (!flattenPublic(type, &sortedEntries, buffer)) {
475 return false;
476 }
477 }
478
479 // The binary resource table lists resource entries for each configuration.
480 // We store them inverted, where a resource entry lists the values for each
481 // configuration available. Here we reverse this to match the binary table.
482 std::map<ConfigDescription, std::vector<FlatEntry>> configToEntryListMap;
483 for (ResourceEntry* entry : sortedEntries) {
484 const size_t keyIndex = mKeyPool.makeRef(entry->name).getIndex();
485
486 // Group values by configuration.
487 for (auto& configValue : entry->values) {
488 configToEntryListMap[configValue.config].push_back(FlatEntry{
489 entry, configValue.value.get(), (uint32_t) keyIndex,
490 (uint32_t)(mSourcePool.makeRef(util::utf8ToUtf16(
491 configValue.source.path)).getIndex()),
492 (uint32_t)(configValue.source.line
493 ? configValue.source.line.value() : 0)
494 });
495 }
496 }
497
498 // Flatten a configuration value.
499 for (auto& entry : configToEntryListMap) {
500 if (!flattenConfig(type, entry.first, &entry.second, buffer)) {
501 return false;
502 }
503 }
504 }
505 return true;
506 }
507
508 bool flattenPackage(BigBuffer* buffer) {
509 // We must do this before writing the resources, since the string pool IDs may change.
510 mTable->stringPool.sort([](const StringPool::Entry& a, const StringPool::Entry& b) -> bool {
511 int diff = a.context.priority - b.context.priority;
512 if (diff < 0) return true;
513 if (diff > 0) return false;
514 diff = a.context.config.compare(b.context.config);
515 if (diff < 0) return true;
516 if (diff > 0) return false;
517 return a.value < b.value;
518 });
519 mTable->stringPool.prune();
520
521 const size_t beginningIndex = buffer->size();
522
523 BigBuffer typeBuffer(1024);
524 if (!flattenTypes(&typeBuffer)) {
525 return false;
526 }
527
528 ChunkWriter tableWriter(buffer);
529 ResTable_header* tableHeader = tableWriter.startChunk<ResTable_header>(RES_TABLE_TYPE);
530 tableHeader->packageCount = util::hostToDevice32(1);
531
532 SymbolTable_entry* symbolEntryData = nullptr;
533 if (mOptions.useExtendedChunks && !mSymbols.symbols.empty()) {
534 // Sort the offsets so we can scan them linearly.
535 std::sort(mSymbols.symbols.begin(), mSymbols.symbols.end(),
536 [](const SymbolWriter::Entry& a, const SymbolWriter::Entry& b) -> bool {
537 return a.offset < b.offset;
538 });
539
540 ChunkWriter symbolWriter(tableWriter.getBuffer());
541 SymbolTable_header* symbolHeader = symbolWriter.startChunk<SymbolTable_header>(
542 RES_TABLE_SYMBOL_TABLE_TYPE);
543 symbolHeader->count = util::hostToDevice32(mSymbols.symbols.size());
544
545 symbolEntryData = symbolWriter.nextBlock<SymbolTable_entry>(mSymbols.symbols.size());
546 StringPool::flattenUtf8(symbolWriter.getBuffer(), mSymbols.pool);
547 symbolWriter.finish();
548 }
549
550 if (mOptions.useExtendedChunks && mSourcePool.size() > 0) {
551 // Write out source pool.
552 ChunkWriter srcWriter(tableWriter.getBuffer());
553 srcWriter.startChunk<ResChunk_header>(RES_TABLE_SOURCE_POOL_TYPE);
554 StringPool::flattenUtf8(srcWriter.getBuffer(), mSourcePool);
555 srcWriter.finish();
556 }
557
558 StringPool::flattenUtf8(tableWriter.getBuffer(), mTable->stringPool);
559
560 ChunkWriter pkgWriter(tableWriter.getBuffer());
561 ResTable_package* pkgHeader = pkgWriter.startChunk<ResTable_package>(
562 RES_TABLE_PACKAGE_TYPE);
563 pkgHeader->id = util::hostToDevice32(mPackage->id.value());
564
565 if (mPackage->name.size() >= NELEM(pkgHeader->name)) {
566 mDiag->error(DiagMessage() <<
567 "package name '" << mPackage->name << "' is too long");
568 return false;
569 }
570
571 strcpy16_htod(pkgHeader->name, NELEM(pkgHeader->name), mPackage->name);
572
573 pkgHeader->typeStrings = util::hostToDevice32(pkgWriter.size());
574 StringPool::flattenUtf16(pkgWriter.getBuffer(), mTypePool);
575
576 pkgHeader->keyStrings = util::hostToDevice32(pkgWriter.size());
577 StringPool::flattenUtf16(pkgWriter.getBuffer(), mKeyPool);
578
579 // Actually write out the symbol entries if we have symbols.
580 if (symbolEntryData) {
581 for (auto& entry : mSymbols.symbols) {
582 symbolEntryData->stringIndex = util::hostToDevice32(entry.name.getIndex());
583
584 // The symbols were all calculated with the typeBuffer offset. We need to
585 // add the beginning of the output buffer.
586 symbolEntryData->offset = util::hostToDevice32(
587 (pkgWriter.getBuffer()->size() - beginningIndex) + entry.offset);
588
589 symbolEntryData++;
590 }
591 }
592
593 // Write out the types and entries.
594 pkgWriter.getBuffer()->appendBuffer(std::move(typeBuffer));
595
596 pkgWriter.finish();
597 tableWriter.finish();
598 return true;
599 }
600};
601
602} // namespace
603
604bool TableFlattener::consume(IAaptContext* context, ResourceTable* table) {
605 for (auto& package : table->packages) {
606 // Only support flattening one package. Since the StringPool is shared between packages
607 // in ResourceTable, we must fail if other packages are present, since their strings
608 // will be included in the final ResourceTable.
609 if (context->getCompilationPackage() != package->name) {
610 context->getDiagnostics()->error(DiagMessage()
611 << "resources for package '" << package->name
612 << "' can't be flattened when compiling package '"
613 << context->getCompilationPackage() << "'");
614 return false;
615 }
616
617 if (!package->id || package->id.value() != context->getPackageId()) {
618 context->getDiagnostics()->error(DiagMessage()
619 << "package '" << package->name << "' must have "
620 << "package id "
621 << std::hex << context->getPackageId() << std::dec);
622 return false;
623 }
624
625 PackageFlattener flattener = {
626 context->getDiagnostics(),
627 mOptions,
628 table,
629 package.get()
630 };
631
632 if (!flattener.flattenPackage(mBuffer)) {
633 return false;
634 }
635 return true;
636 }
637
638 context->getDiagnostics()->error(DiagMessage()
639 << "compilation package '" << context->getCompilationPackage()
640 << "' not found");
641 return false;
642}
643
644} // namespace aapt