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, false);
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, NavigationFragmentNameAndClassRulesAreEmitted) {
135   std::unique_ptr<IAaptContext> context = test::ContextBuilder()
136       .SetCompilationPackage("com.base").Build();
137   std::unique_ptr<xml::XmlResource> navigation = test::BuildXmlDom(R"(
138       <navigation
139           xmlns:android="http://schemas.android.com/apk/res/android"
140           xmlns:app="http://schemas.android.com/apk/res-auto">
141           <custom android:id="@id/foo"
142               android:name="com.package.Foo"/>
143           <fragment android:id="@id/bar"
144               android:name="com.package.Bar">
145               <nested android:id="@id/nested"
146                   android:name=".Nested"/>
147           </fragment>
148       </navigation>
149   )");
150 
151   navigation->file.name = test::ParseNameOrDie("navigation/graph.xml");
152 
153   proguard::KeepSet set;
154   ASSERT_TRUE(proguard::CollectProguardRules(context.get(), navigation.get(), &set));
155 
156   std::string actual = GetKeepSetString(set, /** minimal_rules */ false);
157   EXPECT_THAT(actual, HasSubstr("-keep class com.package.Foo { <init>(...); }"));
158   EXPECT_THAT(actual, HasSubstr("-keep class com.package.Bar { <init>(...); }"));
159   EXPECT_THAT(actual, HasSubstr("-keep class com.base.Nested { <init>(...); }"));
160 
161   actual = GetKeepSetString(set, /** minimal_rules */ true);
162   EXPECT_THAT(actual, HasSubstr("-keep class com.package.Foo { <init>(...); }"));
163   EXPECT_THAT(actual, HasSubstr("-keep class com.package.Bar { <init>(...); }"));
164   EXPECT_THAT(actual, HasSubstr("-keep class com.base.Nested { <init>(...); }"));
165 }
166 
167 TEST(ProguardRulesTest, CustomViewRulesAreEmitted) {
168   std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
169   std::unique_ptr<xml::XmlResource> layout = test::BuildXmlDom(R"(
170       <View xmlns:android="http://schemas.android.com/apk/res/android">
171         <com.foo.Bar />
172       </View>)");
173   layout->file.name = test::ParseNameOrDie("layout/foo");
174 
175   proguard::KeepSet set;
176   ASSERT_TRUE(proguard::CollectProguardRules(context.get(), layout.get(), &set));
177 
178   std::string actual = GetKeepSetString(set, /** minimal_rules */ false);
179   EXPECT_THAT(actual, HasSubstr("-keep class com.foo.Bar { <init>(...); }"));
180 
181   actual = GetKeepSetString(set, /** minimal_rules */ true);
182   EXPECT_THAT(actual, HasSubstr(
183       "-keep class com.foo.Bar { <init>(android.content.Context, android.util.AttributeSet); }"));
184 }
185 
186 TEST(ProguardRulesTest, IncludedLayoutRulesAreConditional) {
187   std::unique_ptr<xml::XmlResource> bar_layout = test::BuildXmlDom(R"(
188       <View xmlns:android="http://schemas.android.com/apk/res/android">
189         <com.foo.Bar />
190       </View>)");
191   bar_layout->file.name = test::ParseNameOrDie("com.foo:layout/bar");
192 
193   ResourceTable table;
194   StdErrDiagnostics errDiagnostics;
195   table.AddResource(bar_layout->file.name, ConfigDescription::DefaultConfig(), "",
196                     util::make_unique<FileReference>(), &errDiagnostics);
197 
198   std::unique_ptr<IAaptContext> context =
199       test::ContextBuilder()
200           .SetCompilationPackage("com.foo")
201           .AddSymbolSource(util::make_unique<ResourceTableSymbolSource>(&table))
202           .Build();
203 
204   std::unique_ptr<xml::XmlResource> foo_layout = test::BuildXmlDom(R"(
205       <View xmlns:android="http://schemas.android.com/apk/res/android">
206         <include layout="@layout/bar" />
207       </View>)");
208   foo_layout->file.name = test::ParseNameOrDie("com.foo:layout/foo");
209 
210   XmlReferenceLinker xml_linker;
211   ASSERT_TRUE(xml_linker.Consume(context.get(), bar_layout.get()));
212   ASSERT_TRUE(xml_linker.Consume(context.get(), foo_layout.get()));
213 
214   proguard::KeepSet set = proguard::KeepSet(true);
215   ASSERT_TRUE(proguard::CollectProguardRules(context.get(), bar_layout.get(), &set));
216   ASSERT_TRUE(proguard::CollectProguardRules(context.get(), foo_layout.get(), &set));
217 
218   std::string actual = GetKeepSetString(set, /** minimal_rules */ false);
219   EXPECT_THAT(actual, HasSubstr("-if class **.R$layout"));
220   EXPECT_THAT(actual, HasSubstr("-keep class com.foo.Bar { <init>(...); }"));
221   EXPECT_THAT(actual, HasSubstr("int foo"));
222   EXPECT_THAT(actual, HasSubstr("int bar"));
223 
224   actual = GetKeepSetString(set, /** minimal_rules */ true);
225   EXPECT_THAT(actual, HasSubstr("-if class **.R$layout"));
226   EXPECT_THAT(actual, HasSubstr(
227     "-keep class com.foo.Bar { <init>(android.content.Context, android.util.AttributeSet); }"));
228   EXPECT_THAT(actual, HasSubstr("int foo"));
229   EXPECT_THAT(actual, HasSubstr("int bar"));
230 }
231 
232 TEST(ProguardRulesTest, AliasedLayoutRulesAreConditional) {
233   std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
234   std::unique_ptr<xml::XmlResource> layout = test::BuildXmlDom(R"(
235       <View xmlns:android="http://schemas.android.com/apk/res/android">
236         <com.foo.Bar />
237       </View>)");
238   layout->file.name = test::ParseNameOrDie("layout/foo");
239 
240   proguard::KeepSet set = proguard::KeepSet(true);
241   set.AddReference({test::ParseNameOrDie("layout/bar"), {}}, layout->file.name);
242   ASSERT_TRUE(proguard::CollectProguardRules(context.get(), layout.get(), &set));
243 
244   std::string actual = GetKeepSetString(set, /** minimal_rules */ false);
245   EXPECT_THAT(actual, HasSubstr(
246       "-keep class com.foo.Bar { <init>(...); }"));
247   EXPECT_THAT(actual, HasSubstr("-if class **.R$layout"));
248   EXPECT_THAT(actual, HasSubstr("int foo"));
249   EXPECT_THAT(actual, HasSubstr("int bar"));
250 
251   actual = GetKeepSetString(set, /** minimal_rules */ true);
252   EXPECT_THAT(actual, HasSubstr(
253     "-keep class com.foo.Bar { <init>(android.content.Context, android.util.AttributeSet); }"));
254   EXPECT_THAT(actual, HasSubstr("-if class **.R$layout"));
255   EXPECT_THAT(actual, HasSubstr("int foo"));
256   EXPECT_THAT(actual, HasSubstr("int bar"));
257 }
258 
259 TEST(ProguardRulesTest, NonLayoutReferencesAreUnconditional) {
260   std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
261   std::unique_ptr<xml::XmlResource> layout = test::BuildXmlDom(R"(
262       <View xmlns:android="http://schemas.android.com/apk/res/android">
263         <com.foo.Bar />
264       </View>)");
265   layout->file.name = test::ParseNameOrDie("layout/foo");
266 
267   proguard::KeepSet set = proguard::KeepSet(true);
268   set.AddReference({test::ParseNameOrDie("style/MyStyle"), {}}, layout->file.name);
269   ASSERT_TRUE(proguard::CollectProguardRules(context.get(), layout.get(), &set));
270 
271   std::string actual = GetKeepSetString(set, /** minimal_rules */ false);
272   EXPECT_THAT(actual, Not(HasSubstr("-if")));
273   EXPECT_THAT(actual, HasSubstr("-keep class com.foo.Bar { <init>(...); }"));
274 
275   actual = GetKeepSetString(set, /** minimal_rules */ true);
276   EXPECT_THAT(actual, Not(HasSubstr("-if")));
277   EXPECT_THAT(actual, HasSubstr(
278     "-keep class com.foo.Bar { <init>(android.content.Context, android.util.AttributeSet); }"));
279 }
280 
281 TEST(ProguardRulesTest, ViewOnClickRuleIsEmitted) {
282   std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
283   std::unique_ptr<xml::XmlResource> layout = test::BuildXmlDom(R"(
284       <View xmlns:android="http://schemas.android.com/apk/res/android"
285           android:onClick="bar_method" />)");
286   layout->file.name = test::ParseNameOrDie("layout/foo");
287 
288   proguard::KeepSet set;
289   ASSERT_TRUE(proguard::CollectProguardRules(context.get(), layout.get(), &set));
290 
291   std::string actual = GetKeepSetString(set,  /** minimal_rules */ false);
292   EXPECT_THAT(actual, HasSubstr(
293       "-keepclassmembers class * { *** bar_method(android.view.View); }"));
294 
295   actual = GetKeepSetString(set,  /** minimal_rules */ true);
296   EXPECT_THAT(actual, HasSubstr(
297     "-keepclassmembers class * { *** bar_method(android.view.View); }"));
298 }
299 
300 TEST(ProguardRulesTest, MenuRulesAreEmitted) {
301   std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
302   std::unique_ptr<xml::XmlResource> menu = test::BuildXmlDom(R"(
303       <menu xmlns:android="http://schemas.android.com/apk/res/android">
304         <item android:onClick="on_click"
305             android:actionViewClass="com.foo.Bar"
306             android:actionProviderClass="com.foo.Baz"
307             android:name="com.foo.Bat" />
308       </menu>)");
309   menu->file.name = test::ParseNameOrDie("menu/foo");
310 
311   proguard::KeepSet set;
312   ASSERT_TRUE(proguard::CollectProguardRules(context.get(), menu.get(), &set));
313 
314   std::string actual = GetKeepSetString(set,  /** minimal_rules */ false);
315   EXPECT_THAT(actual, HasSubstr(
316     "-keepclassmembers class * { *** on_click(android.view.MenuItem); }"));
317   EXPECT_THAT(actual, HasSubstr("-keep class com.foo.Bar { <init>(...); }"));
318   EXPECT_THAT(actual, HasSubstr("-keep class com.foo.Baz { <init>(...); }"));
319   EXPECT_THAT(actual, Not(HasSubstr("com.foo.Bat")));
320 
321   actual = GetKeepSetString(set,  /** minimal_rules */ true);
322   EXPECT_THAT(actual, HasSubstr(
323     "-keepclassmembers class * { *** on_click(android.view.MenuItem); }"));
324   EXPECT_THAT(actual, HasSubstr("-keep class com.foo.Bar { <init>(android.content.Context); }"));
325   EXPECT_THAT(actual, HasSubstr("-keep class com.foo.Baz { <init>(android.content.Context); }"));
326   EXPECT_THAT(actual, Not(HasSubstr("com.foo.Bat")));
327 }
328 
329 TEST(ProguardRulesTest, MenuRulesAreEmittedForActionClasses) {
330   std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
331   std::unique_ptr<xml::XmlResource> menu = test::BuildXmlDom(R"(
332       <menu xmlns:android="http://schemas.android.com/apk/res/android"
333             xmlns:app="http://schemas.android.com/apk/res-auto">
334         <item android:id="@+id/my_item"
335             app:actionViewClass="com.foo.Bar"
336             app:actionProviderClass="com.foo.Baz" />
337       </menu>)");
338   menu->file.name = test::ParseNameOrDie("menu/foo");
339 
340   proguard::KeepSet set;
341   ASSERT_TRUE(proguard::CollectProguardRules(context.get(), menu.get(), &set));
342 
343   std::string actual = GetKeepSetString(set, /** minimal_rules */ false);
344   EXPECT_THAT(actual, HasSubstr("-keep class com.foo.Bar"));
345   EXPECT_THAT(actual, HasSubstr("-keep class com.foo.Baz"));
346 }
347 
348 TEST(ProguardRulesTest, TransitionPathMotionRulesAreEmitted) {
349   std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
350   std::unique_ptr<xml::XmlResource> transition = test::BuildXmlDom(R"(
351       <changeBounds>
352         <pathMotion class="com.foo.Bar"/>
353       </changeBounds>)");
354   transition->file.name = test::ParseNameOrDie("transition/foo");
355 
356   proguard::KeepSet set;
357   ASSERT_TRUE(proguard::CollectProguardRules(context.get(), transition.get(), &set));
358 
359   std::string actual = GetKeepSetString(set, /** minimal_rules */ false);
360   EXPECT_THAT(actual, HasSubstr("-keep class com.foo.Bar { <init>(...); }"));
361 
362   actual = GetKeepSetString(set, /** minimal_rules */ true);
363   EXPECT_THAT(actual, HasSubstr(
364     "-keep class com.foo.Bar { <init>(android.content.Context, android.util.AttributeSet); }"));
365 }
366 
367 TEST(ProguardRulesTest, TransitionRulesAreEmitted) {
368   std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
369   std::unique_ptr<xml::XmlResource> transitionSet = test::BuildXmlDom(R"(
370       <transitionSet>
371         <transition class="com.foo.Bar"/>
372       </transitionSet>)");
373   transitionSet->file.name = test::ParseNameOrDie("transition/foo");
374 
375   proguard::KeepSet set;
376   ASSERT_TRUE(proguard::CollectProguardRules(context.get(), transitionSet.get(), &set));
377 
378   std::string actual = GetKeepSetString(set, /** minimal_rules */ false);
379   EXPECT_THAT(actual, HasSubstr("-keep class com.foo.Bar { <init>(...); }"));
380 
381   actual = GetKeepSetString(set, /** minimal_rules */ true);
382   EXPECT_THAT(actual, HasSubstr(
383     "-keep class com.foo.Bar { <init>(android.content.Context, android.util.AttributeSet); }"));
384 }
385 
386 TEST(ProguardRulesTest, UsageLocationComparator) {
387   proguard::UsageLocation location1 = {{"pkg", ResourceType::kAttr, "x"}};
388   proguard::UsageLocation location2 = {{"pkg", ResourceType::kAttr, "y"}};
389 
390   EXPECT_EQ(location1 < location2, true);
391   EXPECT_EQ(location2 < location1, false);
392 }
393 
394 }  // namespace aapt
395