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