• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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 <regex>
18 #include <string>
19 
20 #include "LoadedApk.h"
21 #include "cmd/Dump.h"
22 #include "io/StringStream.h"
23 #include "test/Common.h"
24 #include "test/Test.h"
25 #include "text/Printer.h"
26 
27 using ::aapt::io::StringOutputStream;
28 using ::aapt::text::Printer;
29 using testing::Eq;
30 using testing::Ne;
31 
32 namespace aapt {
33 
34 using FlaggedResourcesTest = CommandTestFixture;
35 
36 static android::NoOpDiagnostics noop_diag;
37 
DumpStringPoolToString(LoadedApk * loaded_apk,std::string * output)38 void DumpStringPoolToString(LoadedApk* loaded_apk, std::string* output) {
39   StringOutputStream output_stream(output);
40   Printer printer(&output_stream);
41 
42   DumpStringsCommand command(&printer, &noop_diag);
43   ASSERT_EQ(command.Dump(loaded_apk), 0);
44   output_stream.Flush();
45 }
46 
DumpResourceTableToString(LoadedApk * loaded_apk,std::string * output)47 void DumpResourceTableToString(LoadedApk* loaded_apk, std::string* output) {
48   StringOutputStream output_stream(output);
49   Printer printer(&output_stream);
50 
51   DumpTableCommand command(&printer, &noop_diag);
52   ASSERT_EQ(command.Dump(loaded_apk), 0);
53   output_stream.Flush();
54 }
55 
DumpChunksToString(LoadedApk * loaded_apk,std::string * output)56 void DumpChunksToString(LoadedApk* loaded_apk, std::string* output) {
57   StringOutputStream output_stream(output);
58   Printer printer(&output_stream);
59 
60   DumpChunks command(&printer, &noop_diag);
61   ASSERT_EQ(command.Dump(loaded_apk), 0);
62   output_stream.Flush();
63 }
64 
DumpXmlTreeToString(LoadedApk * loaded_apk,std::string file,std::string * output)65 void DumpXmlTreeToString(LoadedApk* loaded_apk, std::string file, std::string* output) {
66   StringOutputStream output_stream(output);
67   Printer printer(&output_stream);
68 
69   auto xml = loaded_apk->LoadXml(file, &noop_diag);
70   ASSERT_NE(xml, nullptr);
71   Debug::DumpXml(*xml, &printer);
72   output_stream.Flush();
73 }
74 
TEST_F(FlaggedResourcesTest,DisabledStringRemovedFromPool)75 TEST_F(FlaggedResourcesTest, DisabledStringRemovedFromPool) {
76   auto apk_path = file::BuildPath({android::base::GetExecutableDirectory(), "resapp.apk"});
77   auto loaded_apk = LoadedApk::LoadApkFromPath(apk_path, &noop_diag);
78 
79   std::string output;
80   DumpStringPoolToString(loaded_apk.get(), &output);
81 
82   std::string excluded = "DONTFIND";
83   ASSERT_EQ(output.find(excluded), std::string::npos);
84 }
85 
TEST_F(FlaggedResourcesTest,DisabledResourcesRemovedFromTable)86 TEST_F(FlaggedResourcesTest, DisabledResourcesRemovedFromTable) {
87   auto apk_path = file::BuildPath({android::base::GetExecutableDirectory(), "resapp.apk"});
88   auto loaded_apk = LoadedApk::LoadApkFromPath(apk_path, &noop_diag);
89 
90   std::string output;
91   DumpResourceTableToString(loaded_apk.get(), &output);
92   ASSERT_EQ(output.find("bool4"), std::string::npos);
93   ASSERT_EQ(output.find("str1"), std::string::npos);
94   ASSERT_EQ(output.find("layout2"), std::string::npos);
95   ASSERT_EQ(output.find("removedpng"), std::string::npos);
96 }
97 
TEST_F(FlaggedResourcesTest,DisabledResourcesRemovedFromTableChunks)98 TEST_F(FlaggedResourcesTest, DisabledResourcesRemovedFromTableChunks) {
99   auto apk_path = file::BuildPath({android::base::GetExecutableDirectory(), "resapp.apk"});
100   auto loaded_apk = LoadedApk::LoadApkFromPath(apk_path, &noop_diag);
101 
102   std::string output;
103   DumpChunksToString(loaded_apk.get(), &output);
104 
105   ASSERT_EQ(output.find("bool4"), std::string::npos);
106   ASSERT_EQ(output.find("str1"), std::string::npos);
107   ASSERT_EQ(output.find("layout2"), std::string::npos);
108   ASSERT_EQ(output.find("removedpng"), std::string::npos);
109 }
110 
TEST_F(FlaggedResourcesTest,DisabledResourcesInRJava)111 TEST_F(FlaggedResourcesTest, DisabledResourcesInRJava) {
112   auto r_path = file::BuildPath({android::base::GetExecutableDirectory(), "resource-flagging-java",
113                                  "com", "android", "intenal", "flaggedresources", "R.java"});
114   std::string r_contents;
115   ::android::base::ReadFileToString(r_path, &r_contents);
116 
117   ASSERT_NE(r_contents.find("public static final int bool4"), std::string::npos);
118   ASSERT_NE(r_contents.find("public static final int str1"), std::string::npos);
119 }
120 
TEST_F(FlaggedResourcesTest,TwoValuesSameDisabledFlag)121 TEST_F(FlaggedResourcesTest, TwoValuesSameDisabledFlag) {
122   test::TestDiagnosticsImpl diag;
123   const std::string compiled_files_dir = GetTestPath("compiled");
124   ASSERT_FALSE(CompileFile(
125       GetTestPath("res/values/values.xml"),
126       R"(<resources xmlns:android="http://schemas.android.com/apk/res/android">
127            <bool name="bool1" android:featureFlag="test.package.falseFlag">false</bool>
128            <bool name="bool1" android:featureFlag="test.package.falseFlag">true</bool>
129          </resources>)",
130       compiled_files_dir, &diag,
131       {"--feature-flags", "test.package.falseFlag:ro=false,test.package.trueFlag:ro=true"}));
132   ASSERT_TRUE(diag.GetLog().contains("duplicate value for resource 'bool/bool1'"));
133 }
134 
135 TEST_F(FlaggedResourcesTest, TwoValuesSameDisabledFlagDifferentFiles) {
136   test::TestDiagnosticsImpl diag;
137   const std::string compiled_files_dir = GetTestPath("compiled");
138   ASSERT_TRUE(CompileFile(
139       GetTestPath("res/values/values1.xml"),
140       R"(<resources xmlns:android="http://schemas.android.com/apk/res/android">
141            <bool name="bool1" android:featureFlag="test.package.falseFlag">false</bool>
142          </resources>)",
143       compiled_files_dir, &diag,
144       {"--feature-flags", "test.package.falseFlag:ro=false,test.package.trueFlag:ro=true"}));
145   ASSERT_TRUE(CompileFile(
146       GetTestPath("res/values/values2.xml"),
147       R"(<resources xmlns:android="http://schemas.android.com/apk/res/android">
148            <bool name="bool1" android:featureFlag="test.package.falseFlag">true</bool>
149          </resources>)",
150       compiled_files_dir, &diag,
151       {"--feature-flags", "test.package.falseFlag:ro=false,test.package.trueFlag:ro=true"}));
152   const std::string out_apk = GetTestPath("out.apk");
153   std::vector<std::string> link_args = {
154       "--manifest",
155       GetDefaultManifest(),
156       "-o",
157       out_apk,
158   };
159 
160   ASSERT_FALSE(Link(link_args, compiled_files_dir, &diag));
161   ASSERT_TRUE(diag.GetLog().contains("duplicate value for resource 'bool1'"));
162 }
163 
164 TEST_F(FlaggedResourcesTest, EnabledXmlELementAttributeRemoved) {
165   auto apk_path = file::BuildPath({android::base::GetExecutableDirectory(), "resapp.apk"});
166   auto loaded_apk = LoadedApk::LoadApkFromPath(apk_path, &noop_diag);
167 
168   std::string output;
169   DumpXmlTreeToString(loaded_apk.get(), "res/layout-v36/layout1.xml", &output);
170   ASSERT_FALSE(output.contains("test.package.trueFlag"));
171   ASSERT_TRUE(output.contains("FIND_ME"));
172   ASSERT_TRUE(output.contains("test.package.readWriteFlag"));
173 }
174 
175 TEST_F(FlaggedResourcesTest, ReadWriteFlagInPathFails) {
176   test::TestDiagnosticsImpl diag;
177   const std::string compiled_files_dir = GetTestPath("compiled");
178   ASSERT_FALSE(CompileFile(GetTestPath("res/values/flag(!test.package.rwFlag)/bools.xml"),
179                            R"(<resources>
180                                 <bool name="bool1">false</bool>
181                               </resources>)",
182                            compiled_files_dir, &diag,
183                            {"--feature-flags", "test.package.rwFlag=false"}));
184 
185   ASSERT_TRUE(diag.GetLog().contains(
186       "Only read only flags may be used with resources: test.package.rwFlag"));
187 }
188 
189 TEST_F(FlaggedResourcesTest, ReadWriteFlagInXmlGetsFlagged) {
190   auto apk_path = file::BuildPath({android::base::GetExecutableDirectory(), "resapp.apk"});
191   auto loaded_apk = LoadedApk::LoadApkFromPath(apk_path, &noop_diag);
192 
193   std::string output;
194   DumpChunksToString(loaded_apk.get(), &output);
195 
196   // The actual line looks something like:
197   // [ResTable_entry] id: 0x0000 name: layout1 keyIndex: 14 size: 8 flags: 0x0010
198   //
199   // This regex matches that line and captures the name and the flag value for checking.
200   std::regex regex("[0-9a-zA-Z:_\\]\\[ ]+name: ([0-9a-zA-Z]+)[0-9a-zA-Z: ]+flags: (0x\\d{4})");
201   std::smatch match;
202 
203   std::stringstream ss(output);
204   std::string line;
205   bool found = false;
206   int fields_flagged = 0;
207   while (std::getline(ss, line)) {
208     bool first_line = false;
209     if (line.contains("config: v36")) {
210       std::getline(ss, line);
211       first_line = true;
212     }
213     if (!line.contains("flags")) {
214       continue;
215     }
216     if (std::regex_search(line, match, regex) && (match.size() == 3)) {
217       unsigned int hex_value;
218       std::stringstream hex_ss;
219       hex_ss << std::hex << match[2];
220       hex_ss >> hex_value;
221       if (hex_value & android::ResTable_entry::FLAG_USES_FEATURE_FLAGS) {
222         fields_flagged++;
223         if (first_line && match[1] == "layout1") {
224           found = true;
225         }
226       }
227     }
228   }
229 
230   ASSERT_TRUE(found) << "No entry for layout1 at v36 with FLAG_USES_FEATURE_FLAGS bit set";
231   // There should only be 2 entry that has the FLAG_USES_FEATURE_FLAGS bit of flags set to 1, the
232   // three versions of the layout file that has flags
233   ASSERT_EQ(fields_flagged, 3);
234 }
235 
236 }  // namespace aapt
237