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