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