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