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 .SetPackageId("com.app.test", 0x7f)
159 .AddSimple("com.app.test:id/one", ResourceId(0x7f020000))
160 .AddSimple("com.app.test:id/two", ResourceId(0x7f020001))
161 .AddValue("com.app.test:id/three", ResourceId(0x7f020002),
162 test::BuildReference("com.app.test:id/one", ResourceId(0x7f020000)))
163 .AddValue("com.app.test:integer/one", ResourceId(0x7f030000),
164 util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 1u))
165 .AddValue("com.app.test:integer/one", test::ParseConfigOrDie("v1"),
166 ResourceId(0x7f030000),
167 util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 2u))
168 .AddString("com.app.test:string/test", ResourceId(0x7f040000), "foo")
169 .AddString("com.app.test:layout/bar", ResourceId(0x7f050000), "res/layout/bar.xml")
170 .Build();
171
172 ResTable res_table;
173 ASSERT_TRUE(Flatten(context_.get(), {}, table.get(), &res_table));
174
175 EXPECT_TRUE(Exists(&res_table, "com.app.test:id/one", ResourceId(0x7f020000), {},
176 Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
177
178 EXPECT_TRUE(Exists(&res_table, "com.app.test:id/two", ResourceId(0x7f020001), {},
179 Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
180
181 EXPECT_TRUE(Exists(&res_table, "com.app.test:id/three", ResourceId(0x7f020002), {},
182 Res_value::TYPE_REFERENCE, 0x7f020000u, 0u));
183
184 EXPECT_TRUE(Exists(&res_table, "com.app.test:integer/one", ResourceId(0x7f030000), {},
185 Res_value::TYPE_INT_DEC, 1u, ResTable_config::CONFIG_VERSION));
186
187 EXPECT_TRUE(Exists(&res_table, "com.app.test:integer/one", ResourceId(0x7f030000),
188 test::ParseConfigOrDie("v1"), Res_value::TYPE_INT_DEC, 2u,
189 ResTable_config::CONFIG_VERSION));
190
191 std::u16string foo_str = u"foo";
192 ssize_t idx = res_table.getTableStringBlock(0)->indexOfString(foo_str.data(), foo_str.size());
193 ASSERT_GE(idx, 0);
194 EXPECT_TRUE(Exists(&res_table, "com.app.test:string/test", ResourceId(0x7f040000), {},
195 Res_value::TYPE_STRING, (uint32_t)idx, 0u));
196
197 std::u16string bar_path = u"res/layout/bar.xml";
198 idx = res_table.getTableStringBlock(0)->indexOfString(bar_path.data(), bar_path.size());
199 ASSERT_GE(idx, 0);
200 EXPECT_TRUE(Exists(&res_table, "com.app.test:layout/bar", ResourceId(0x7f050000), {},
201 Res_value::TYPE_STRING, (uint32_t)idx, 0u));
202 }
203
TEST_F(TableFlattenerTest,FlattenEntriesWithGapsInIds)204 TEST_F(TableFlattenerTest, FlattenEntriesWithGapsInIds) {
205 std::unique_ptr<ResourceTable> table =
206 test::ResourceTableBuilder()
207 .SetPackageId("com.app.test", 0x7f)
208 .AddSimple("com.app.test:id/one", ResourceId(0x7f020001))
209 .AddSimple("com.app.test:id/three", ResourceId(0x7f020003))
210 .Build();
211
212 ResTable res_table;
213 ASSERT_TRUE(Flatten(context_.get(), {}, table.get(), &res_table));
214
215 EXPECT_TRUE(Exists(&res_table, "com.app.test:id/one", ResourceId(0x7f020001), {},
216 Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
217 EXPECT_TRUE(Exists(&res_table, "com.app.test:id/three", ResourceId(0x7f020003), {},
218 Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
219 }
220
TEST_F(TableFlattenerTest,FlattenMinMaxAttributes)221 TEST_F(TableFlattenerTest, FlattenMinMaxAttributes) {
222 Attribute attr;
223 attr.type_mask = android::ResTable_map::TYPE_INTEGER;
224 attr.min_int = 10;
225 attr.max_int = 23;
226 std::unique_ptr<ResourceTable> table =
227 test::ResourceTableBuilder()
228 .SetPackageId("android", 0x01)
229 .AddValue("android:attr/foo", ResourceId(0x01010000), util::make_unique<Attribute>(attr))
230 .Build();
231
232 ResourceTable result;
233 ASSERT_TRUE(Flatten(context_.get(), {}, table.get(), &result));
234
235 Attribute* actual_attr = test::GetValue<Attribute>(&result, "android:attr/foo");
236 ASSERT_THAT(actual_attr, NotNull());
237 EXPECT_EQ(attr.IsWeak(), actual_attr->IsWeak());
238 EXPECT_EQ(attr.type_mask, actual_attr->type_mask);
239 EXPECT_EQ(attr.min_int, actual_attr->min_int);
240 EXPECT_EQ(attr.max_int, actual_attr->max_int);
241 }
242
TEST_F(TableFlattenerTest,FlattenArray)243 TEST_F(TableFlattenerTest, FlattenArray) {
244 auto array = util::make_unique<Array>();
245 array->elements.push_back(util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC),
246 1u));
247 array->elements.push_back(util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC),
248 2u));
249 std::unique_ptr<ResourceTable> table =
250 test::ResourceTableBuilder()
251 .SetPackageId("android", 0x01)
252 .AddValue("android:array/foo", ResourceId(0x01010000), std::move(array))
253 .Build();
254
255 std::string result;
256 ASSERT_TRUE(Flatten(context_.get(), {}, table.get(), &result));
257
258 // Parse the flattened resource table
259 ResChunkPullParser parser(result.data(), result.size());
260 ASSERT_TRUE(parser.IsGoodEvent(parser.Next()));
261 ASSERT_EQ(util::DeviceToHost16(parser.chunk()->type), RES_TABLE_TYPE);
262
263 // Retrieve the package of the entry
264 ResChunkPullParser table_parser(GetChunkData(parser.chunk()), GetChunkDataLen(parser.chunk()));
265 const ResChunk_header* package_chunk = nullptr;
266 while (table_parser.IsGoodEvent(table_parser.Next())) {
267 if (util::DeviceToHost16(table_parser.chunk()->type) == RES_TABLE_PACKAGE_TYPE) {
268 package_chunk = table_parser.chunk();
269 break;
270 }
271 }
272
273 // Retrieve the type that proceeds the array entry
274 ASSERT_NE(package_chunk, nullptr);
275 ResChunkPullParser package_parser(GetChunkData(table_parser.chunk()),
276 GetChunkDataLen(table_parser.chunk()));
277 const ResChunk_header* type_chunk = nullptr;
278 while (package_parser.IsGoodEvent(package_parser.Next())) {
279 if (util::DeviceToHost16(package_parser.chunk()->type) == RES_TABLE_TYPE_TYPE) {
280 type_chunk = package_parser.chunk();
281 break;
282 }
283 }
284
285 // Retrieve the array entry
286 ASSERT_NE(type_chunk, nullptr);
287 TypeVariant typeVariant((const ResTable_type*) type_chunk);
288 auto entry = (const ResTable_map_entry*)*typeVariant.beginEntries();
289 ASSERT_EQ(util::DeviceToHost16(entry->count), 2u);
290
291 // Check that the value and name of the array entries are correct
292 auto values = (const ResTable_map*)(((const uint8_t *)entry) + entry->size);
293 ASSERT_EQ(values->value.data, 1u);
294 ASSERT_EQ(values->name.ident, android::ResTable_map::ATTR_MIN);
295 ASSERT_EQ((values+1)->value.data, 2u);
296 ASSERT_EQ((values+1)->name.ident, android::ResTable_map::ATTR_MIN + 1);
297 }
298
BuildTableWithSparseEntries(IAaptContext * context,const ConfigDescription & sparse_config,float load)299 static std::unique_ptr<ResourceTable> BuildTableWithSparseEntries(
300 IAaptContext* context, const ConfigDescription& sparse_config, float load) {
301 std::unique_ptr<ResourceTable> table =
302 test::ResourceTableBuilder()
303 .SetPackageId(context->GetCompilationPackage(), context->GetPackageId())
304 .Build();
305
306 // Add regular entries.
307 int stride = static_cast<int>(1.0f / load);
308 for (int i = 0; i < 100; i++) {
309 const ResourceName name = test::ParseNameOrDie(
310 base::StringPrintf("%s:string/foo_%d", context->GetCompilationPackage().data(), i));
311 const ResourceId resid(context->GetPackageId(), 0x02, static_cast<uint16_t>(i));
312 const auto value =
313 util::make_unique<BinaryPrimitive>(Res_value::TYPE_INT_DEC, static_cast<uint32_t>(i));
314 CHECK(table->AddResourceWithId(name, resid, ConfigDescription::DefaultConfig(), "",
315 std::unique_ptr<Value>(value->Clone(nullptr)),
316 context->GetDiagnostics()));
317
318 // Every few entries, write out a sparse_config value. This will give us the desired load.
319 if (i % stride == 0) {
320 CHECK(table->AddResourceWithId(name, resid, sparse_config, "",
321 std::unique_ptr<Value>(value->Clone(nullptr)),
322 context->GetDiagnostics()));
323 }
324 }
325 return table;
326 }
327
TEST_F(TableFlattenerTest,FlattenSparseEntryWithMinSdkO)328 TEST_F(TableFlattenerTest, FlattenSparseEntryWithMinSdkO) {
329 std::unique_ptr<IAaptContext> context = test::ContextBuilder()
330 .SetCompilationPackage("android")
331 .SetPackageId(0x01)
332 .SetMinSdkVersion(SDK_O)
333 .Build();
334
335 const ConfigDescription sparse_config = test::ParseConfigOrDie("en-rGB");
336 auto table_in = BuildTableWithSparseEntries(context.get(), sparse_config, 0.25f);
337
338 TableFlattenerOptions options;
339 options.use_sparse_entries = true;
340
341 std::string no_sparse_contents;
342 ASSERT_TRUE(Flatten(context.get(), {}, table_in.get(), &no_sparse_contents));
343
344 std::string sparse_contents;
345 ASSERT_TRUE(Flatten(context.get(), options, table_in.get(), &sparse_contents));
346
347 EXPECT_GT(no_sparse_contents.size(), sparse_contents.size());
348
349 // Attempt to parse the sparse contents.
350
351 ResourceTable sparse_table;
352 BinaryResourceParser parser(context->GetDiagnostics(), &sparse_table, Source("test.arsc"),
353 sparse_contents.data(), sparse_contents.size());
354 ASSERT_TRUE(parser.Parse());
355
356 auto value = test::GetValueForConfig<BinaryPrimitive>(&sparse_table, "android:string/foo_0",
357 sparse_config);
358 ASSERT_THAT(value, NotNull());
359 EXPECT_EQ(0u, value->value.data);
360
361 ASSERT_THAT(test::GetValueForConfig<BinaryPrimitive>(&sparse_table, "android:string/foo_1",
362 sparse_config),
363 IsNull());
364
365 value = test::GetValueForConfig<BinaryPrimitive>(&sparse_table, "android:string/foo_4",
366 sparse_config);
367 ASSERT_THAT(value, NotNull());
368 EXPECT_EQ(4u, value->value.data);
369 }
370
TEST_F(TableFlattenerTest,FlattenSparseEntryWithConfigSdkVersionO)371 TEST_F(TableFlattenerTest, FlattenSparseEntryWithConfigSdkVersionO) {
372 std::unique_ptr<IAaptContext> context = test::ContextBuilder()
373 .SetCompilationPackage("android")
374 .SetPackageId(0x01)
375 .SetMinSdkVersion(SDK_LOLLIPOP)
376 .Build();
377
378 const ConfigDescription sparse_config = test::ParseConfigOrDie("en-rGB-v26");
379 auto table_in = BuildTableWithSparseEntries(context.get(), sparse_config, 0.25f);
380
381 TableFlattenerOptions options;
382 options.use_sparse_entries = true;
383
384 std::string no_sparse_contents;
385 ASSERT_TRUE(Flatten(context.get(), {}, table_in.get(), &no_sparse_contents));
386
387 std::string sparse_contents;
388 ASSERT_TRUE(Flatten(context.get(), options, table_in.get(), &sparse_contents));
389
390 EXPECT_GT(no_sparse_contents.size(), sparse_contents.size());
391 }
392
TEST_F(TableFlattenerTest,DoNotUseSparseEntryForDenseConfig)393 TEST_F(TableFlattenerTest, DoNotUseSparseEntryForDenseConfig) {
394 std::unique_ptr<IAaptContext> context = test::ContextBuilder()
395 .SetCompilationPackage("android")
396 .SetPackageId(0x01)
397 .SetMinSdkVersion(SDK_O)
398 .Build();
399
400 const ConfigDescription sparse_config = test::ParseConfigOrDie("en-rGB");
401 auto table_in = BuildTableWithSparseEntries(context.get(), sparse_config, 0.80f);
402
403 TableFlattenerOptions options;
404 options.use_sparse_entries = true;
405
406 std::string no_sparse_contents;
407 ASSERT_TRUE(Flatten(context.get(), {}, table_in.get(), &no_sparse_contents));
408
409 std::string sparse_contents;
410 ASSERT_TRUE(Flatten(context.get(), options, table_in.get(), &sparse_contents));
411
412 EXPECT_EQ(no_sparse_contents.size(), sparse_contents.size());
413 }
414
TEST_F(TableFlattenerTest,FlattenSharedLibrary)415 TEST_F(TableFlattenerTest, FlattenSharedLibrary) {
416 std::unique_ptr<IAaptContext> context =
417 test::ContextBuilder().SetCompilationPackage("lib").SetPackageId(0x00).Build();
418 std::unique_ptr<ResourceTable> table =
419 test::ResourceTableBuilder()
420 .SetPackageId("lib", 0x00)
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().package->id.value());
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 .SetPackageId("lib", 0x00)
442 .AddValue("lib:style/Theme",
443 ResourceId(0x00030001),
444 test::StyleBuilder()
445 .AddItem("lib:attr/bar", ResourceId(0x00010002),
446 ResourceUtils::TryParseInt("2"))
447 .AddItem("lib:attr/foo", ResourceId(0x00010001),
448 ResourceUtils::TryParseInt("1"))
449 .AddItem("android:attr/bar", ResourceId(0x01010002),
450 ResourceUtils::TryParseInt("4"))
451 .AddItem("android:attr/foo", ResourceId(0x01010001),
452 ResourceUtils::TryParseInt("3"))
453 .Build())
454 .Build();
455 ResourceTable result;
456 ASSERT_TRUE(Flatten(context.get(), {}, table.get(), &result));
457
458 Maybe<ResourceTable::SearchResult> search_result =
459 result.FindResource(test::ParseNameOrDie("lib:style/Theme"));
460 ASSERT_TRUE(search_result);
461 EXPECT_EQ(0x00u, search_result.value().package->id.value());
462 EXPECT_EQ(0x03u, search_result.value().type->id.value());
463 EXPECT_EQ(0x01u, search_result.value().entry->id.value());
464 ASSERT_EQ(1u, search_result.value().entry->values.size());
465 Value* value = search_result.value().entry->values[0]->value.get();
466 Style* style = ValueCast<Style>(value);
467 ASSERT_TRUE(style);
468 ASSERT_EQ(4u, style->entries.size());
469 // Ensure the attributes from the shared library come after the items from
470 // android.
471 EXPECT_EQ(0x01010001, style->entries[0].key.id.value());
472 EXPECT_EQ(0x01010002, style->entries[1].key.id.value());
473 EXPECT_EQ(0x00010001, style->entries[2].key.id.value());
474 EXPECT_EQ(0x00010002, style->entries[3].key.id.value());
475 }
476
TEST_F(TableFlattenerTest,FlattenTableReferencingSharedLibraries)477 TEST_F(TableFlattenerTest, FlattenTableReferencingSharedLibraries) {
478 std::unique_ptr<IAaptContext> context =
479 test::ContextBuilder().SetCompilationPackage("app").SetPackageId(0x7f).Build();
480 std::unique_ptr<ResourceTable> table =
481 test::ResourceTableBuilder()
482 .SetPackageId("app", 0x7f)
483 .AddValue("app:id/foo", ResourceId(0x7f010000),
484 test::BuildReference("lib_one:id/foo", ResourceId(0x02010000)))
485 .AddValue("app:id/bar", ResourceId(0x7f010001),
486 test::BuildReference("lib_two:id/bar", ResourceId(0x03010000)))
487 .Build();
488 table->included_packages_[0x02] = "lib_one";
489 table->included_packages_[0x03] = "lib_two";
490
491 ResTable result;
492 ASSERT_TRUE(Flatten(context.get(), {}, table.get(), &result));
493
494 const DynamicRefTable* dynamic_ref_table = result.getDynamicRefTableForCookie(1);
495 ASSERT_THAT(dynamic_ref_table, NotNull());
496
497 const KeyedVector<String16, uint8_t>& entries = dynamic_ref_table->entries();
498
499 ssize_t idx = entries.indexOfKey(android::String16("lib_one"));
500 ASSERT_GE(idx, 0);
501 EXPECT_EQ(0x02u, entries.valueAt(idx));
502
503 idx = entries.indexOfKey(android::String16("lib_two"));
504 ASSERT_GE(idx, 0);
505 EXPECT_EQ(0x03u, entries.valueAt(idx));
506 }
507
TEST_F(TableFlattenerTest,PackageWithNonStandardIdHasDynamicRefTable)508 TEST_F(TableFlattenerTest, PackageWithNonStandardIdHasDynamicRefTable) {
509 std::unique_ptr<IAaptContext> context =
510 test::ContextBuilder().SetCompilationPackage("app").SetPackageId(0x80).Build();
511 std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
512 .SetPackageId("app", 0x80)
513 .AddSimple("app:id/foo", ResourceId(0x80010000))
514 .Build();
515
516 ResTable result;
517 ASSERT_TRUE(Flatten(context.get(), {}, table.get(), &result));
518
519 const DynamicRefTable* dynamic_ref_table = result.getDynamicRefTableForCookie(1);
520 ASSERT_THAT(dynamic_ref_table, NotNull());
521
522 const KeyedVector<String16, uint8_t>& entries = dynamic_ref_table->entries();
523 ssize_t idx = entries.indexOfKey(android::String16("app"));
524 ASSERT_GE(idx, 0);
525 EXPECT_EQ(0x80u, entries.valueAt(idx));
526 }
527
TEST_F(TableFlattenerTest,LongPackageNameIsTruncated)528 TEST_F(TableFlattenerTest, LongPackageNameIsTruncated) {
529 std::string kPackageName(256, 'F');
530
531 std::unique_ptr<IAaptContext> context =
532 test::ContextBuilder().SetCompilationPackage(kPackageName).SetPackageId(0x7f).Build();
533 std::unique_ptr<ResourceTable> table =
534 test::ResourceTableBuilder()
535 .SetPackageId(kPackageName, 0x7f)
536 .AddSimple(kPackageName + ":id/foo", ResourceId(0x7f010000))
537 .Build();
538
539 ResTable result;
540 ASSERT_TRUE(Flatten(context.get(), {}, table.get(), &result));
541
542 ASSERT_EQ(1u, result.getBasePackageCount());
543 EXPECT_EQ(127u, result.getBasePackageName(0).size());
544 }
545
TEST_F(TableFlattenerTest,LongSharedLibraryPackageNameIsIllegal)546 TEST_F(TableFlattenerTest, LongSharedLibraryPackageNameIsIllegal) {
547 std::string kPackageName(256, 'F');
548
549 std::unique_ptr<IAaptContext> context = test::ContextBuilder()
550 .SetCompilationPackage(kPackageName)
551 .SetPackageId(0x7f)
552 .SetPackageType(PackageType::kSharedLib)
553 .Build();
554 std::unique_ptr<ResourceTable> table =
555 test::ResourceTableBuilder()
556 .SetPackageId(kPackageName, 0x7f)
557 .AddSimple(kPackageName + ":id/foo", ResourceId(0x7f010000))
558 .Build();
559
560 ResTable result;
561 ASSERT_FALSE(Flatten(context.get(), {}, table.get(), &result));
562 }
563
TEST_F(TableFlattenerTest,ObfuscatingResourceNamesNoNameCollapseExemptionsSucceeds)564 TEST_F(TableFlattenerTest, ObfuscatingResourceNamesNoNameCollapseExemptionsSucceeds) {
565 std::unique_ptr<ResourceTable> table =
566 test::ResourceTableBuilder()
567 .SetPackageId("com.app.test", 0x7f)
568 .AddSimple("com.app.test:id/one", ResourceId(0x7f020000))
569 .AddSimple("com.app.test:id/two", ResourceId(0x7f020001))
570 .AddValue("com.app.test:id/three", ResourceId(0x7f020002),
571 test::BuildReference("com.app.test:id/one", ResourceId(0x7f020000)))
572 .AddValue("com.app.test:integer/one", ResourceId(0x7f030000),
573 util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 1u))
574 .AddValue("com.app.test:integer/one", test::ParseConfigOrDie("v1"),
575 ResourceId(0x7f030000),
576 util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 2u))
577 .AddString("com.app.test:string/test", ResourceId(0x7f040000), "foo")
578 .AddString("com.app.test:layout/bar", ResourceId(0x7f050000), "res/layout/bar.xml")
579 .Build();
580
581 TableFlattenerOptions options;
582 options.collapse_key_stringpool = true;
583
584 ResTable res_table;
585
586 ASSERT_TRUE(Flatten(context_.get(), options, table.get(), &res_table));
587
588 EXPECT_TRUE(Exists(&res_table, "com.app.test:id/0_resource_name_obfuscated",
589 ResourceId(0x7f020000), {}, Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
590
591 EXPECT_TRUE(Exists(&res_table, "com.app.test:id/0_resource_name_obfuscated",
592 ResourceId(0x7f020001), {}, Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
593
594 EXPECT_TRUE(Exists(&res_table, "com.app.test:id/0_resource_name_obfuscated",
595 ResourceId(0x7f020002), {}, Res_value::TYPE_REFERENCE, 0x7f020000u, 0u));
596
597 EXPECT_TRUE(Exists(&res_table, "com.app.test:integer/0_resource_name_obfuscated",
598 ResourceId(0x7f030000), {}, Res_value::TYPE_INT_DEC, 1u,
599 ResTable_config::CONFIG_VERSION));
600
601 EXPECT_TRUE(Exists(&res_table, "com.app.test:integer/0_resource_name_obfuscated",
602 ResourceId(0x7f030000), test::ParseConfigOrDie("v1"), Res_value::TYPE_INT_DEC,
603 2u, ResTable_config::CONFIG_VERSION));
604
605 std::u16string foo_str = u"foo";
606 ssize_t idx = res_table.getTableStringBlock(0)->indexOfString(foo_str.data(), foo_str.size());
607 ASSERT_GE(idx, 0);
608 EXPECT_TRUE(Exists(&res_table, "com.app.test:string/0_resource_name_obfuscated",
609 ResourceId(0x7f040000), {}, Res_value::TYPE_STRING, (uint32_t)idx, 0u));
610
611 std::u16string bar_path = u"res/layout/bar.xml";
612 idx = res_table.getTableStringBlock(0)->indexOfString(bar_path.data(), bar_path.size());
613 ASSERT_GE(idx, 0);
614 EXPECT_TRUE(Exists(&res_table, "com.app.test:layout/0_resource_name_obfuscated",
615 ResourceId(0x7f050000), {}, Res_value::TYPE_STRING, (uint32_t)idx, 0u));
616 }
617
TEST_F(TableFlattenerTest,ObfuscatingResourceNamesWithNameCollapseExemptionsSucceeds)618 TEST_F(TableFlattenerTest, ObfuscatingResourceNamesWithNameCollapseExemptionsSucceeds) {
619 std::unique_ptr<ResourceTable> table =
620 test::ResourceTableBuilder()
621 .SetPackageId("com.app.test", 0x7f)
622 .AddSimple("com.app.test:id/one", ResourceId(0x7f020000))
623 .AddSimple("com.app.test:id/two", ResourceId(0x7f020001))
624 .AddValue("com.app.test:id/three", ResourceId(0x7f020002),
625 test::BuildReference("com.app.test:id/one", ResourceId(0x7f020000)))
626 .AddValue("com.app.test:integer/one", ResourceId(0x7f030000),
627 util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 1u))
628 .AddValue("com.app.test:integer/one", test::ParseConfigOrDie("v1"),
629 ResourceId(0x7f030000),
630 util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 2u))
631 .AddString("com.app.test:string/test", ResourceId(0x7f040000), "foo")
632 .AddString("com.app.test:layout/bar", ResourceId(0x7f050000), "res/layout/bar.xml")
633 .Build();
634
635 TableFlattenerOptions options;
636 options.collapse_key_stringpool = true;
637 options.name_collapse_exemptions.insert(ResourceName({}, ResourceType::kId, "one"));
638 options.name_collapse_exemptions.insert(ResourceName({}, ResourceType::kString, "test"));
639 ResTable res_table;
640
641 ASSERT_TRUE(Flatten(context_.get(), options, table.get(), &res_table));
642
643 EXPECT_TRUE(Exists(&res_table, "com.app.test:id/one",
644 ResourceId(0x7f020000), {}, Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
645
646 EXPECT_TRUE(Exists(&res_table, "com.app.test:id/0_resource_name_obfuscated",
647 ResourceId(0x7f020001), {}, Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
648
649 EXPECT_TRUE(Exists(&res_table, "com.app.test:id/0_resource_name_obfuscated",
650 ResourceId(0x7f020002), {}, Res_value::TYPE_REFERENCE, 0x7f020000u, 0u));
651
652 // Note that this resource is also named "one", but it's a different type, so gets obfuscated.
653 EXPECT_TRUE(Exists(&res_table, "com.app.test:integer/0_resource_name_obfuscated",
654 ResourceId(0x7f030000), {}, Res_value::TYPE_INT_DEC, 1u,
655 ResTable_config::CONFIG_VERSION));
656
657 EXPECT_TRUE(Exists(&res_table, "com.app.test:integer/0_resource_name_obfuscated",
658 ResourceId(0x7f030000), test::ParseConfigOrDie("v1"), Res_value::TYPE_INT_DEC,
659 2u, ResTable_config::CONFIG_VERSION));
660
661 std::u16string foo_str = u"foo";
662 ssize_t idx = res_table.getTableStringBlock(0)->indexOfString(foo_str.data(), foo_str.size());
663 ASSERT_GE(idx, 0);
664 EXPECT_TRUE(Exists(&res_table, "com.app.test:string/test", ResourceId(0x7f040000), {},
665 Res_value::TYPE_STRING, (uint32_t)idx, 0u));
666
667 std::u16string bar_path = u"res/layout/bar.xml";
668 idx = res_table.getTableStringBlock(0)->indexOfString(bar_path.data(), bar_path.size());
669 ASSERT_GE(idx, 0);
670 EXPECT_TRUE(Exists(&res_table, "com.app.test:layout/0_resource_name_obfuscated",
671 ResourceId(0x7f050000), {}, Res_value::TYPE_STRING, (uint32_t)idx, 0u));
672 }
673
TEST_F(TableFlattenerTest,FlattenOverlayable)674 TEST_F(TableFlattenerTest, FlattenOverlayable) {
675 OverlayableItem overlayable_item(std::make_shared<Overlayable>("TestName", "overlay://theme"));
676 overlayable_item.policies |= PolicyFlags::PRODUCT_PARTITION;
677 overlayable_item.policies |= PolicyFlags::SYSTEM_PARTITION;
678 overlayable_item.policies |= PolicyFlags::VENDOR_PARTITION;
679
680 std::string name = "com.app.test:integer/overlayable";
681 std::unique_ptr<ResourceTable> table =
682 test::ResourceTableBuilder()
683 .SetPackageId("com.app.test", 0x7f)
684 .AddSimple(name, ResourceId(0x7f020000))
685 .SetOverlayable(name, overlayable_item)
686 .Build();
687
688 ResourceTable output_table;
689 ASSERT_TRUE(Flatten(context_.get(), {}, table.get(), &output_table));
690
691 auto search_result = output_table.FindResource(test::ParseNameOrDie(name));
692 ASSERT_TRUE(search_result);
693 ASSERT_THAT(search_result.value().entry, NotNull());
694 ASSERT_TRUE(search_result.value().entry->overlayable_item);
695 OverlayableItem& result_overlayable_item = search_result.value().entry->overlayable_item.value();
696 EXPECT_EQ(result_overlayable_item.policies, PolicyFlags::SYSTEM_PARTITION
697 | PolicyFlags::VENDOR_PARTITION
698 | PolicyFlags::PRODUCT_PARTITION);
699 }
700
TEST_F(TableFlattenerTest,FlattenMultipleOverlayablePolicies)701 TEST_F(TableFlattenerTest, FlattenMultipleOverlayablePolicies) {
702 auto overlayable = std::make_shared<Overlayable>("TestName", "overlay://theme");
703 std::string name_zero = "com.app.test:integer/overlayable_zero_item";
704 OverlayableItem overlayable_item_zero(overlayable);
705 overlayable_item_zero.policies |= PolicyFlags::PRODUCT_PARTITION;
706 overlayable_item_zero.policies |= PolicyFlags::SYSTEM_PARTITION;
707
708 std::string name_one = "com.app.test:integer/overlayable_one_item";
709 OverlayableItem overlayable_item_one(overlayable);
710 overlayable_item_one.policies |= PolicyFlags::PUBLIC;
711
712 std::string name_two = "com.app.test:integer/overlayable_two_item";
713 OverlayableItem overlayable_item_two(overlayable);
714 overlayable_item_two.policies |= PolicyFlags::PRODUCT_PARTITION;
715 overlayable_item_two.policies |= PolicyFlags::SYSTEM_PARTITION;
716 overlayable_item_two.policies |= PolicyFlags::VENDOR_PARTITION;
717
718 std::unique_ptr<ResourceTable> table =
719 test::ResourceTableBuilder()
720 .SetPackageId("com.app.test", 0x7f)
721 .AddSimple(name_zero, ResourceId(0x7f020000))
722 .SetOverlayable(name_zero, overlayable_item_zero)
723 .AddSimple(name_one, ResourceId(0x7f020001))
724 .SetOverlayable(name_one, overlayable_item_one)
725 .AddSimple(name_two, ResourceId(0x7f020002))
726 .SetOverlayable(name_two, overlayable_item_two)
727 .Build();
728
729 ResourceTable output_table;
730 ASSERT_TRUE(Flatten(context_.get(), {}, table.get(), &output_table));
731
732 auto search_result = output_table.FindResource(test::ParseNameOrDie(name_zero));
733 ASSERT_TRUE(search_result);
734 ASSERT_THAT(search_result.value().entry, NotNull());
735 ASSERT_TRUE(search_result.value().entry->overlayable_item);
736 OverlayableItem& overlayable_item = search_result.value().entry->overlayable_item.value();
737 EXPECT_EQ(overlayable_item.policies, PolicyFlags::SYSTEM_PARTITION
738 | PolicyFlags::PRODUCT_PARTITION);
739
740 search_result = output_table.FindResource(test::ParseNameOrDie(name_one));
741 ASSERT_TRUE(search_result);
742 ASSERT_THAT(search_result.value().entry, NotNull());
743 ASSERT_TRUE(search_result.value().entry->overlayable_item);
744 overlayable_item = search_result.value().entry->overlayable_item.value();
745 EXPECT_EQ(overlayable_item.policies, PolicyFlags::PUBLIC);
746
747 search_result = output_table.FindResource(test::ParseNameOrDie(name_two));
748 ASSERT_TRUE(search_result);
749 ASSERT_THAT(search_result.value().entry, NotNull());
750 ASSERT_TRUE(search_result.value().entry->overlayable_item);
751 overlayable_item = search_result.value().entry->overlayable_item.value();
752 EXPECT_EQ(overlayable_item.policies, PolicyFlags::SYSTEM_PARTITION
753 | PolicyFlags::PRODUCT_PARTITION
754 | PolicyFlags::VENDOR_PARTITION);
755 }
756
TEST_F(TableFlattenerTest,FlattenMultipleOverlayable)757 TEST_F(TableFlattenerTest, FlattenMultipleOverlayable) {
758 auto group = std::make_shared<Overlayable>("TestName", "overlay://theme");
759 std::string name_zero = "com.app.test:integer/overlayable_zero";
760 OverlayableItem overlayable_item_zero(group);
761 overlayable_item_zero.policies |= PolicyFlags::PRODUCT_PARTITION;
762 overlayable_item_zero.policies |= PolicyFlags::SYSTEM_PARTITION;
763
764 auto group_one = std::make_shared<Overlayable>("OtherName", "overlay://customization");
765 std::string name_one = "com.app.test:integer/overlayable_one";
766 OverlayableItem overlayable_item_one(group_one);
767 overlayable_item_one.policies |= PolicyFlags::PUBLIC;
768
769 std::string name_two = "com.app.test:integer/overlayable_two";
770 OverlayableItem overlayable_item_two(group);
771 overlayable_item_two.policies |= PolicyFlags::ODM_PARTITION;
772 overlayable_item_two.policies |= PolicyFlags::OEM_PARTITION;
773 overlayable_item_two.policies |= PolicyFlags::VENDOR_PARTITION;
774
775 std::string name_three = "com.app.test:integer/overlayable_three";
776 OverlayableItem overlayable_item_three(group_one);
777 overlayable_item_three.policies |= PolicyFlags::SIGNATURE;
778 overlayable_item_three.policies |= PolicyFlags::ACTOR_SIGNATURE;
779
780 std::unique_ptr<ResourceTable> table =
781 test::ResourceTableBuilder()
782 .SetPackageId("com.app.test", 0x7f)
783 .AddSimple(name_zero, ResourceId(0x7f020000))
784 .SetOverlayable(name_zero, overlayable_item_zero)
785 .AddSimple(name_one, ResourceId(0x7f020001))
786 .SetOverlayable(name_one, overlayable_item_one)
787 .AddSimple(name_two, ResourceId(0x7f020002))
788 .SetOverlayable(name_two, overlayable_item_two)
789 .AddSimple(name_three, ResourceId(0x7f020003))
790 .SetOverlayable(name_three, overlayable_item_three)
791 .Build();
792
793 ResourceTable output_table;
794 ASSERT_TRUE(Flatten(context_.get(), {}, table.get(), &output_table));
795 auto search_result = output_table.FindResource(test::ParseNameOrDie(name_zero));
796 ASSERT_TRUE(search_result);
797 ASSERT_THAT(search_result.value().entry, NotNull());
798 ASSERT_TRUE(search_result.value().entry->overlayable_item);
799 OverlayableItem& result_overlayable = search_result.value().entry->overlayable_item.value();
800 EXPECT_EQ(result_overlayable.overlayable->name, "TestName");
801 EXPECT_EQ(result_overlayable.overlayable->actor, "overlay://theme");
802 EXPECT_EQ(result_overlayable.policies, PolicyFlags::SYSTEM_PARTITION
803 | PolicyFlags::PRODUCT_PARTITION);
804
805 search_result = output_table.FindResource(test::ParseNameOrDie(name_one));
806 ASSERT_TRUE(search_result);
807 ASSERT_THAT(search_result.value().entry, NotNull());
808 ASSERT_TRUE(search_result.value().entry->overlayable_item);
809 result_overlayable = search_result.value().entry->overlayable_item.value();
810 EXPECT_EQ(result_overlayable.overlayable->name, "OtherName");
811 EXPECT_EQ(result_overlayable.overlayable->actor, "overlay://customization");
812 EXPECT_EQ(result_overlayable.policies, PolicyFlags::PUBLIC);
813
814 search_result = output_table.FindResource(test::ParseNameOrDie(name_two));
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, "TestName");
820 EXPECT_EQ(result_overlayable.overlayable->actor, "overlay://theme");
821 EXPECT_EQ(result_overlayable.policies, PolicyFlags::ODM_PARTITION
822 | PolicyFlags::OEM_PARTITION
823 | PolicyFlags::VENDOR_PARTITION);
824
825 search_result = output_table.FindResource(test::ParseNameOrDie(name_three));
826 ASSERT_TRUE(search_result);
827 ASSERT_THAT(search_result.value().entry, NotNull());
828 ASSERT_TRUE(search_result.value().entry->overlayable_item);
829 result_overlayable = search_result.value().entry->overlayable_item.value();
830 EXPECT_EQ(result_overlayable.overlayable->name, "OtherName");
831 EXPECT_EQ(result_overlayable.overlayable->actor, "overlay://customization");
832 EXPECT_EQ(result_overlayable.policies, PolicyFlags::SIGNATURE
833 | PolicyFlags::ACTOR_SIGNATURE);
834 }
835
TEST_F(TableFlattenerTest,FlattenOverlayableNoPolicyFails)836 TEST_F(TableFlattenerTest, FlattenOverlayableNoPolicyFails) {
837 auto group = std::make_shared<Overlayable>("TestName", "overlay://theme");
838 std::string name_zero = "com.app.test:integer/overlayable_zero";
839 OverlayableItem overlayable_item_zero(group);
840
841 std::unique_ptr<ResourceTable> table =
842 test::ResourceTableBuilder()
843 .SetPackageId("com.app.test", 0x7f)
844 .AddSimple(name_zero, ResourceId(0x7f020000))
845 .SetOverlayable(name_zero, overlayable_item_zero)
846 .Build();
847 ResourceTable output_table;
848 ASSERT_FALSE(Flatten(context_.get(), {}, table.get(), &output_table));
849 }
850
851 } // namespace aapt
852