1 /*
2  * Copyright (C) 2017 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 "java/ProguardRules.h"
18 #include "link/Linkers.h"
19 
20 #include "io/StringStream.h"
21 #include "test/Test.h"
22 
23 using ::aapt::io::StringOutputStream;
24 using ::android::ConfigDescription;
25 using ::testing::HasSubstr;
26 using ::testing::Not;
27 
28 namespace aapt {
29 
GetKeepSetString(const proguard::KeepSet & set,bool minimal_rules)30 std::string GetKeepSetString(const proguard::KeepSet& set, bool minimal_rules) {
31   std::string out;
32   StringOutputStream sout(&out);
33   proguard::WriteKeepSet(set, &sout, minimal_rules);
34   sout.Flush();
35   return out;
36 }
37 
TEST(ProguardRulesTest,ManifestRuleDefaultConstructorOnly)38 TEST(ProguardRulesTest, ManifestRuleDefaultConstructorOnly) {
39   std::unique_ptr<xml::XmlResource> manifest = test::BuildXmlDom(R"(
40       <manifest xmlns:android="http://schemas.android.com/apk/res/android">
41         <application
42             android:appComponentFactory="com.foo.BarAppComponentFactory"
43             android:backupAgent="com.foo.BarBackupAgent"
44             android:name="com.foo.BarApplication"
45             android:zygotePreloadName="com.foo.BarZygotePreload"
46             >
47           <activity android:name="com.foo.BarActivity"/>
48           <service android:name="com.foo.BarService"/>
49           <receiver android:name="com.foo.BarReceiver"/>
50           <provider android:name="com.foo.BarProvider"/>
51         </application>
52         <instrumentation android:name="com.foo.BarInstrumentation"/>
53       </manifest>)");
54 
55   proguard::KeepSet set;
56   ASSERT_TRUE(proguard::CollectProguardRulesForManifest(manifest.get(), &set, false));
57 
58   std::string actual = GetKeepSetString(set, /** minimal_rules */ false);
59   EXPECT_THAT(actual, HasSubstr("-keep class com.foo.BarAppComponentFactory { <init>(); }"));
60   EXPECT_THAT(actual, HasSubstr("-keep class com.foo.BarBackupAgent { <init>(); }"));
61   EXPECT_THAT(actual, HasSubstr("-keep class com.foo.BarApplication { <init>(); }"));
62   EXPECT_THAT(actual, HasSubstr("-keep class com.foo.BarActivity { <init>(); }"));
63   EXPECT_THAT(actual, HasSubstr("-keep class com.foo.BarService { <init>(); }"));
64   EXPECT_THAT(actual, HasSubstr("-keep class com.foo.BarReceiver { <init>(); }"));
65   EXPECT_THAT(actual, HasSubstr("-keep class com.foo.BarProvider { <init>(); }"));
66   EXPECT_THAT(actual, HasSubstr("-keep class com.foo.BarInstrumentation { <init>(); }"));
67   EXPECT_THAT(actual, HasSubstr("-keep class com.foo.BarZygotePreload { <init>(); }"));
68 
69   actual = GetKeepSetString(set, /** minimal_rules */ true);
70   EXPECT_THAT(actual, HasSubstr("-keep class com.foo.BarAppComponentFactory { <init>(); }"));
71   EXPECT_THAT(actual, HasSubstr("-keep class com.foo.BarBackupAgent { <init>(); }"));
72   EXPECT_THAT(actual, HasSubstr("-keep class com.foo.BarApplication { <init>(); }"));
73   EXPECT_THAT(actual, HasSubstr("-keep class com.foo.BarActivity { <init>(); }"));
74   EXPECT_THAT(actual, HasSubstr("-keep class com.foo.BarService { <init>(); }"));
75   EXPECT_THAT(actual, HasSubstr("-keep class com.foo.BarReceiver { <init>(); }"));
76   EXPECT_THAT(actual, HasSubstr("-keep class com.foo.BarProvider { <init>(); }"));
77   EXPECT_THAT(actual, HasSubstr("-keep class com.foo.BarInstrumentation { <init>(); }"));
78   EXPECT_THAT(actual, HasSubstr("-keep class com.foo.BarZygotePreload { <init>(); }"));
79 }
80 
81 TEST(ProguardRulesTest, FragmentNameRuleIsEmitted) {
82   std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
83   std::unique_ptr<xml::XmlResource> layout = test::BuildXmlDom(R"(
84       <fragment xmlns:android="http://schemas.android.com/apk/res/android"
85           android:name="com.foo.Bar"/>)");
86   layout->file.name = test::ParseNameOrDie("layout/foo");
87 
88   proguard::KeepSet set;
89   ASSERT_TRUE(proguard::CollectProguardRules(context.get(), layout.get(), &set));
90 
91   std::string actual = GetKeepSetString(set, /** minimal_rules */ false);
92   EXPECT_THAT(actual, HasSubstr("-keep class com.foo.Bar { <init>(...); }"));
93 
94   actual = GetKeepSetString(set, /** minimal_rules */ true);
95   EXPECT_THAT(actual, HasSubstr("-keep class com.foo.Bar { <init>(); }"));
96 }
97 
98 TEST(ProguardRulesTest, FragmentClassRuleIsEmitted) {
99   std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
100   std::unique_ptr<xml::XmlResource> layout =
101       test::BuildXmlDom(R"(<fragment class="com.foo.Bar"/>)");
102   layout->file.name = test::ParseNameOrDie("layout/foo");
103 
104   proguard::KeepSet set;
105   ASSERT_TRUE(proguard::CollectProguardRules(context.get(), layout.get(), &set));
106 
107   std::string actual = GetKeepSetString(set, /** minimal_rules */ false);
108   EXPECT_THAT(actual, HasSubstr("-keep class com.foo.Bar { <init>(...); }"));
109 
110   actual = GetKeepSetString(set, /** minimal_rules */ true);
111   EXPECT_THAT(actual, HasSubstr("-keep class com.foo.Bar { <init>(); }"));
112 }
113 
114 TEST(ProguardRulesTest, FragmentNameAndClassRulesAreEmitted) {
115   std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
116   std::unique_ptr<xml::XmlResource> layout = test::BuildXmlDom(R"(
117       <fragment xmlns:android="http://schemas.android.com/apk/res/android"
118           android:name="com.foo.Baz"
119           class="com.foo.Bar"/>)");
120   layout->file.name = test::ParseNameOrDie("layout/foo");
121 
122   proguard::KeepSet set;
123   ASSERT_TRUE(proguard::CollectProguardRules(context.get(), layout.get(), &set));
124 
125   std::string actual = GetKeepSetString(set, /** minimal_rules */ false);
126   EXPECT_THAT(actual, HasSubstr("-keep class com.foo.Bar { <init>(...); }"));
127   EXPECT_THAT(actual, HasSubstr("-keep class com.foo.Baz { <init>(...); }"));
128 
129   actual = GetKeepSetString(set, /** minimal_rules */ true);
130   EXPECT_THAT(actual, HasSubstr("-keep class com.foo.Bar { <init>(); }"));
131   EXPECT_THAT(actual, HasSubstr("-keep class com.foo.Baz { <init>(); }"));
132 }
133 
134 TEST(ProguardRulesTest, FragmentContainerViewNameRuleIsEmitted) {
135   std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
136   std::unique_ptr<xml::XmlResource> layout = test::BuildXmlDom(R"(
137       <androidx.fragment.app.FragmentContainerView
138           xmlns:android="http://schemas.android.com/apk/res/android"
139           android:name="com.foo.Bar"/>)");
140   layout->file.name = test::ParseNameOrDie("layout/foo");
141 
142   proguard::KeepSet set;
143   ASSERT_TRUE(proguard::CollectProguardRules(context.get(), layout.get(), &set));
144 
145   std::string actual = GetKeepSetString(set, /** minimal_rules */ false);
146   EXPECT_THAT(actual, HasSubstr("-keep class com.foo.Bar { <init>(...); }"));
147 
148   actual = GetKeepSetString(set, /** minimal_rules */ true);
149   EXPECT_THAT(actual, HasSubstr("-keep class com.foo.Bar { <init>(); }"));
150 }
151 
152 TEST(ProguardRulesTest, FragmentContainerViewClassRuleIsEmitted) {
153   std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
154   std::unique_ptr<xml::XmlResource> layout =
155       test::BuildXmlDom(R"(<androidx.fragment.app.FragmentContainerView class="com.foo.Bar"/>)");
156   layout->file.name = test::ParseNameOrDie("layout/foo");
157 
158   proguard::KeepSet set;
159   ASSERT_TRUE(proguard::CollectProguardRules(context.get(), layout.get(), &set));
160 
161   std::string actual = GetKeepSetString(set, /** minimal_rules */ false);
162   EXPECT_THAT(actual, HasSubstr("-keep class com.foo.Bar { <init>(...); }"));
163 
164   actual = GetKeepSetString(set, /** minimal_rules */ true);
165   EXPECT_THAT(actual, HasSubstr("-keep class com.foo.Bar { <init>(); }"));
166 }
167 
168 TEST(ProguardRulesTest, FragmentContainerViewNameAndClassRulesAreEmitted) {
169   std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
170   std::unique_ptr<xml::XmlResource> layout = test::BuildXmlDom(R"(
171       <androidx.fragment.app.FragmentContainerView
172           xmlns:android="http://schemas.android.com/apk/res/android"
173           android:name="com.foo.Baz"
174           class="com.foo.Bar"/>)");
175   layout->file.name = test::ParseNameOrDie("layout/foo");
176 
177   proguard::KeepSet set;
178   ASSERT_TRUE(proguard::CollectProguardRules(context.get(), layout.get(), &set));
179 
180   std::string actual = GetKeepSetString(set, /** minimal_rules */ false);
181   EXPECT_THAT(actual, HasSubstr("-keep class com.foo.Bar { <init>(...); }"));
182   EXPECT_THAT(actual, HasSubstr("-keep class com.foo.Baz { <init>(...); }"));
183 
184   actual = GetKeepSetString(set, /** minimal_rules */ true);
185   EXPECT_THAT(actual, HasSubstr("-keep class com.foo.Bar { <init>(); }"));
186   EXPECT_THAT(actual, HasSubstr("-keep class com.foo.Baz { <init>(); }"));
187 }
188 
189 TEST(ProguardRulesTest, NavigationFragmentNameAndClassRulesAreEmitted) {
190   std::unique_ptr<IAaptContext> context = test::ContextBuilder()
191       .SetCompilationPackage("com.base").Build();
192   std::unique_ptr<xml::XmlResource> navigation = test::BuildXmlDom(R"(
193       <navigation
194           xmlns:android="http://schemas.android.com/apk/res/android"
195           xmlns:app="http://schemas.android.com/apk/res-auto">
196           <custom android:id="@id/foo"
197               android:name="com.package.Foo"/>
198           <fragment android:id="@id/bar"
199               android:name="com.package.Bar">
200               <nested android:id="@id/nested"
201                   android:name=".Nested"/>
202           </fragment>
203       </navigation>
204   )");
205 
206   navigation->file.name = test::ParseNameOrDie("navigation/graph.xml");
207 
208   proguard::KeepSet set;
209   ASSERT_TRUE(proguard::CollectProguardRules(context.get(), navigation.get(), &set));
210 
211   std::string actual = GetKeepSetString(set, /** minimal_rules */ false);
212   EXPECT_THAT(actual, HasSubstr("-keep class com.package.Foo { <init>(...); }"));
213   EXPECT_THAT(actual, HasSubstr("-keep class com.package.Bar { <init>(...); }"));
214   EXPECT_THAT(actual, HasSubstr("-keep class com.base.Nested { <init>(...); }"));
215 
216   actual = GetKeepSetString(set, /** minimal_rules */ true);
217   EXPECT_THAT(actual, HasSubstr("-keep class com.package.Foo { <init>(...); }"));
218   EXPECT_THAT(actual, HasSubstr("-keep class com.package.Bar { <init>(...); }"));
219   EXPECT_THAT(actual, HasSubstr("-keep class com.base.Nested { <init>(...); }"));
220 }
221 
222 TEST(ProguardRulesTest, CustomViewRulesAreEmitted) {
223   std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
224   std::unique_ptr<xml::XmlResource> layout = test::BuildXmlDom(R"(
225       <View xmlns:android="http://schemas.android.com/apk/res/android">
226         <com.foo.Bar />
227       </View>)");
228   layout->file.name = test::ParseNameOrDie("layout/foo");
229 
230   proguard::KeepSet set;
231   ASSERT_TRUE(proguard::CollectProguardRules(context.get(), layout.get(), &set));
232 
233   std::string actual = GetKeepSetString(set, /** minimal_rules */ false);
234   EXPECT_THAT(actual, HasSubstr("-keep class com.foo.Bar { <init>(...); }"));
235 
236   actual = GetKeepSetString(set, /** minimal_rules */ true);
237   EXPECT_THAT(actual, HasSubstr(
238       "-keep class com.foo.Bar { <init>(android.content.Context, android.util.AttributeSet); }"));
239 }
240 
241 TEST(ProguardRulesTest, IncludedLayoutRulesAreConditional) {
242   std::unique_ptr<xml::XmlResource> bar_layout = test::BuildXmlDom(R"(
243       <View xmlns:android="http://schemas.android.com/apk/res/android">
244         <com.foo.Bar />
245       </View>)");
246   bar_layout->file.name = test::ParseNameOrDie("com.foo:layout/bar");
247 
248   ResourceTable table;
249   StdErrDiagnostics errDiagnostics;
250   table.AddResource(bar_layout->file.name, ConfigDescription::DefaultConfig(), "",
251                     util::make_unique<FileReference>(), &errDiagnostics);
252 
253   std::unique_ptr<IAaptContext> context =
254       test::ContextBuilder()
255           .SetCompilationPackage("com.foo")
256           .AddSymbolSource(util::make_unique<ResourceTableSymbolSource>(&table))
257           .Build();
258 
259   std::unique_ptr<xml::XmlResource> foo_layout = test::BuildXmlDom(R"(
260       <View xmlns:android="http://schemas.android.com/apk/res/android">
261         <include layout="@layout/bar" />
262       </View>)");
263   foo_layout->file.name = test::ParseNameOrDie("com.foo:layout/foo");
264 
265   XmlReferenceLinker xml_linker;
266   ASSERT_TRUE(xml_linker.Consume(context.get(), bar_layout.get()));
267   ASSERT_TRUE(xml_linker.Consume(context.get(), foo_layout.get()));
268 
269   proguard::KeepSet set = proguard::KeepSet(true);
270   ASSERT_TRUE(proguard::CollectProguardRules(context.get(), bar_layout.get(), &set));
271   ASSERT_TRUE(proguard::CollectProguardRules(context.get(), foo_layout.get(), &set));
272 
273   std::string actual = GetKeepSetString(set, /** minimal_rules */ false);
274   EXPECT_THAT(actual, HasSubstr("-if class **.R$layout"));
275   EXPECT_THAT(actual, HasSubstr("-keep class com.foo.Bar { <init>(...); }"));
276   EXPECT_THAT(actual, HasSubstr("int foo"));
277   EXPECT_THAT(actual, HasSubstr("int bar"));
278 
279   actual = GetKeepSetString(set, /** minimal_rules */ true);
280   EXPECT_THAT(actual, HasSubstr("-if class **.R$layout"));
281   EXPECT_THAT(actual, HasSubstr(
282     "-keep class com.foo.Bar { <init>(android.content.Context, android.util.AttributeSet); }"));
283   EXPECT_THAT(actual, HasSubstr("int foo"));
284   EXPECT_THAT(actual, HasSubstr("int bar"));
285 }
286 
287 TEST(ProguardRulesTest, AliasedLayoutRulesAreConditional) {
288   std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
289   std::unique_ptr<xml::XmlResource> layout = test::BuildXmlDom(R"(
290       <View xmlns:android="http://schemas.android.com/apk/res/android">
291         <com.foo.Bar />
292       </View>)");
293   layout->file.name = test::ParseNameOrDie("layout/foo");
294 
295   proguard::KeepSet set = proguard::KeepSet(true);
296   set.AddReference({test::ParseNameOrDie("layout/bar"), {}}, layout->file.name);
297   ASSERT_TRUE(proguard::CollectProguardRules(context.get(), layout.get(), &set));
298 
299   std::string actual = GetKeepSetString(set, /** minimal_rules */ false);
300   EXPECT_THAT(actual, HasSubstr(
301       "-keep class com.foo.Bar { <init>(...); }"));
302   EXPECT_THAT(actual, HasSubstr("-if class **.R$layout"));
303   EXPECT_THAT(actual, HasSubstr("int foo"));
304   EXPECT_THAT(actual, HasSubstr("int bar"));
305 
306   actual = GetKeepSetString(set, /** minimal_rules */ true);
307   EXPECT_THAT(actual, HasSubstr(
308     "-keep class com.foo.Bar { <init>(android.content.Context, android.util.AttributeSet); }"));
309   EXPECT_THAT(actual, HasSubstr("-if class **.R$layout"));
310   EXPECT_THAT(actual, HasSubstr("int foo"));
311   EXPECT_THAT(actual, HasSubstr("int bar"));
312 }
313 
314 TEST(ProguardRulesTest, NonLayoutReferencesAreUnconditional) {
315   std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
316   std::unique_ptr<xml::XmlResource> layout = test::BuildXmlDom(R"(
317       <View xmlns:android="http://schemas.android.com/apk/res/android">
318         <com.foo.Bar />
319       </View>)");
320   layout->file.name = test::ParseNameOrDie("layout/foo");
321 
322   proguard::KeepSet set = proguard::KeepSet(true);
323   set.AddReference({test::ParseNameOrDie("style/MyStyle"), {}}, layout->file.name);
324   ASSERT_TRUE(proguard::CollectProguardRules(context.get(), layout.get(), &set));
325 
326   std::string actual = GetKeepSetString(set, /** minimal_rules */ false);
327   EXPECT_THAT(actual, Not(HasSubstr("-if")));
328   EXPECT_THAT(actual, HasSubstr("-keep class com.foo.Bar { <init>(...); }"));
329 
330   actual = GetKeepSetString(set, /** minimal_rules */ true);
331   EXPECT_THAT(actual, Not(HasSubstr("-if")));
332   EXPECT_THAT(actual, HasSubstr(
333     "-keep class com.foo.Bar { <init>(android.content.Context, android.util.AttributeSet); }"));
334 }
335 
336 TEST(ProguardRulesTest, ViewOnClickRuleIsEmitted) {
337   std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
338   std::unique_ptr<xml::XmlResource> layout = test::BuildXmlDom(R"(
339       <View xmlns:android="http://schemas.android.com/apk/res/android"
340           android:onClick="bar_method" />)");
341   layout->file.name = test::ParseNameOrDie("layout/foo");
342 
343   proguard::KeepSet set;
344   ASSERT_TRUE(proguard::CollectProguardRules(context.get(), layout.get(), &set));
345 
346   std::string actual = GetKeepSetString(set,  /** minimal_rules */ false);
347   EXPECT_THAT(actual, HasSubstr(
348       "-keepclassmembers class * { *** bar_method(android.view.View); }"));
349 
350   actual = GetKeepSetString(set,  /** minimal_rules */ true);
351   EXPECT_THAT(actual, HasSubstr(
352     "-keepclassmembers class * { *** bar_method(android.view.View); }"));
353 }
354 
355 TEST(ProguardRulesTest, MenuRulesAreEmitted) {
356   std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
357   std::unique_ptr<xml::XmlResource> menu = test::BuildXmlDom(R"(
358       <menu xmlns:android="http://schemas.android.com/apk/res/android">
359         <item android:onClick="on_click"
360             android:actionViewClass="com.foo.Bar"
361             android:actionProviderClass="com.foo.Baz"
362             android:name="com.foo.Bat" />
363       </menu>)");
364   menu->file.name = test::ParseNameOrDie("menu/foo");
365 
366   proguard::KeepSet set;
367   ASSERT_TRUE(proguard::CollectProguardRules(context.get(), menu.get(), &set));
368 
369   std::string actual = GetKeepSetString(set,  /** minimal_rules */ false);
370   EXPECT_THAT(actual, HasSubstr(
371     "-keepclassmembers class * { *** on_click(android.view.MenuItem); }"));
372   EXPECT_THAT(actual, HasSubstr("-keep class com.foo.Bar { <init>(...); }"));
373   EXPECT_THAT(actual, HasSubstr("-keep class com.foo.Baz { <init>(...); }"));
374   EXPECT_THAT(actual, Not(HasSubstr("com.foo.Bat")));
375 
376   actual = GetKeepSetString(set,  /** minimal_rules */ true);
377   EXPECT_THAT(actual, HasSubstr(
378     "-keepclassmembers class * { *** on_click(android.view.MenuItem); }"));
379   EXPECT_THAT(actual, HasSubstr("-keep class com.foo.Bar { <init>(android.content.Context); }"));
380   EXPECT_THAT(actual, HasSubstr("-keep class com.foo.Baz { <init>(android.content.Context); }"));
381   EXPECT_THAT(actual, Not(HasSubstr("com.foo.Bat")));
382 }
383 
384 TEST(ProguardRulesTest, TransitionPathMotionRulesAreEmitted) {
385   std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
386   std::unique_ptr<xml::XmlResource> transition = test::BuildXmlDom(R"(
387       <changeBounds>
388         <pathMotion class="com.foo.Bar"/>
389       </changeBounds>)");
390   transition->file.name = test::ParseNameOrDie("transition/foo");
391 
392   proguard::KeepSet set;
393   ASSERT_TRUE(proguard::CollectProguardRules(context.get(), transition.get(), &set));
394 
395   std::string actual = GetKeepSetString(set, /** minimal_rules */ false);
396   EXPECT_THAT(actual, HasSubstr("-keep class com.foo.Bar { <init>(...); }"));
397 
398   actual = GetKeepSetString(set, /** minimal_rules */ true);
399   EXPECT_THAT(actual, HasSubstr(
400     "-keep class com.foo.Bar { <init>(android.content.Context, android.util.AttributeSet); }"));
401 }
402 
403 TEST(ProguardRulesTest, TransitionRulesAreEmitted) {
404   std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
405   std::unique_ptr<xml::XmlResource> transitionSet = test::BuildXmlDom(R"(
406       <transitionSet>
407         <transition class="com.foo.Bar"/>
408       </transitionSet>)");
409   transitionSet->file.name = test::ParseNameOrDie("transition/foo");
410 
411   proguard::KeepSet set;
412   ASSERT_TRUE(proguard::CollectProguardRules(context.get(), transitionSet.get(), &set));
413 
414   std::string actual = GetKeepSetString(set, /** minimal_rules */ false);
415   EXPECT_THAT(actual, HasSubstr("-keep class com.foo.Bar { <init>(...); }"));
416 
417   actual = GetKeepSetString(set, /** minimal_rules */ true);
418   EXPECT_THAT(actual, HasSubstr(
419     "-keep class com.foo.Bar { <init>(android.content.Context, android.util.AttributeSet); }"));
420 }
421 
422 TEST(ProguardRulesTest, UsageLocationComparator) {
423   proguard::UsageLocation location1 = {{"pkg", ResourceType::kAttr, "x"}};
424   proguard::UsageLocation location2 = {{"pkg", ResourceType::kAttr, "y"}};
425 
426   EXPECT_EQ(location1 < location2, true);
427   EXPECT_EQ(location2 < location1, false);
428 }
429 
430 }  // namespace aapt
431