• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 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 "optimize/Obfuscator.h"
18 
19 #include <map>
20 #include <memory>
21 #include <string>
22 #include <utility>
23 
24 #include "ResourceTable.h"
25 #include "android-base/file.h"
26 #include "test/Test.h"
27 
28 using ::aapt::test::GetValue;
29 using ::testing::AnyOf;
30 using ::testing::Contains;
31 using ::testing::Eq;
32 using ::testing::HasSubstr;
33 using ::testing::IsFalse;
34 using ::testing::IsTrue;
35 using ::testing::Not;
36 using ::testing::NotNull;
37 
38 namespace aapt {
39 
40 namespace {
41 
GetExtension(android::StringPiece path)42 android::StringPiece GetExtension(android::StringPiece path) {
43   auto iter = std::find(path.begin(), path.end(), '.');
44   return android::StringPiece(iter, path.end() - iter);
45 }
46 
FillTable(aapt::test::ResourceTableBuilder & builder,int start,int end)47 void FillTable(aapt::test::ResourceTableBuilder& builder, int start, int end) {
48   for (int i = start; i < end; i++) {
49     builder.AddFileReference("android:drawable/xmlfile" + std::to_string(i),
50                              "res/drawable/xmlfile" + std::to_string(i) + ".xml");
51   }
52 }
53 
54 class FakeObfuscator : public Obfuscator {
55  public:
FakeObfuscator(OptimizeOptions & optimize_options,const std::unordered_map<std::string,std::string> & shortened_name_map)56   explicit FakeObfuscator(OptimizeOptions& optimize_options,
57                           const std::unordered_map<std::string, std::string>& shortened_name_map)
58       : Obfuscator(optimize_options), shortened_name_map_(shortened_name_map) {
59   }
60 
61  protected:
ShortenFileName(android::StringPiece file_path,int output_length)62   std::string ShortenFileName(android::StringPiece file_path, int output_length) override {
63     return shortened_name_map_[std::string(file_path)];
64   }
65 
66  private:
67   std::unordered_map<std::string, std::string> shortened_name_map_;
68   DISALLOW_COPY_AND_ASSIGN(FakeObfuscator);
69 };
70 
TEST(ObfuscatorTest,FileRefPathsChangedInResourceTable)71 TEST(ObfuscatorTest, FileRefPathsChangedInResourceTable) {
72   std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
73 
74   std::unique_ptr<ResourceTable> table =
75       test::ResourceTableBuilder()
76           .AddFileReference("android:drawable/xmlfile", "res/drawables/xmlfile.xml")
77           .AddFileReference("android:drawable/xmlfile2", "res/drawables/xmlfile2.xml")
78           .AddString("android:string/string", "res/should/still/be/the/same.png")
79           .Build();
80 
81   OptimizeOptions options{.shorten_resource_paths = true};
82   std::map<std::string, std::string>& path_map = options.table_flattener_options.shortened_path_map;
83   ASSERT_TRUE(Obfuscator(options).Consume(context.get(), table.get()));
84 
85   // Expect that the path map is populated
86   ASSERT_THAT(path_map.find("res/drawables/xmlfile.xml"), Not(Eq(path_map.end())));
87   ASSERT_THAT(path_map.find("res/drawables/xmlfile2.xml"), Not(Eq(path_map.end())));
88 
89   // The file paths were changed
90   EXPECT_THAT(path_map.at("res/drawables/xmlfile.xml"), Not(Eq("res/drawables/xmlfile.xml")));
91   EXPECT_THAT(path_map.at("res/drawables/xmlfile2.xml"), Not(Eq("res/drawables/xmlfile2.xml")));
92 
93   // Different file paths should remain different
94   EXPECT_THAT(path_map["res/drawables/xmlfile.xml"],
95               Not(Eq(path_map["res/drawables/xmlfile2.xml"])));
96 
97   FileReference* ref = GetValue<FileReference>(table.get(), "android:drawable/xmlfile");
98   ASSERT_THAT(ref, NotNull());
99   // The map correctly points to the new location of the file
100   EXPECT_THAT(path_map["res/drawables/xmlfile.xml"], Eq(*ref->path));
101 
102   // Strings should not be affected, only file paths
103   EXPECT_THAT(*GetValue<String>(table.get(), "android:string/string")->value,
104               Eq("res/should/still/be/the/same.png"));
105   EXPECT_THAT(path_map.find("res/should/still/be/the/same.png"), Eq(path_map.end()));
106 }
107 
TEST(ObfuscatorTest,SkipColorFileRefPaths)108 TEST(ObfuscatorTest, SkipColorFileRefPaths) {
109   std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
110 
111   std::unique_ptr<ResourceTable> table =
112       test::ResourceTableBuilder()
113           .AddFileReference("android:color/colorlist", "res/color/colorlist.xml")
114           .AddFileReference("android:color/colorlist", "res/color-mdp-v21/colorlist.xml",
115                             test::ParseConfigOrDie("mdp-v21"))
116           .Build();
117 
118   OptimizeOptions options{.shorten_resource_paths = true};
119   std::map<std::string, std::string>& path_map = options.table_flattener_options.shortened_path_map;
120   ASSERT_TRUE(Obfuscator(options).Consume(context.get(), table.get()));
121 
122   // Expect that the path map to not contain the ColorStateList
123   ASSERT_THAT(path_map.find("res/color/colorlist.xml"), Eq(path_map.end()));
124   ASSERT_THAT(path_map.find("res/color-mdp-v21/colorlist.xml"), Eq(path_map.end()));
125 }
126 
TEST(ObfuscatorTest,SkipPathShortenExemptions)127 TEST(ObfuscatorTest, SkipPathShortenExemptions) {
128   std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
129 
130   std::unique_ptr<ResourceTable> table =
131       test::ResourceTableBuilder()
132           .AddFileReference("android:drawable/xmlfile", "res/drawables/xmlfile.xml")
133           .AddFileReference("android:drawable/xmlfile2", "res/drawables/xmlfile2.xml")
134           .AddString("android:string/string", "res/should/still/be/the/same.png")
135           .Build();
136 
137   OptimizeOptions options{.shorten_resource_paths = true};
138   TableFlattenerOptions& flattenerOptions = options.table_flattener_options;
139   flattenerOptions.path_shorten_exemptions.insert(
140       ResourceName({}, ResourceType::kDrawable, "xmlfile"));
141   std::map<std::string, std::string>& path_map = options.table_flattener_options.shortened_path_map;
142   ASSERT_TRUE(Obfuscator(options).Consume(context.get(), table.get()));
143 
144   // Expect that the path map to not contain the first drawable which is in exemption set
145   EXPECT_THAT(path_map.find("res/drawables/xmlfile.xml"), Eq(path_map.end()));
146 
147   // Expect that the path map to contain the second drawable which is not in exemption set
148   EXPECT_THAT(path_map.find("res/drawables/xmlfile2.xml"), Not(Eq(path_map.end())));
149 
150   FileReference* ref = GetValue<FileReference>(table.get(), "android:drawable/xmlfile");
151   ASSERT_THAT(ref, NotNull());
152   ASSERT_THAT(HasFailure(), IsFalse());
153   // The path of first drawable in exemption was not changed
154   EXPECT_THAT("res/drawables/xmlfile.xml", Eq(*ref->path));
155 
156   // The file path of second drawable not in exemption set was changed
157   EXPECT_THAT(path_map.at("res/drawables/xmlfile2.xml"), Not(Eq("res/drawables/xmlfile2.xml")));
158 
159   FileReference* ref2 = GetValue<FileReference>(table.get(), "android:drawable/xmlfile2");
160   ASSERT_THAT(ref, NotNull());
161   // The map of second drawable not in exemption correctly points to the new location of the file
162   EXPECT_THAT(path_map["res/drawables/xmlfile2.xml"], Eq(*ref2->path));
163 }
164 
TEST(ObfuscatorTest,KeepExtensions)165 TEST(ObfuscatorTest, KeepExtensions) {
166   std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
167 
168   std::string original_xml_path = "res/drawable/xmlfile.xml";
169   std::string original_png_path = "res/drawable/pngfile.png";
170 
171   std::unique_ptr<ResourceTable> table =
172       test::ResourceTableBuilder()
173           .AddFileReference("android:color/xmlfile", original_xml_path)
174           .AddFileReference("android:color/pngfile", original_png_path)
175           .Build();
176 
177   OptimizeOptions options{.shorten_resource_paths = true};
178   std::map<std::string, std::string>& path_map = options.table_flattener_options.shortened_path_map;
179   ASSERT_TRUE(Obfuscator(options).Consume(context.get(), table.get()));
180 
181   // Expect that the path map is populated
182   ASSERT_THAT(path_map.find("res/drawable/xmlfile.xml"), Not(Eq(path_map.end())));
183   ASSERT_THAT(path_map.find("res/drawable/pngfile.png"), Not(Eq(path_map.end())));
184 
185   EXPECT_THAT(GetExtension(path_map[original_xml_path]), Eq(android::StringPiece(".xml")));
186   EXPECT_THAT(GetExtension(path_map[original_png_path]), Eq(android::StringPiece(".png")));
187 }
188 
TEST(ObfuscatorTest,ShortenedToReservedWindowsNames)189 TEST(ObfuscatorTest, ShortenedToReservedWindowsNames) {
190   std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
191 
192   std::string original_path_1 = "res/drawable/pngfile_1.png";
193   std::string original_path_2 = "res/drawable/pngfile_2.png";
194   std::string original_path_3 = "res/drawable/pngfile_3.png";
195   std::string original_path_4 = "res/drawable/pngfile_4.png";
196   std::string original_path_5 = "res/drawable/pngfile_5.png";
197   std::string original_path_6 = "res/drawable/pngfile_6.png";
198   std::string original_path_7 = "res/drawable/pngfile_7.png";
199   std::string original_path_8 = "res/drawable/pngfile_8.png";
200   std::string original_path_9 = "res/drawable/pngfile_9.png";
201 
202   std::unique_ptr<ResourceTable> table =
203       test::ResourceTableBuilder()
204           .AddFileReference("android:drawable/pngfile_1", original_path_1)
205           .AddFileReference("android:drawable/pngfile_2", original_path_2)
206           .AddFileReference("android:drawable/pngfile_3", original_path_3)
207           .AddFileReference("android:drawable/pngfile_4", original_path_4)
208           .AddFileReference("android:drawable/pngfile_5", original_path_5)
209           .AddFileReference("android:drawable/pngfile_6", original_path_6)
210           .AddFileReference("android:drawable/pngfile_7", original_path_7)
211           .AddFileReference("android:drawable/pngfile_8", original_path_8)
212           .AddFileReference("android:drawable/pngfile_9", original_path_9)
213           .Build();
214 
215   OptimizeOptions options{.shorten_resource_paths = true};
216   std::map<std::string, std::string>& path_map = options.table_flattener_options.shortened_path_map;
217   auto obfuscator = FakeObfuscator(
218       options,
219       {
220           {original_path_1, "CON"},
221           {original_path_2, "Prn"},
222           {original_path_3, "AuX"},
223           {original_path_4, "nul"},
224           {original_path_5, "cOM"},
225           {original_path_6, "lPt"},
226           {original_path_7, "lPt"},
227           {original_path_8, "lPt"},  // 6, 7, and 8 will be appended with a number to disambiguate
228           {original_path_9, "F0o"},  // This one is not reserved
229       });
230   ASSERT_TRUE(obfuscator.Consume(context.get(), table.get()));
231 
232   // Expect that the path map is populated
233   ASSERT_THAT(path_map.find(original_path_1), Not(Eq(path_map.end())));
234   ASSERT_THAT(path_map.find(original_path_2), Not(Eq(path_map.end())));
235   ASSERT_THAT(path_map.find(original_path_3), Not(Eq(path_map.end())));
236   ASSERT_THAT(path_map.find(original_path_4), Not(Eq(path_map.end())));
237   ASSERT_THAT(path_map.find(original_path_5), Not(Eq(path_map.end())));
238   ASSERT_THAT(path_map.find(original_path_6), Not(Eq(path_map.end())));
239   ASSERT_THAT(path_map.find(original_path_7), Not(Eq(path_map.end())));
240   ASSERT_THAT(path_map.find(original_path_8), Not(Eq(path_map.end())));
241   ASSERT_THAT(path_map.find(original_path_9), Not(Eq(path_map.end())));
242 
243   EXPECT_THAT(path_map[original_path_1], Eq("res/_CON.png"));
244   EXPECT_THAT(path_map[original_path_2], Eq("res/_Prn.png"));
245   EXPECT_THAT(path_map[original_path_3], Eq("res/_AuX.png"));
246   EXPECT_THAT(path_map[original_path_4], Eq("res/_nul.png"));
247   EXPECT_THAT(path_map[original_path_5], Eq("res/_cOM.png"));
248   EXPECT_THAT(path_map[original_path_9], Eq("res/F0o.png"));
249 
250   std::set<std::string> lpt_shortened_names{path_map[original_path_6], path_map[original_path_7],
251                                             path_map[original_path_8]};
252   EXPECT_THAT(lpt_shortened_names, Contains("res/_lPt.png"));
253   EXPECT_THAT(lpt_shortened_names, Contains("res/_lPt1.png"));
254   EXPECT_THAT(lpt_shortened_names, Contains("res/_lPt2.png"));
255 }
256 
TEST(ObfuscatorTest,DeterministicallyHandleCollisions)257 TEST(ObfuscatorTest, DeterministicallyHandleCollisions) {
258   std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
259 
260   // 4000 resources is the limit at which the hash space is expanded to 3
261   // letters to reduce collisions, we want as many collisions as possible thus
262   // N-1.
263   const auto kNumResources = 3999;
264   const auto kNumTries = 5;
265 
266   test::ResourceTableBuilder builder1;
267   FillTable(builder1, 0, kNumResources);
268   std::unique_ptr<ResourceTable> table1 = builder1.Build();
269   OptimizeOptions options{.shorten_resource_paths = true};
270   std::map<std::string, std::string>& expected_mapping =
271       options.table_flattener_options.shortened_path_map;
272   ASSERT_TRUE(Obfuscator(options).Consume(context.get(), table1.get()));
273 
274   // We are trying to ensure lack of non-determinism, it is not simple to prove
275   // a negative, thus we must try the test a few times so that the test itself
276   // is non-flaky. Basically create the pathmap 5 times from the same set of
277   // resources but a different order of addition and then ensure they are always
278   // mapped to the same short path.
279   for (int i = 0; i < kNumTries; i++) {
280     test::ResourceTableBuilder builder2;
281     // This loop adds resources to the resource table in the range of
282     // [0:kNumResources).  Adding the file references in different order makes
283     // non-determinism more likely to surface. Thus we add resources
284     // [start_index:kNumResources) first then [0:start_index). We also use a
285     // different start_index each run.
286     int start_index = (kNumResources / kNumTries) * i;
287     FillTable(builder2, start_index, kNumResources);
288     FillTable(builder2, 0, start_index);
289     std::unique_ptr<ResourceTable> table2 = builder2.Build();
290 
291     OptimizeOptions actualOptimizerOptions{.shorten_resource_paths = true};
292     TableFlattenerOptions& actual_options = actualOptimizerOptions.table_flattener_options;
293     std::map<std::string, std::string>& actual_mapping = actual_options.shortened_path_map;
294     ASSERT_TRUE(Obfuscator(actualOptimizerOptions).Consume(context.get(), table2.get()));
295 
296     for (auto& item : actual_mapping) {
297       ASSERT_THAT(expected_mapping[item.first], Eq(item.second));
298     }
299   }
300 }
301 
TEST(ObfuscatorTest,DumpIdResourceMap)302 TEST(ObfuscatorTest, DumpIdResourceMap) {
303   std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
304 
305   OverlayableItem overlayable_item(std::make_shared<Overlayable>("TestName", "overlay://theme"));
306   overlayable_item.policies |= PolicyFlags::PRODUCT_PARTITION;
307   overlayable_item.policies |= PolicyFlags::SYSTEM_PARTITION;
308   overlayable_item.policies |= PolicyFlags::VENDOR_PARTITION;
309 
310   std::string original_xml_path = "res/drawable/xmlfile.xml";
311   std::string original_png_path = "res/drawable/pngfile.png";
312 
313   std::string name = "com.app.test:string/overlayable";
314   std::unique_ptr<ResourceTable> table =
315       test::ResourceTableBuilder()
316           .AddFileReference("android:color/xmlfile", original_xml_path)
317           .AddFileReference("android:color/pngfile", original_png_path)
318           .AddValue("com.app.test:color/mycolor", aapt::ResourceId(0x7f020000),
319                     aapt::util::make_unique<aapt::BinaryPrimitive>(
320                         uint8_t(android::Res_value::TYPE_INT_COLOR_ARGB8), 0xffaabbcc))
321           .AddString("com.app.test:string/mystring", ResourceId(0x7f030000), "hi")
322           .AddString("com.app.test:string/in_exemption", ResourceId(0x7f030001), "Hi")
323           .AddString(name, ResourceId(0x7f030002), "HI")
324           .SetOverlayable(name, overlayable_item)
325           .Build();
326 
327   OptimizeOptions options{.shorten_resource_paths = true};
328   TableFlattenerOptions& flattenerOptions = options.table_flattener_options;
329   flattenerOptions.collapse_key_stringpool = true;
330   flattenerOptions.name_collapse_exemptions.insert(
331       ResourceName({}, ResourceType::kString, "in_exemption"));
332   auto& id_resource_map = flattenerOptions.id_resource_map;
333   ASSERT_TRUE(Obfuscator(options).Consume(context.get(), table.get()));
334 
335   // Expect that the id resource name map is populated
336   ASSERT_THAT(id_resource_map.find(0x7f020000), Not(Eq(id_resource_map.end())));
337   EXPECT_THAT(id_resource_map.at(0x7f020000), Eq("mycolor"));
338   ASSERT_THAT(id_resource_map.find(0x7f030000), Not(Eq(id_resource_map.end())));
339   EXPECT_THAT(id_resource_map.at(0x7f030000), Eq("mystring"));
340   EXPECT_THAT(id_resource_map.find(0x7f030001), Eq(id_resource_map.end()));
341   EXPECT_THAT(id_resource_map.find(0x7f030002), Eq(id_resource_map.end()));
342 }
343 
TEST(ObfuscatorTest,IsEnabledWithDefaultOption)344 TEST(ObfuscatorTest, IsEnabledWithDefaultOption) {
345   OptimizeOptions options;
346   Obfuscator obfuscatorWithDefaultOption(options);
347   ASSERT_THAT(obfuscatorWithDefaultOption.IsEnabled(), Eq(false));
348 }
349 
TEST(ObfuscatorTest,IsEnabledWithShortenPathOption)350 TEST(ObfuscatorTest, IsEnabledWithShortenPathOption) {
351   OptimizeOptions options{.shorten_resource_paths = true};
352   Obfuscator obfuscatorWithShortenPathOption(options);
353   ASSERT_THAT(obfuscatorWithShortenPathOption.IsEnabled(), Eq(true));
354 }
355 
TEST(ObfuscatorTest,IsEnabledWithCollapseStringPoolOption)356 TEST(ObfuscatorTest, IsEnabledWithCollapseStringPoolOption) {
357   OptimizeOptions options;
358   options.table_flattener_options.collapse_key_stringpool = true;
359   Obfuscator obfuscatorWithCollapseStringPoolOption(options);
360   ASSERT_THAT(obfuscatorWithCollapseStringPoolOption.IsEnabled(), Eq(true));
361 }
362 
TEST(ObfuscatorTest,IsEnabledWithShortenPathAndCollapseStringPoolOption)363 TEST(ObfuscatorTest, IsEnabledWithShortenPathAndCollapseStringPoolOption) {
364   OptimizeOptions options{.shorten_resource_paths = true};
365   options.table_flattener_options.collapse_key_stringpool = true;
366   Obfuscator obfuscatorWithCollapseStringPoolOption(options);
367   ASSERT_THAT(obfuscatorWithCollapseStringPoolOption.IsEnabled(), Eq(true));
368 }
369 
getProtocolBufferTableUnderTest()370 static std::unique_ptr<ResourceTable> getProtocolBufferTableUnderTest() {
371   std::string original_xml_path = "res/drawable/xmlfile.xml";
372   std::string original_png_path = "res/drawable/pngfile.png";
373 
374   return test::ResourceTableBuilder()
375       .AddFileReference("com.app.test:drawable/xmlfile", original_xml_path)
376       .AddFileReference("com.app.test:drawable/pngfile", original_png_path)
377       .AddValue("com.app.test:color/mycolor", aapt::ResourceId(0x7f020000),
378                 aapt::util::make_unique<aapt::BinaryPrimitive>(
379                     uint8_t(android::Res_value::TYPE_INT_COLOR_ARGB8), 0xffaabbcc))
380       .AddString("com.app.test:string/mystring", ResourceId(0x7f030000), "hello world")
381       .Build();
382 }
383 
TEST(ObfuscatorTest,WriteObfuscationMapInProtocolBufferFormat)384 TEST(ObfuscatorTest, WriteObfuscationMapInProtocolBufferFormat) {
385   OptimizeOptions options{.shorten_resource_paths = true};
386   options.table_flattener_options.collapse_key_stringpool = true;
387   Obfuscator obfuscator(options);
388   ASSERT_TRUE(obfuscator.Consume(test::ContextBuilder().Build().get(),
389                                  getProtocolBufferTableUnderTest().get()));
390 
391   const auto map_path = testing::TempDir() + "/obfuscated_map.pb";
392   ASSERT_TRUE(obfuscator.WriteObfuscationMap(map_path));
393 
394   std::string pbOut;
395   ASSERT_TRUE(android::base::ReadFileToString(map_path, &pbOut, false /* follow_symlinks */));
396   EXPECT_THAT(pbOut, HasSubstr("drawable/xmlfile.xml"));
397   EXPECT_THAT(pbOut, HasSubstr("drawable/pngfile.png"));
398   EXPECT_THAT(pbOut, HasSubstr("mycolor"));
399   EXPECT_THAT(pbOut, HasSubstr("mystring"));
400   pb::ResourceMappings resourceMappings;
401   ASSERT_THAT(resourceMappings.ParseFromString(pbOut), IsTrue());
402   ASSERT_THAT(resourceMappings.collapsed_names().resource_names_size(), Eq(2));
403   auto& resource_names = resourceMappings.collapsed_names().resource_names();
404   EXPECT_THAT(resource_names.at(0).name(), AnyOf(Eq("mycolor"), Eq("mystring")));
405   EXPECT_THAT(resource_names.at(1).name(), AnyOf(Eq("mycolor"), Eq("mystring")));
406   auto& shortened_paths = resourceMappings.shortened_paths();
407   EXPECT_THAT(shortened_paths.resource_paths_size(), Eq(2));
408   EXPECT_THAT(shortened_paths.resource_paths(0).original_path(),
409               AnyOf(Eq("res/drawable/pngfile.png"), Eq("res/drawable/xmlfile.xml")));
410   EXPECT_THAT(shortened_paths.resource_paths(1).original_path(),
411               AnyOf(Eq("res/drawable/pngfile.png"), Eq("res/drawable/xmlfile.xml")));
412 }
413 
TEST(ObfuscatorTest,WriteObfuscatingMapWithNonEnabledOption)414 TEST(ObfuscatorTest, WriteObfuscatingMapWithNonEnabledOption) {
415   OptimizeOptions options;
416   Obfuscator obfuscator(options);
417   ASSERT_TRUE(obfuscator.Consume(test::ContextBuilder().Build().get(),
418                                  getProtocolBufferTableUnderTest().get()));
419 
420   const auto map_path = testing::TempDir() + "/obfuscated_map.pb";
421   ASSERT_TRUE(obfuscator.WriteObfuscationMap(map_path));
422 
423   std::string pbOut;
424   ASSERT_TRUE(android::base::ReadFileToString(map_path, &pbOut, false /* follow_symlinks */));
425   ASSERT_THAT(pbOut, Eq(""));
426 }
427 
428 }  // namespace
429 
430 }  // namespace aapt
431