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 "format/binary/TableFlattener.h"
18
19 #include "android-base/stringprintf.h"
20 #include "androidfw/TypeWrappers.h"
21
22 #include "ResChunkPullParser.h"
23 #include "ResourceUtils.h"
24 #include "SdkConstants.h"
25 #include "format/binary/BinaryResourceParser.h"
26 #include "test/Test.h"
27 #include "util/Util.h"
28
29 using namespace android;
30
31 using ::testing::Gt;
32 using ::testing::IsNull;
33 using ::testing::NotNull;
34
35 using PolicyFlags = android::ResTable_overlayable_policy_header::PolicyFlags;
36
37 namespace aapt {
38
39 class TableFlattenerTest : public ::testing::Test {
40 public:
SetUp()41 void SetUp() override {
42 context_ =
43 test::ContextBuilder().SetCompilationPackage("com.app.test").SetPackageId(0x7f).Build();
44 }
45
Flatten(IAaptContext * context,const TableFlattenerOptions & options,ResourceTable * table,std::string * out_content)46 ::testing::AssertionResult Flatten(IAaptContext* context, const TableFlattenerOptions& options,
47 ResourceTable* table, std::string* out_content) {
48 BigBuffer buffer(1024);
49 TableFlattener flattener(options, &buffer);
50 if (!flattener.Consume(context, table)) {
51 return ::testing::AssertionFailure() << "failed to flatten ResourceTable";
52 }
53 *out_content = buffer.to_string();
54 return ::testing::AssertionSuccess();
55 }
56
Flatten(IAaptContext * context,const TableFlattenerOptions & options,ResourceTable * table,ResTable * out_table)57 ::testing::AssertionResult Flatten(IAaptContext* context, const TableFlattenerOptions& options,
58 ResourceTable* table, ResTable* out_table) {
59 std::string content;
60 auto result = Flatten(context, options, table, &content);
61 if (!result) {
62 return result;
63 }
64
65 if (out_table->add(content.data(), content.size(), 1, true) != NO_ERROR) {
66 return ::testing::AssertionFailure() << "flattened ResTable is corrupt";
67 }
68 return ::testing::AssertionSuccess();
69 }
70
Flatten(IAaptContext * context,const TableFlattenerOptions & options,ResourceTable * table,ResourceTable * out_table)71 ::testing::AssertionResult Flatten(IAaptContext* context, const TableFlattenerOptions& options,
72 ResourceTable* table, ResourceTable* out_table) {
73 std::string content;
74 auto result = Flatten(context, options, table, &content);
75 if (!result) {
76 return result;
77 }
78
79 BinaryResourceParser parser(context->GetDiagnostics(), out_table, {}, content.data(),
80 content.size());
81 if (!parser.Parse()) {
82 return ::testing::AssertionFailure() << "flattened ResTable is corrupt";
83 }
84 return ::testing::AssertionSuccess();
85 }
86
Exists(ResTable * table,const StringPiece & expected_name,const ResourceId & expected_id,const ConfigDescription & expected_config,const uint8_t expected_data_type,const uint32_t expected_data,const uint32_t expected_spec_flags)87 ::testing::AssertionResult Exists(ResTable* table, const StringPiece& expected_name,
88 const ResourceId& expected_id,
89 const ConfigDescription& expected_config,
90 const uint8_t expected_data_type, const uint32_t expected_data,
91 const uint32_t expected_spec_flags) {
92 const ResourceName expected_res_name = test::ParseNameOrDie(expected_name);
93
94 table->setParameters(&expected_config);
95
96 ResTable_config config;
97 Res_value val;
98 uint32_t spec_flags;
99 if (table->getResource(expected_id.id, &val, false, 0, &spec_flags, &config) < 0) {
100 return ::testing::AssertionFailure() << "could not find resource with";
101 }
102
103 if (expected_data_type != val.dataType) {
104 return ::testing::AssertionFailure()
105 << "expected data type " << std::hex << (int)expected_data_type
106 << " but got data type " << (int)val.dataType << std::dec << " instead";
107 }
108
109 if (expected_data != val.data) {
110 return ::testing::AssertionFailure()
111 << "expected data " << std::hex << expected_data << " but got data " << val.data
112 << std::dec << " instead";
113 }
114
115 if (expected_spec_flags != spec_flags) {
116 return ::testing::AssertionFailure()
117 << "expected specFlags " << std::hex << expected_spec_flags << " but got specFlags "
118 << spec_flags << std::dec << " instead";
119 }
120
121 ResTable::resource_name actual_name;
122 if (!table->getResourceName(expected_id.id, false, &actual_name)) {
123 return ::testing::AssertionFailure() << "failed to find resource name";
124 }
125
126 Maybe<ResourceName> resName = ResourceUtils::ToResourceName(actual_name);
127 if (!resName) {
128 return ::testing::AssertionFailure()
129 << "expected name '" << expected_res_name << "' but got '"
130 << StringPiece16(actual_name.package, actual_name.packageLen) << ":"
131 << StringPiece16(actual_name.type, actual_name.typeLen) << "/"
132 << StringPiece16(actual_name.name, actual_name.nameLen) << "'";
133 }
134
135 ResourceName actual_res_name(resName.value());
136
137 if (expected_res_name.entry != actual_res_name.entry ||
138 expected_res_name.package != actual_res_name.package ||
139 expected_res_name.type != actual_res_name.type) {
140 return ::testing::AssertionFailure() << "expected resource '" << expected_res_name.to_string()
141 << "' but got '" << actual_res_name.to_string() << "'";
142 }
143
144 if (expected_config != config) {
145 return ::testing::AssertionFailure() << "expected config '" << expected_config
146 << "' but got '" << ConfigDescription(config) << "'";
147 }
148 return ::testing::AssertionSuccess();
149 }
150
151 protected:
152 std::unique_ptr<IAaptContext> context_;
153 };
154
TEST_F(TableFlattenerTest,FlattenFullyLinkedTable)155 TEST_F(TableFlattenerTest, FlattenFullyLinkedTable) {
156 std::unique_ptr<ResourceTable> table =
157 test::ResourceTableBuilder()
158 .AddSimple("com.app.test:id/one", ResourceId(0x7f020000))
159 .AddSimple("com.app.test:id/two", ResourceId(0x7f020001))
160 .AddValue("com.app.test:id/three", ResourceId(0x7f020002),
161 test::BuildReference("com.app.test:id/one", ResourceId(0x7f020000)))
162 .AddValue("com.app.test:integer/one", ResourceId(0x7f030000),
163 util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 1u))
164 .AddValue("com.app.test:integer/one", test::ParseConfigOrDie("v1"),
165 ResourceId(0x7f030000),
166 util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 2u))
167 .AddString("com.app.test:string/test", ResourceId(0x7f040000), "foo")
168 .AddString("com.app.test:layout/bar", ResourceId(0x7f050000), "res/layout/bar.xml")
169 .Build();
170
171 ResTable res_table;
172 ASSERT_TRUE(Flatten(context_.get(), {}, table.get(), &res_table));
173
174 EXPECT_TRUE(Exists(&res_table, "com.app.test:id/one", ResourceId(0x7f020000), {},
175 Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
176
177 EXPECT_TRUE(Exists(&res_table, "com.app.test:id/two", ResourceId(0x7f020001), {},
178 Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
179
180 EXPECT_TRUE(Exists(&res_table, "com.app.test:id/three", ResourceId(0x7f020002), {},
181 Res_value::TYPE_REFERENCE, 0x7f020000u, 0u));
182
183 EXPECT_TRUE(Exists(&res_table, "com.app.test:integer/one", ResourceId(0x7f030000), {},
184 Res_value::TYPE_INT_DEC, 1u, ResTable_config::CONFIG_VERSION));
185
186 EXPECT_TRUE(Exists(&res_table, "com.app.test:integer/one", ResourceId(0x7f030000),
187 test::ParseConfigOrDie("v1"), Res_value::TYPE_INT_DEC, 2u,
188 ResTable_config::CONFIG_VERSION));
189
190 std::u16string foo_str = u"foo";
191 auto idx = res_table.getTableStringBlock(0)->indexOfString(foo_str.data(), foo_str.size());
192 ASSERT_TRUE(idx.has_value());
193 EXPECT_TRUE(Exists(&res_table, "com.app.test:string/test", ResourceId(0x7f040000), {},
194 Res_value::TYPE_STRING, (uint32_t)*idx, 0u));
195
196 std::u16string bar_path = u"res/layout/bar.xml";
197 idx = res_table.getTableStringBlock(0)->indexOfString(bar_path.data(), bar_path.size());
198 ASSERT_TRUE(idx.has_value());
199 EXPECT_TRUE(Exists(&res_table, "com.app.test:layout/bar", ResourceId(0x7f050000), {},
200 Res_value::TYPE_STRING, (uint32_t)*idx, 0u));
201 }
202
TEST_F(TableFlattenerTest,FlattenEntriesWithGapsInIds)203 TEST_F(TableFlattenerTest, FlattenEntriesWithGapsInIds) {
204 std::unique_ptr<ResourceTable> table =
205 test::ResourceTableBuilder()
206 .AddSimple("com.app.test:id/one", ResourceId(0x7f020001))
207 .AddSimple("com.app.test:id/three", ResourceId(0x7f020003))
208 .Build();
209
210 ResTable res_table;
211 ASSERT_TRUE(Flatten(context_.get(), {}, table.get(), &res_table));
212
213 EXPECT_TRUE(Exists(&res_table, "com.app.test:id/one", ResourceId(0x7f020001), {},
214 Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
215 EXPECT_TRUE(Exists(&res_table, "com.app.test:id/three", ResourceId(0x7f020003), {},
216 Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
217 }
218
TEST_F(TableFlattenerTest,FlattenMinMaxAttributes)219 TEST_F(TableFlattenerTest, FlattenMinMaxAttributes) {
220 Attribute attr;
221 attr.type_mask = android::ResTable_map::TYPE_INTEGER;
222 attr.min_int = 10;
223 attr.max_int = 23;
224 std::unique_ptr<ResourceTable> table =
225 test::ResourceTableBuilder()
226 .AddValue("android:attr/foo", ResourceId(0x01010000), util::make_unique<Attribute>(attr))
227 .Build();
228
229 ResourceTable result;
230 ASSERT_TRUE(Flatten(context_.get(), {}, table.get(), &result));
231
232 Attribute* actual_attr = test::GetValue<Attribute>(&result, "android:attr/foo");
233 ASSERT_THAT(actual_attr, NotNull());
234 EXPECT_EQ(attr.IsWeak(), actual_attr->IsWeak());
235 EXPECT_EQ(attr.type_mask, actual_attr->type_mask);
236 EXPECT_EQ(attr.min_int, actual_attr->min_int);
237 EXPECT_EQ(attr.max_int, actual_attr->max_int);
238 }
239
TEST_F(TableFlattenerTest,FlattenArray)240 TEST_F(TableFlattenerTest, FlattenArray) {
241 auto array = util::make_unique<Array>();
242 array->elements.push_back(util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC),
243 1u));
244 array->elements.push_back(util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC),
245 2u));
246 std::unique_ptr<ResourceTable> table =
247 test::ResourceTableBuilder()
248 .AddValue("android:array/foo", ResourceId(0x01010000), std::move(array))
249 .Build();
250
251 std::string result;
252 ASSERT_TRUE(Flatten(context_.get(), {}, table.get(), &result));
253
254 // Parse the flattened resource table
255 ResChunkPullParser parser(result.data(), result.size());
256 ASSERT_TRUE(parser.IsGoodEvent(parser.Next()));
257 ASSERT_EQ(util::DeviceToHost16(parser.chunk()->type), RES_TABLE_TYPE);
258
259 // Retrieve the package of the entry
260 ResChunkPullParser table_parser(GetChunkData(parser.chunk()), GetChunkDataLen(parser.chunk()));
261 const ResChunk_header* package_chunk = nullptr;
262 while (table_parser.IsGoodEvent(table_parser.Next())) {
263 if (util::DeviceToHost16(table_parser.chunk()->type) == RES_TABLE_PACKAGE_TYPE) {
264 package_chunk = table_parser.chunk();
265 break;
266 }
267 }
268
269 // Retrieve the type that proceeds the array entry
270 ASSERT_NE(package_chunk, nullptr);
271 ResChunkPullParser package_parser(GetChunkData(table_parser.chunk()),
272 GetChunkDataLen(table_parser.chunk()));
273 const ResChunk_header* type_chunk = nullptr;
274 while (package_parser.IsGoodEvent(package_parser.Next())) {
275 if (util::DeviceToHost16(package_parser.chunk()->type) == RES_TABLE_TYPE_TYPE) {
276 type_chunk = package_parser.chunk();
277 break;
278 }
279 }
280
281 // Retrieve the array entry
282 ASSERT_NE(type_chunk, nullptr);
283 TypeVariant typeVariant((const ResTable_type*) type_chunk);
284 auto entry = (const ResTable_map_entry*)*typeVariant.beginEntries();
285 ASSERT_EQ(util::DeviceToHost16(entry->count), 2u);
286
287 // Check that the value and name of the array entries are correct
288 auto values = (const ResTable_map*)(((const uint8_t *)entry) + entry->size);
289 ASSERT_EQ(values->value.data, 1u);
290 ASSERT_EQ(values->name.ident, android::ResTable_map::ATTR_MIN);
291 ASSERT_EQ((values+1)->value.data, 2u);
292 ASSERT_EQ((values+1)->name.ident, android::ResTable_map::ATTR_MIN + 1);
293 }
294
BuildTableWithSparseEntries(IAaptContext * context,const ConfigDescription & sparse_config,float load)295 static std::unique_ptr<ResourceTable> BuildTableWithSparseEntries(
296 IAaptContext* context, const ConfigDescription& sparse_config, float load) {
297 std::unique_ptr<ResourceTable> table =
298 test::ResourceTableBuilder()
299 .Build();
300
301 // Add regular entries.
302 CloningValueTransformer cloner(&table->string_pool);
303 int stride = static_cast<int>(1.0f / load);
304 for (int i = 0; i < 100; i++) {
305 const ResourceName name = test::ParseNameOrDie(
306 base::StringPrintf("%s:string/foo_%d", context->GetCompilationPackage().data(), i));
307 const ResourceId resid(context->GetPackageId(), 0x02, static_cast<uint16_t>(i));
308 const auto value =
309 util::make_unique<BinaryPrimitive>(Res_value::TYPE_INT_DEC, static_cast<uint32_t>(i));
310 CHECK(table->AddResource(NewResourceBuilder(name)
311 .SetId(resid)
312 .SetValue(std::unique_ptr<Value>(value->Transform(cloner)))
313 .Build(),
314 context->GetDiagnostics()));
315
316 // Every few entries, write out a sparse_config value. This will give us the desired load.
317 if (i % stride == 0) {
318 CHECK(table->AddResource(
319 NewResourceBuilder(name)
320 .SetId(resid)
321 .SetValue(std::unique_ptr<Value>(value->Transform(cloner)), sparse_config)
322 .Build(),
323 context->GetDiagnostics()));
324 }
325 }
326 return table;
327 }
328
TEST_F(TableFlattenerTest,FlattenSparseEntryWithMinSdkO)329 TEST_F(TableFlattenerTest, FlattenSparseEntryWithMinSdkO) {
330 std::unique_ptr<IAaptContext> context = test::ContextBuilder()
331 .SetCompilationPackage("android")
332 .SetPackageId(0x01)
333 .SetMinSdkVersion(SDK_O)
334 .Build();
335
336 const ConfigDescription sparse_config = test::ParseConfigOrDie("en-rGB");
337 auto table_in = BuildTableWithSparseEntries(context.get(), sparse_config, 0.25f);
338
339 TableFlattenerOptions options;
340 options.use_sparse_entries = true;
341
342 std::string no_sparse_contents;
343 ASSERT_TRUE(Flatten(context.get(), {}, table_in.get(), &no_sparse_contents));
344
345 std::string sparse_contents;
346 ASSERT_TRUE(Flatten(context.get(), options, table_in.get(), &sparse_contents));
347
348 EXPECT_GT(no_sparse_contents.size(), sparse_contents.size());
349
350 // Attempt to parse the sparse contents.
351
352 ResourceTable sparse_table;
353 BinaryResourceParser parser(context->GetDiagnostics(), &sparse_table, Source("test.arsc"),
354 sparse_contents.data(), sparse_contents.size());
355 ASSERT_TRUE(parser.Parse());
356
357 auto value = test::GetValueForConfig<BinaryPrimitive>(&sparse_table, "android:string/foo_0",
358 sparse_config);
359 ASSERT_THAT(value, NotNull());
360 EXPECT_EQ(0u, value->value.data);
361
362 ASSERT_THAT(test::GetValueForConfig<BinaryPrimitive>(&sparse_table, "android:string/foo_1",
363 sparse_config),
364 IsNull());
365
366 value = test::GetValueForConfig<BinaryPrimitive>(&sparse_table, "android:string/foo_4",
367 sparse_config);
368 ASSERT_THAT(value, NotNull());
369 EXPECT_EQ(4u, value->value.data);
370 }
371
TEST_F(TableFlattenerTest,FlattenSparseEntryWithConfigSdkVersionO)372 TEST_F(TableFlattenerTest, FlattenSparseEntryWithConfigSdkVersionO) {
373 std::unique_ptr<IAaptContext> context = test::ContextBuilder()
374 .SetCompilationPackage("android")
375 .SetPackageId(0x01)
376 .SetMinSdkVersion(SDK_LOLLIPOP)
377 .Build();
378
379 const ConfigDescription sparse_config = test::ParseConfigOrDie("en-rGB-v26");
380 auto table_in = BuildTableWithSparseEntries(context.get(), sparse_config, 0.25f);
381
382 TableFlattenerOptions options;
383 options.use_sparse_entries = true;
384
385 std::string no_sparse_contents;
386 ASSERT_TRUE(Flatten(context.get(), {}, table_in.get(), &no_sparse_contents));
387
388 std::string sparse_contents;
389 ASSERT_TRUE(Flatten(context.get(), options, table_in.get(), &sparse_contents));
390
391 EXPECT_GT(no_sparse_contents.size(), sparse_contents.size());
392 }
393
TEST_F(TableFlattenerTest,DoNotUseSparseEntryForDenseConfig)394 TEST_F(TableFlattenerTest, DoNotUseSparseEntryForDenseConfig) {
395 std::unique_ptr<IAaptContext> context = test::ContextBuilder()
396 .SetCompilationPackage("android")
397 .SetPackageId(0x01)
398 .SetMinSdkVersion(SDK_O)
399 .Build();
400
401 const ConfigDescription sparse_config = test::ParseConfigOrDie("en-rGB");
402 auto table_in = BuildTableWithSparseEntries(context.get(), sparse_config, 0.80f);
403
404 TableFlattenerOptions options;
405 options.use_sparse_entries = true;
406
407 std::string no_sparse_contents;
408 ASSERT_TRUE(Flatten(context.get(), {}, table_in.get(), &no_sparse_contents));
409
410 std::string sparse_contents;
411 ASSERT_TRUE(Flatten(context.get(), options, table_in.get(), &sparse_contents));
412
413 EXPECT_EQ(no_sparse_contents.size(), sparse_contents.size());
414 }
415
TEST_F(TableFlattenerTest,FlattenSharedLibrary)416 TEST_F(TableFlattenerTest, FlattenSharedLibrary) {
417 std::unique_ptr<IAaptContext> context =
418 test::ContextBuilder().SetCompilationPackage("lib").SetPackageId(0x00).Build();
419 std::unique_ptr<ResourceTable> table =
420 test::ResourceTableBuilder()
421 .AddValue("lib:id/foo", ResourceId(0x00010000), util::make_unique<Id>())
422 .Build();
423 ResourceTable result;
424 ASSERT_TRUE(Flatten(context.get(), {}, table.get(), &result));
425
426 Maybe<ResourceTable::SearchResult> search_result =
427 result.FindResource(test::ParseNameOrDie("lib:id/foo"));
428 ASSERT_TRUE(search_result);
429 EXPECT_EQ(0x00u, search_result.value().entry->id.value().package_id());
430
431 auto iter = result.included_packages_.find(0x00);
432 ASSERT_NE(result.included_packages_.end(), iter);
433 EXPECT_EQ("lib", iter->second);
434 }
435
TEST_F(TableFlattenerTest,FlattenSharedLibraryWithStyle)436 TEST_F(TableFlattenerTest, FlattenSharedLibraryWithStyle) {
437 std::unique_ptr<IAaptContext> context =
438 test::ContextBuilder().SetCompilationPackage("lib").SetPackageId(0x00).Build();
439 std::unique_ptr<ResourceTable> table =
440 test::ResourceTableBuilder()
441 .AddValue("lib:style/Theme",
442 ResourceId(0x00030001),
443 test::StyleBuilder()
444 .AddItem("lib:attr/bar", ResourceId(0x00010002),
445 ResourceUtils::TryParseInt("2"))
446 .AddItem("lib:attr/foo", ResourceId(0x00010001),
447 ResourceUtils::TryParseInt("1"))
448 .AddItem("android:attr/bar", ResourceId(0x01010002),
449 ResourceUtils::TryParseInt("4"))
450 .AddItem("android:attr/foo", ResourceId(0x01010001),
451 ResourceUtils::TryParseInt("3"))
452 .Build())
453 .Build();
454 ResourceTable result;
455 ASSERT_TRUE(Flatten(context.get(), {}, table.get(), &result));
456
457 Maybe<ResourceTable::SearchResult> search_result =
458 result.FindResource(test::ParseNameOrDie("lib:style/Theme"));
459 ASSERT_TRUE(search_result);
460 EXPECT_EQ(0x00030001u, search_result.value().entry->id.value());
461 ASSERT_EQ(1u, search_result.value().entry->values.size());
462 Value* value = search_result.value().entry->values[0]->value.get();
463 Style* style = ValueCast<Style>(value);
464 ASSERT_TRUE(style);
465 ASSERT_EQ(4u, style->entries.size());
466 // Ensure the attributes from the shared library come after the items from
467 // android.
468 EXPECT_EQ(0x01010001, style->entries[0].key.id.value());
469 EXPECT_EQ(0x01010002, style->entries[1].key.id.value());
470 EXPECT_EQ(0x00010001, style->entries[2].key.id.value());
471 EXPECT_EQ(0x00010002, style->entries[3].key.id.value());
472 }
473
TEST_F(TableFlattenerTest,FlattenTableReferencingSharedLibraries)474 TEST_F(TableFlattenerTest, FlattenTableReferencingSharedLibraries) {
475 std::unique_ptr<IAaptContext> context =
476 test::ContextBuilder().SetCompilationPackage("app").SetPackageId(0x7f).Build();
477 std::unique_ptr<ResourceTable> table =
478 test::ResourceTableBuilder()
479 .AddValue("app:id/foo", ResourceId(0x7f010000),
480 test::BuildReference("lib_one:id/foo", ResourceId(0x02010000)))
481 .AddValue("app:id/bar", ResourceId(0x7f010001),
482 test::BuildReference("lib_two:id/bar", ResourceId(0x03010000)))
483 .Build();
484 table->included_packages_[0x02] = "lib_one";
485 table->included_packages_[0x03] = "lib_two";
486
487 ResTable result;
488 ASSERT_TRUE(Flatten(context.get(), {}, table.get(), &result));
489
490 const DynamicRefTable* dynamic_ref_table = result.getDynamicRefTableForCookie(1);
491 ASSERT_THAT(dynamic_ref_table, NotNull());
492
493 const KeyedVector<String16, uint8_t>& entries = dynamic_ref_table->entries();
494
495 ssize_t idx = entries.indexOfKey(android::String16("lib_one"));
496 ASSERT_GE(idx, 0);
497 EXPECT_EQ(0x02u, entries.valueAt(idx));
498
499 idx = entries.indexOfKey(android::String16("lib_two"));
500 ASSERT_GE(idx, 0);
501 EXPECT_EQ(0x03u, entries.valueAt(idx));
502 }
503
TEST_F(TableFlattenerTest,PackageWithNonStandardIdHasDynamicRefTable)504 TEST_F(TableFlattenerTest, PackageWithNonStandardIdHasDynamicRefTable) {
505 std::unique_ptr<IAaptContext> context =
506 test::ContextBuilder().SetCompilationPackage("app").SetPackageId(0x80).Build();
507 std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
508 .AddSimple("app:id/foo", ResourceId(0x80010000))
509 .Build();
510
511 ResTable result;
512 ASSERT_TRUE(Flatten(context.get(), {}, table.get(), &result));
513
514 const DynamicRefTable* dynamic_ref_table = result.getDynamicRefTableForCookie(1);
515 ASSERT_THAT(dynamic_ref_table, NotNull());
516
517 const KeyedVector<String16, uint8_t>& entries = dynamic_ref_table->entries();
518 ssize_t idx = entries.indexOfKey(android::String16("app"));
519 ASSERT_GE(idx, 0);
520 EXPECT_EQ(0x80u, entries.valueAt(idx));
521 }
522
TEST_F(TableFlattenerTest,LongPackageNameIsTruncated)523 TEST_F(TableFlattenerTest, LongPackageNameIsTruncated) {
524 std::string kPackageName(256, 'F');
525
526 std::unique_ptr<IAaptContext> context =
527 test::ContextBuilder().SetCompilationPackage(kPackageName).SetPackageId(0x7f).Build();
528 std::unique_ptr<ResourceTable> table =
529 test::ResourceTableBuilder()
530 .AddSimple(kPackageName + ":id/foo", ResourceId(0x7f010000))
531 .Build();
532
533 ResTable result;
534 ASSERT_TRUE(Flatten(context.get(), {}, table.get(), &result));
535
536 ASSERT_EQ(1u, result.getBasePackageCount());
537 EXPECT_EQ(127u, result.getBasePackageName(0).size());
538 }
539
TEST_F(TableFlattenerTest,LongSharedLibraryPackageNameIsIllegal)540 TEST_F(TableFlattenerTest, LongSharedLibraryPackageNameIsIllegal) {
541 std::string kPackageName(256, 'F');
542
543 std::unique_ptr<IAaptContext> context = test::ContextBuilder()
544 .SetCompilationPackage(kPackageName)
545 .SetPackageId(0x7f)
546 .SetPackageType(PackageType::kSharedLib)
547 .Build();
548 std::unique_ptr<ResourceTable> table =
549 test::ResourceTableBuilder()
550 .AddSimple(kPackageName + ":id/foo", ResourceId(0x7f010000))
551 .Build();
552
553 ResTable result;
554 ASSERT_FALSE(Flatten(context.get(), {}, table.get(), &result));
555 }
556
TEST_F(TableFlattenerTest,ObfuscatingResourceNamesNoNameCollapseExemptionsSucceeds)557 TEST_F(TableFlattenerTest, ObfuscatingResourceNamesNoNameCollapseExemptionsSucceeds) {
558 std::unique_ptr<ResourceTable> table =
559 test::ResourceTableBuilder()
560 .AddSimple("com.app.test:id/one", ResourceId(0x7f020000))
561 .AddSimple("com.app.test:id/two", ResourceId(0x7f020001))
562 .AddValue("com.app.test:id/three", ResourceId(0x7f020002),
563 test::BuildReference("com.app.test:id/one", ResourceId(0x7f020000)))
564 .AddValue("com.app.test:integer/one", ResourceId(0x7f030000),
565 util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 1u))
566 .AddValue("com.app.test:integer/one", test::ParseConfigOrDie("v1"),
567 ResourceId(0x7f030000),
568 util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 2u))
569 .AddString("com.app.test:string/test", ResourceId(0x7f040000), "foo")
570 .AddString("com.app.test:layout/bar", ResourceId(0x7f050000), "res/layout/bar.xml")
571 .Build();
572
573 TableFlattenerOptions options;
574 options.collapse_key_stringpool = true;
575
576 ResTable res_table;
577
578 ASSERT_TRUE(Flatten(context_.get(), options, table.get(), &res_table));
579
580 EXPECT_TRUE(Exists(&res_table, "com.app.test:id/0_resource_name_obfuscated",
581 ResourceId(0x7f020000), {}, Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
582
583 EXPECT_TRUE(Exists(&res_table, "com.app.test:id/0_resource_name_obfuscated",
584 ResourceId(0x7f020001), {}, Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
585
586 EXPECT_TRUE(Exists(&res_table, "com.app.test:id/0_resource_name_obfuscated",
587 ResourceId(0x7f020002), {}, Res_value::TYPE_REFERENCE, 0x7f020000u, 0u));
588
589 EXPECT_TRUE(Exists(&res_table, "com.app.test:integer/0_resource_name_obfuscated",
590 ResourceId(0x7f030000), {}, Res_value::TYPE_INT_DEC, 1u,
591 ResTable_config::CONFIG_VERSION));
592
593 EXPECT_TRUE(Exists(&res_table, "com.app.test:integer/0_resource_name_obfuscated",
594 ResourceId(0x7f030000), test::ParseConfigOrDie("v1"), Res_value::TYPE_INT_DEC,
595 2u, ResTable_config::CONFIG_VERSION));
596
597 std::u16string foo_str = u"foo";
598 auto idx = res_table.getTableStringBlock(0)->indexOfString(foo_str.data(), foo_str.size());
599 ASSERT_TRUE(idx.has_value());
600 EXPECT_TRUE(Exists(&res_table, "com.app.test:string/0_resource_name_obfuscated",
601 ResourceId(0x7f040000), {}, Res_value::TYPE_STRING, (uint32_t)*idx, 0u));
602
603 std::u16string bar_path = u"res/layout/bar.xml";
604 idx = res_table.getTableStringBlock(0)->indexOfString(bar_path.data(), bar_path.size());
605 ASSERT_TRUE(idx.has_value());
606 EXPECT_TRUE(Exists(&res_table, "com.app.test:layout/0_resource_name_obfuscated",
607 ResourceId(0x7f050000), {}, Res_value::TYPE_STRING, (uint32_t)*idx, 0u));
608 }
609
TEST_F(TableFlattenerTest,ObfuscatingResourceNamesWithNameCollapseExemptionsSucceeds)610 TEST_F(TableFlattenerTest, ObfuscatingResourceNamesWithNameCollapseExemptionsSucceeds) {
611 std::unique_ptr<ResourceTable> table =
612 test::ResourceTableBuilder()
613 .AddSimple("com.app.test:id/one", ResourceId(0x7f020000))
614 .AddSimple("com.app.test:id/two", ResourceId(0x7f020001))
615 .AddValue("com.app.test:id/three", ResourceId(0x7f020002),
616 test::BuildReference("com.app.test:id/one", ResourceId(0x7f020000)))
617 .AddValue("com.app.test:integer/one", ResourceId(0x7f030000),
618 util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 1u))
619 .AddValue("com.app.test:integer/one", test::ParseConfigOrDie("v1"),
620 ResourceId(0x7f030000),
621 util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 2u))
622 .AddString("com.app.test:string/test", ResourceId(0x7f040000), "foo")
623 .AddString("com.app.test:layout/bar", ResourceId(0x7f050000), "res/layout/bar.xml")
624 .Build();
625
626 TableFlattenerOptions options;
627 options.collapse_key_stringpool = true;
628 options.name_collapse_exemptions.insert(ResourceName({}, ResourceType::kId, "one"));
629 options.name_collapse_exemptions.insert(ResourceName({}, ResourceType::kString, "test"));
630 ResTable res_table;
631
632 ASSERT_TRUE(Flatten(context_.get(), options, table.get(), &res_table));
633
634 EXPECT_TRUE(Exists(&res_table, "com.app.test:id/one",
635 ResourceId(0x7f020000), {}, Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
636
637 EXPECT_TRUE(Exists(&res_table, "com.app.test:id/0_resource_name_obfuscated",
638 ResourceId(0x7f020001), {}, Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
639
640 EXPECT_TRUE(Exists(&res_table, "com.app.test:id/0_resource_name_obfuscated",
641 ResourceId(0x7f020002), {}, Res_value::TYPE_REFERENCE, 0x7f020000u, 0u));
642
643 // Note that this resource is also named "one", but it's a different type, so gets obfuscated.
644 EXPECT_TRUE(Exists(&res_table, "com.app.test:integer/0_resource_name_obfuscated",
645 ResourceId(0x7f030000), {}, Res_value::TYPE_INT_DEC, 1u,
646 ResTable_config::CONFIG_VERSION));
647
648 EXPECT_TRUE(Exists(&res_table, "com.app.test:integer/0_resource_name_obfuscated",
649 ResourceId(0x7f030000), test::ParseConfigOrDie("v1"), Res_value::TYPE_INT_DEC,
650 2u, ResTable_config::CONFIG_VERSION));
651
652 std::u16string foo_str = u"foo";
653 auto idx = res_table.getTableStringBlock(0)->indexOfString(foo_str.data(), foo_str.size());
654 ASSERT_TRUE(idx.has_value());
655 EXPECT_TRUE(Exists(&res_table, "com.app.test:string/test", ResourceId(0x7f040000), {},
656 Res_value::TYPE_STRING, (uint32_t)*idx, 0u));
657
658 std::u16string bar_path = u"res/layout/bar.xml";
659 idx = res_table.getTableStringBlock(0)->indexOfString(bar_path.data(), bar_path.size());
660 ASSERT_TRUE(idx.has_value());
661 EXPECT_TRUE(Exists(&res_table, "com.app.test:layout/0_resource_name_obfuscated",
662 ResourceId(0x7f050000), {}, Res_value::TYPE_STRING, (uint32_t)*idx, 0u));
663 }
664
TEST_F(TableFlattenerTest,FlattenOverlayable)665 TEST_F(TableFlattenerTest, FlattenOverlayable) {
666 OverlayableItem overlayable_item(std::make_shared<Overlayable>("TestName", "overlay://theme"));
667 overlayable_item.policies |= PolicyFlags::PRODUCT_PARTITION;
668 overlayable_item.policies |= PolicyFlags::SYSTEM_PARTITION;
669 overlayable_item.policies |= PolicyFlags::VENDOR_PARTITION;
670
671 std::string name = "com.app.test:integer/overlayable";
672 std::unique_ptr<ResourceTable> table =
673 test::ResourceTableBuilder()
674 .AddSimple(name, ResourceId(0x7f020000))
675 .SetOverlayable(name, overlayable_item)
676 .Build();
677
678 ResourceTable output_table;
679 ASSERT_TRUE(Flatten(context_.get(), {}, table.get(), &output_table));
680
681 auto search_result = output_table.FindResource(test::ParseNameOrDie(name));
682 ASSERT_TRUE(search_result);
683 ASSERT_THAT(search_result.value().entry, NotNull());
684 ASSERT_TRUE(search_result.value().entry->overlayable_item);
685 OverlayableItem& result_overlayable_item = search_result.value().entry->overlayable_item.value();
686 EXPECT_EQ(result_overlayable_item.policies, PolicyFlags::SYSTEM_PARTITION
687 | PolicyFlags::VENDOR_PARTITION
688 | PolicyFlags::PRODUCT_PARTITION);
689 }
690
TEST_F(TableFlattenerTest,FlattenMultipleOverlayablePolicies)691 TEST_F(TableFlattenerTest, FlattenMultipleOverlayablePolicies) {
692 auto overlayable = std::make_shared<Overlayable>("TestName", "overlay://theme");
693 std::string name_zero = "com.app.test:integer/overlayable_zero_item";
694 OverlayableItem overlayable_item_zero(overlayable);
695 overlayable_item_zero.policies |= PolicyFlags::PRODUCT_PARTITION;
696 overlayable_item_zero.policies |= PolicyFlags::SYSTEM_PARTITION;
697
698 std::string name_one = "com.app.test:integer/overlayable_one_item";
699 OverlayableItem overlayable_item_one(overlayable);
700 overlayable_item_one.policies |= PolicyFlags::PUBLIC;
701
702 std::string name_two = "com.app.test:integer/overlayable_two_item";
703 OverlayableItem overlayable_item_two(overlayable);
704 overlayable_item_two.policies |= PolicyFlags::PRODUCT_PARTITION;
705 overlayable_item_two.policies |= PolicyFlags::SYSTEM_PARTITION;
706 overlayable_item_two.policies |= PolicyFlags::VENDOR_PARTITION;
707
708 std::unique_ptr<ResourceTable> table =
709 test::ResourceTableBuilder()
710 .AddSimple(name_zero, ResourceId(0x7f020000))
711 .SetOverlayable(name_zero, overlayable_item_zero)
712 .AddSimple(name_one, ResourceId(0x7f020001))
713 .SetOverlayable(name_one, overlayable_item_one)
714 .AddSimple(name_two, ResourceId(0x7f020002))
715 .SetOverlayable(name_two, overlayable_item_two)
716 .Build();
717
718 ResourceTable output_table;
719 ASSERT_TRUE(Flatten(context_.get(), {}, table.get(), &output_table));
720
721 auto search_result = output_table.FindResource(test::ParseNameOrDie(name_zero));
722 ASSERT_TRUE(search_result);
723 ASSERT_THAT(search_result.value().entry, NotNull());
724 ASSERT_TRUE(search_result.value().entry->overlayable_item);
725 OverlayableItem& overlayable_item = search_result.value().entry->overlayable_item.value();
726 EXPECT_EQ(overlayable_item.policies, PolicyFlags::SYSTEM_PARTITION
727 | PolicyFlags::PRODUCT_PARTITION);
728
729 search_result = output_table.FindResource(test::ParseNameOrDie(name_one));
730 ASSERT_TRUE(search_result);
731 ASSERT_THAT(search_result.value().entry, NotNull());
732 ASSERT_TRUE(search_result.value().entry->overlayable_item);
733 overlayable_item = search_result.value().entry->overlayable_item.value();
734 EXPECT_EQ(overlayable_item.policies, PolicyFlags::PUBLIC);
735
736 search_result = output_table.FindResource(test::ParseNameOrDie(name_two));
737 ASSERT_TRUE(search_result);
738 ASSERT_THAT(search_result.value().entry, NotNull());
739 ASSERT_TRUE(search_result.value().entry->overlayable_item);
740 overlayable_item = search_result.value().entry->overlayable_item.value();
741 EXPECT_EQ(overlayable_item.policies, PolicyFlags::SYSTEM_PARTITION
742 | PolicyFlags::PRODUCT_PARTITION
743 | PolicyFlags::VENDOR_PARTITION);
744 }
745
TEST_F(TableFlattenerTest,FlattenMultipleOverlayable)746 TEST_F(TableFlattenerTest, FlattenMultipleOverlayable) {
747 auto group = std::make_shared<Overlayable>("TestName", "overlay://theme");
748 std::string name_zero = "com.app.test:integer/overlayable_zero";
749 OverlayableItem overlayable_item_zero(group);
750 overlayable_item_zero.policies |= PolicyFlags::PRODUCT_PARTITION;
751 overlayable_item_zero.policies |= PolicyFlags::SYSTEM_PARTITION;
752
753 auto group_one = std::make_shared<Overlayable>("OtherName", "overlay://customization");
754 std::string name_one = "com.app.test:integer/overlayable_one";
755 OverlayableItem overlayable_item_one(group_one);
756 overlayable_item_one.policies |= PolicyFlags::PUBLIC;
757
758 std::string name_two = "com.app.test:integer/overlayable_two";
759 OverlayableItem overlayable_item_two(group);
760 overlayable_item_two.policies |= PolicyFlags::ODM_PARTITION;
761 overlayable_item_two.policies |= PolicyFlags::OEM_PARTITION;
762 overlayable_item_two.policies |= PolicyFlags::VENDOR_PARTITION;
763
764 std::string name_three = "com.app.test:integer/overlayable_three";
765 OverlayableItem overlayable_item_three(group_one);
766 overlayable_item_three.policies |= PolicyFlags::SIGNATURE;
767 overlayable_item_three.policies |= PolicyFlags::ACTOR_SIGNATURE;
768 overlayable_item_three.policies |= PolicyFlags::CONFIG_SIGNATURE;
769
770 std::unique_ptr<ResourceTable> table =
771 test::ResourceTableBuilder()
772 .AddSimple(name_zero, ResourceId(0x7f020000))
773 .SetOverlayable(name_zero, overlayable_item_zero)
774 .AddSimple(name_one, ResourceId(0x7f020001))
775 .SetOverlayable(name_one, overlayable_item_one)
776 .AddSimple(name_two, ResourceId(0x7f020002))
777 .SetOverlayable(name_two, overlayable_item_two)
778 .AddSimple(name_three, ResourceId(0x7f020003))
779 .SetOverlayable(name_three, overlayable_item_three)
780 .Build();
781
782 ResourceTable output_table;
783 ASSERT_TRUE(Flatten(context_.get(), {}, table.get(), &output_table));
784 auto search_result = output_table.FindResource(test::ParseNameOrDie(name_zero));
785 ASSERT_TRUE(search_result);
786 ASSERT_THAT(search_result.value().entry, NotNull());
787 ASSERT_TRUE(search_result.value().entry->overlayable_item);
788 OverlayableItem& result_overlayable = search_result.value().entry->overlayable_item.value();
789 EXPECT_EQ(result_overlayable.overlayable->name, "TestName");
790 EXPECT_EQ(result_overlayable.overlayable->actor, "overlay://theme");
791 EXPECT_EQ(result_overlayable.policies, PolicyFlags::SYSTEM_PARTITION
792 | PolicyFlags::PRODUCT_PARTITION);
793
794 search_result = output_table.FindResource(test::ParseNameOrDie(name_one));
795 ASSERT_TRUE(search_result);
796 ASSERT_THAT(search_result.value().entry, NotNull());
797 ASSERT_TRUE(search_result.value().entry->overlayable_item);
798 result_overlayable = search_result.value().entry->overlayable_item.value();
799 EXPECT_EQ(result_overlayable.overlayable->name, "OtherName");
800 EXPECT_EQ(result_overlayable.overlayable->actor, "overlay://customization");
801 EXPECT_EQ(result_overlayable.policies, PolicyFlags::PUBLIC);
802
803 search_result = output_table.FindResource(test::ParseNameOrDie(name_two));
804 ASSERT_TRUE(search_result);
805 ASSERT_THAT(search_result.value().entry, NotNull());
806 ASSERT_TRUE(search_result.value().entry->overlayable_item);
807 result_overlayable = search_result.value().entry->overlayable_item.value();
808 EXPECT_EQ(result_overlayable.overlayable->name, "TestName");
809 EXPECT_EQ(result_overlayable.overlayable->actor, "overlay://theme");
810 EXPECT_EQ(result_overlayable.policies, PolicyFlags::ODM_PARTITION
811 | PolicyFlags::OEM_PARTITION
812 | PolicyFlags::VENDOR_PARTITION);
813
814 search_result = output_table.FindResource(test::ParseNameOrDie(name_three));
815 ASSERT_TRUE(search_result);
816 ASSERT_THAT(search_result.value().entry, NotNull());
817 ASSERT_TRUE(search_result.value().entry->overlayable_item);
818 result_overlayable = search_result.value().entry->overlayable_item.value();
819 EXPECT_EQ(result_overlayable.overlayable->name, "OtherName");
820 EXPECT_EQ(result_overlayable.overlayable->actor, "overlay://customization");
821 EXPECT_EQ(result_overlayable.policies, PolicyFlags::SIGNATURE
822 | PolicyFlags::ACTOR_SIGNATURE
823 | PolicyFlags::CONFIG_SIGNATURE);
824 }
825
TEST_F(TableFlattenerTest,FlattenOverlayableNoPolicyFails)826 TEST_F(TableFlattenerTest, FlattenOverlayableNoPolicyFails) {
827 auto group = std::make_shared<Overlayable>("TestName", "overlay://theme");
828 std::string name_zero = "com.app.test:integer/overlayable_zero";
829 OverlayableItem overlayable_item_zero(group);
830
831 std::unique_ptr<ResourceTable> table =
832 test::ResourceTableBuilder()
833 .AddSimple(name_zero, ResourceId(0x7f020000))
834 .SetOverlayable(name_zero, overlayable_item_zero)
835 .Build();
836 ResourceTable output_table;
837 ASSERT_FALSE(Flatten(context_.get(), {}, table.get(), &output_table));
838 }
839
840 } // namespace aapt
841