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 "java/ProguardRules.h"
18 
19 #include <memory>
20 #include <string>
21 
22 #include "android-base/macros.h"
23 #include "androidfw/StringPiece.h"
24 
25 #include "JavaClassGenerator.h"
26 #include "ResourceUtils.h"
27 #include "ValueVisitor.h"
28 #include "text/Printer.h"
29 #include "util/Util.h"
30 #include "xml/XmlDom.h"
31 
32 using ::aapt::io::OutputStream;
33 using ::aapt::text::Printer;
34 
35 namespace aapt {
36 namespace proguard {
37 
38 class BaseVisitor : public xml::Visitor {
39  public:
40   using xml::Visitor::Visit;
41 
BaseVisitor(const ResourceFile & file,KeepSet * keep_set)42   BaseVisitor(const ResourceFile& file, KeepSet* keep_set) : BaseVisitor(file, keep_set, "...") {
43   }
44 
BaseVisitor(const ResourceFile & file,KeepSet * keep_set,const std::string & ctor_signature)45   BaseVisitor(const ResourceFile& file, KeepSet* keep_set, const std::string& ctor_signature)
46       : file_(file), keep_set_(keep_set), ctor_signature_(ctor_signature) {
47   }
48 
Visit(xml::Element * node)49   void Visit(xml::Element* node) override {
50     if (!node->namespace_uri.empty()) {
51       Maybe<xml::ExtractedPackage> maybe_package =
52           xml::ExtractPackageFromNamespace(node->namespace_uri);
53       if (maybe_package) {
54         // This is a custom view, let's figure out the class name from this.
55         std::string package = maybe_package.value().package + "." + node->name;
56         if (util::IsJavaClassName(package)) {
57           AddClass(node->line_number, package, ctor_signature_);
58         }
59       }
60     } else if (util::IsJavaClassName(node->name)) {
61       AddClass(node->line_number, node->name, ctor_signature_);
62     }
63 
64     for (const auto& child : node->children) {
65       child->Accept(this);
66     }
67 
68     for (const auto& attr : node->attributes) {
69       if (attr.compiled_value) {
70         auto ref = ValueCast<Reference>(attr.compiled_value.get());
71         if (ref) {
72           AddReference(node->line_number, ref);
73         }
74       }
75     }
76   }
77 
78  protected:
79   ResourceFile file_;
80   KeepSet* keep_set_;
81   std::string ctor_signature_;
82 
AddClass(size_t line_number,const std::string & class_name,const std::string & ctor_signature)83   virtual void AddClass(size_t line_number, const std::string& class_name,
84                         const std::string& ctor_signature) {
85     keep_set_->AddConditionalClass({file_.name, file_.source.WithLine(line_number)},
86         {class_name, ctor_signature});
87   }
88 
AddMethod(size_t line_number,const std::string & method_name,const std::string & method_signature)89   void AddMethod(size_t line_number, const std::string& method_name,
90                  const std::string& method_signature) {
91     keep_set_->AddMethod({file_.name, file_.source.WithLine(line_number)},
92         {method_name, method_signature});
93   }
94 
AddReference(size_t line_number,Reference * ref)95   void AddReference(size_t line_number, Reference* ref) {
96     if (ref && ref->name) {
97       ResourceName ref_name = ref->name.value();
98       if (ref_name.package.empty()) {
99         ref_name = ResourceName(file_.name.package, ref_name.type, ref_name.entry);
100       }
101       keep_set_->AddReference({file_.name, file_.source.WithLine(line_number)}, ref_name);
102     }
103   }
104 
105  private:
106   DISALLOW_COPY_AND_ASSIGN(BaseVisitor);
107 
108 };
109 
110 class LayoutVisitor : public BaseVisitor {
111  public:
LayoutVisitor(const ResourceFile & file,KeepSet * keep_set)112   LayoutVisitor(const ResourceFile& file, KeepSet* keep_set)
113       : BaseVisitor(file, keep_set, "android.content.Context, android.util.AttributeSet") {
114   }
115 
Visit(xml::Element * node)116   void Visit(xml::Element* node) override {
117     bool is_view = false;
118     bool is_fragment = false;
119     if (node->namespace_uri.empty()) {
120       if (node->name == "view") {
121         is_view = true;
122       } else if (node->name == "fragment") {
123         is_fragment = true;
124       }
125     } else if (node->namespace_uri == xml::kSchemaAndroid) {
126       is_fragment = node->name == "fragment";
127     }
128 
129     for (const auto& attr : node->attributes) {
130       if (attr.namespace_uri.empty() && attr.name == "class") {
131         if (util::IsJavaClassName(attr.value)) {
132           if (is_view) {
133             AddClass(node->line_number, attr.value,
134                 "android.content.Context, android.util.AttributeSet");
135           } else if (is_fragment) {
136             AddClass(node->line_number, attr.value, "");
137           }
138         }
139       } else if (attr.namespace_uri == xml::kSchemaAndroid && attr.name == "name") {
140         if (is_fragment && util::IsJavaClassName(attr.value)) {
141           AddClass(node->line_number, attr.value, "");
142         }
143       } else if (attr.namespace_uri == xml::kSchemaAndroid && attr.name == "onClick") {
144         AddMethod(node->line_number, attr.value, "android.view.View");
145       }
146     }
147 
148     BaseVisitor::Visit(node);
149   }
150 
151  private:
152   DISALLOW_COPY_AND_ASSIGN(LayoutVisitor);
153 };
154 
155 class MenuVisitor : public BaseVisitor {
156  public:
MenuVisitor(const ResourceFile & file,KeepSet * keep_set)157   MenuVisitor(const ResourceFile& file, KeepSet* keep_set) : BaseVisitor(file, keep_set) {
158   }
159 
Visit(xml::Element * node)160   void Visit(xml::Element* node) override {
161     if (node->namespace_uri.empty() && node->name == "item") {
162       for (const auto& attr : node->attributes) {
163         // AppCompat-v7 defines its own versions of Android attributes if
164         // they're defined after SDK 7 (the below are from 11 and 14,
165         // respectively), so don't bother checking the XML namespace.
166         //
167         // Given the names of the containing XML files and the attribute
168         // names, it's unlikely that keeping these classes would be wrong.
169         if ((attr.name == "actionViewClass" || attr.name == "actionProviderClass") &&
170             util::IsJavaClassName(attr.value)) {
171           AddClass(node->line_number, attr.value, "android.content.Context");
172         }
173 
174         if (attr.namespace_uri == xml::kSchemaAndroid && attr.name == "onClick") {
175           AddMethod(node->line_number, attr.value, "android.view.MenuItem");
176         }
177       }
178     }
179 
180     BaseVisitor::Visit(node);
181   }
182 
183  private:
184   DISALLOW_COPY_AND_ASSIGN(MenuVisitor);
185 };
186 
187 class XmlResourceVisitor : public BaseVisitor {
188  public:
XmlResourceVisitor(const ResourceFile & file,KeepSet * keep_set)189   XmlResourceVisitor(const ResourceFile& file, KeepSet* keep_set) : BaseVisitor(file, keep_set) {
190   }
191 
Visit(xml::Element * node)192   void Visit(xml::Element* node) override {
193     bool check_fragment = false;
194     if (node->namespace_uri.empty()) {
195       check_fragment =
196           node->name == "PreferenceScreen" || node->name == "header";
197     }
198 
199     if (check_fragment) {
200       xml::Attribute* attr =
201           node->FindAttribute(xml::kSchemaAndroid, "fragment");
202       if (attr && util::IsJavaClassName(attr->value)) {
203         AddClass(node->line_number, attr->value, "");
204       }
205     }
206 
207     BaseVisitor::Visit(node);
208   }
209 
210  private:
211   DISALLOW_COPY_AND_ASSIGN(XmlResourceVisitor);
212 };
213 
214 class NavigationVisitor : public BaseVisitor {
215  public:
NavigationVisitor(const ResourceFile & file,KeepSet * keep_set,const std::string & package)216   NavigationVisitor(const ResourceFile& file, KeepSet* keep_set, const std::string& package)
217       : BaseVisitor(file, keep_set), package_(package) {
218   }
219 
Visit(xml::Element * node)220   void Visit(xml::Element* node) override {
221     const auto& attr = node->FindAttribute(xml::kSchemaAndroid, "name");
222     if (attr != nullptr && !attr->value.empty()) {
223       std::string name = (attr->value[0] == '.') ? package_ + attr->value : attr->value;
224       if (util::IsJavaClassName(name)) {
225         AddClass(node->line_number, name, "...");
226       }
227     }
228 
229     BaseVisitor::Visit(node);
230   }
231 
232  private:
233   DISALLOW_COPY_AND_ASSIGN(NavigationVisitor);
234   const std::string package_;
235 };
236 
237 class TransitionVisitor : public BaseVisitor {
238  public:
TransitionVisitor(const ResourceFile & file,KeepSet * keep_set)239   TransitionVisitor(const ResourceFile& file, KeepSet* keep_set) : BaseVisitor(file, keep_set) {
240   }
241 
Visit(xml::Element * node)242   void Visit(xml::Element* node) override {
243     bool check_class =
244         node->namespace_uri.empty() && (node->name == "transition" || node->name == "pathMotion");
245     if (check_class) {
246       xml::Attribute* attr = node->FindAttribute({}, "class");
247       if (attr && util::IsJavaClassName(attr->value)) {
248         AddClass(node->line_number, attr->value,
249             "android.content.Context, android.util.AttributeSet");
250       }
251     }
252 
253     BaseVisitor::Visit(node);
254   }
255 
256  private:
257   DISALLOW_COPY_AND_ASSIGN(TransitionVisitor);
258 };
259 
260 class ManifestVisitor : public BaseVisitor {
261  public:
ManifestVisitor(const ResourceFile & file,KeepSet * keep_set,bool main_dex_only)262   ManifestVisitor(const ResourceFile& file, KeepSet* keep_set, bool main_dex_only)
263       : BaseVisitor(file, keep_set), main_dex_only_(main_dex_only) {
264   }
265 
Visit(xml::Element * node)266   void Visit(xml::Element* node) override {
267     if (node->namespace_uri.empty()) {
268       bool get_name = false;
269       if (node->name == "manifest") {
270         xml::Attribute* attr = node->FindAttribute({}, "package");
271         if (attr) {
272           package_ = attr->value;
273         }
274       } else if (node->name == "application") {
275         get_name = true;
276         xml::Attribute* attr = node->FindAttribute(xml::kSchemaAndroid, "backupAgent");
277         if (attr) {
278           Maybe<std::string> result = util::GetFullyQualifiedClassName(package_, attr->value);
279           if (result) {
280             AddClass(node->line_number, result.value(), "");
281           }
282         }
283         attr = node->FindAttribute(xml::kSchemaAndroid, "appComponentFactory");
284         if (attr) {
285           Maybe<std::string> result = util::GetFullyQualifiedClassName(package_, attr->value);
286           if (result) {
287             AddClass(node->line_number, result.value(), "");
288           }
289         }
290 
291         attr = node->FindAttribute(xml::kSchemaAndroid, "zygotePreloadName");
292         if (attr) {
293           Maybe<std::string> result = util::GetFullyQualifiedClassName(package_, attr->value);
294           if (result) {
295             AddClass(node->line_number, result.value(), "");
296           }
297         }
298 
299         if (main_dex_only_) {
300           xml::Attribute* default_process = node->FindAttribute(xml::kSchemaAndroid, "process");
301           if (default_process) {
302             default_process_ = default_process->value;
303           }
304         }
305       } else if (node->name == "activity" || node->name == "service" ||
306                  node->name == "receiver" || node->name == "provider") {
307         get_name = true;
308 
309         if (main_dex_only_) {
310           xml::Attribute* component_process = node->FindAttribute(xml::kSchemaAndroid, "process");
311 
312           const std::string& process =
313               component_process ? component_process->value : default_process_;
314           get_name = !process.empty() && process[0] != ':';
315         }
316       } else if (node->name == "instrumentation") {
317         get_name = true;
318       }
319 
320       if (get_name) {
321         xml::Attribute* attr = node->FindAttribute(xml::kSchemaAndroid, "name");
322         get_name = attr != nullptr;
323 
324         if (get_name) {
325           Maybe<std::string> result = util::GetFullyQualifiedClassName(package_, attr->value);
326           if (result) {
327             AddClass(node->line_number, result.value(), "");
328           }
329         }
330       }
331     }
332     BaseVisitor::Visit(node);
333   }
334 
AddClass(size_t line_number,const std::string & class_name,const std::string & ctor_signature)335   virtual void AddClass(size_t line_number, const std::string& class_name,
336                         const std::string& ctor_signature) override {
337     keep_set_->AddManifestClass({file_.name, file_.source.WithLine(line_number)}, class_name);
338   }
339 
340  private:
341   DISALLOW_COPY_AND_ASSIGN(ManifestVisitor);
342 
343   std::string package_;
344   const bool main_dex_only_;
345   std::string default_process_;
346 };
347 
CollectProguardRulesForManifest(xml::XmlResource * res,KeepSet * keep_set,bool main_dex_only)348 bool CollectProguardRulesForManifest(xml::XmlResource* res, KeepSet* keep_set, bool main_dex_only) {
349   ManifestVisitor visitor(res->file, keep_set, main_dex_only);
350   if (res->root) {
351     res->root->Accept(&visitor);
352     return true;
353   }
354   return false;
355 }
356 
CollectProguardRules(IAaptContext * context_,xml::XmlResource * res,KeepSet * keep_set)357 bool CollectProguardRules(IAaptContext* context_, xml::XmlResource* res, KeepSet* keep_set) {
358   if (!res->root) {
359     return false;
360   }
361 
362   switch (res->file.name.type) {
363     case ResourceType::kLayout: {
364       LayoutVisitor visitor(res->file, keep_set);
365       res->root->Accept(&visitor);
366       break;
367     }
368 
369     case ResourceType::kXml: {
370       XmlResourceVisitor visitor(res->file, keep_set);
371       res->root->Accept(&visitor);
372       break;
373     }
374 
375     case ResourceType::kNavigation: {
376       NavigationVisitor visitor(res->file, keep_set, context_->GetCompilationPackage());
377       res->root->Accept(&visitor);
378       break;
379     }
380 
381     case ResourceType::kTransition: {
382       TransitionVisitor visitor(res->file, keep_set);
383       res->root->Accept(&visitor);
384       break;
385     }
386 
387     case ResourceType::kMenu: {
388       MenuVisitor visitor(res->file, keep_set);
389       res->root->Accept(&visitor);
390       break;
391     }
392 
393     default: {
394       BaseVisitor visitor(res->file, keep_set);
395       res->root->Accept(&visitor);
396       break;
397     }
398   }
399   return true;
400 }
401 
WriteKeepSet(const KeepSet & keep_set,OutputStream * out,bool minimal_keep,bool no_location_reference)402 void WriteKeepSet(const KeepSet& keep_set, OutputStream* out, bool minimal_keep,
403                   bool no_location_reference) {
404 
405   Printer printer(out);
406   for (const auto& entry : keep_set.manifest_class_set_) {
407     if (!no_location_reference) {
408       for (const UsageLocation& location : entry.second) {
409         printer.Print("# Referenced at ").Println(location.source.to_string());
410       }
411     }
412     printer.Print("-keep class ").Print(entry.first).Println(" { <init>(); }");
413   }
414 
415   for (const auto& entry : keep_set.conditional_class_set_) {
416     std::set<UsageLocation> locations;
417     bool can_be_conditional = false;
418     if (keep_set.conditional_keep_rules_) {
419       can_be_conditional = true;
420       for (const UsageLocation& location : entry.second) {
421         can_be_conditional &= CollectLocations(location, keep_set, &locations);
422       }
423     }
424 
425     if (can_be_conditional) {
426       for (const UsageLocation& location : locations) {
427         if (!no_location_reference) {
428           printer.Print("# Referenced at ").Println(location.source.to_string());
429         }
430         printer.Print("-if class **.R$layout { int ")
431             .Print(JavaClassGenerator::TransformToFieldName(location.name.entry))
432             .Println("; }");
433 
434         printer.Print("-keep class ").Print(entry.first.name).Print(" { <init>(");
435         printer.Print((minimal_keep) ? entry.first.signature : "...");
436         printer.Println("); }");
437       }
438     } else {
439       if (!no_location_reference) {
440         for (const UsageLocation& location : entry.second) {
441           printer.Print("# Referenced at ").Println(location.source.to_string());
442         }
443       }
444 
445       printer.Print("-keep class ").Print(entry.first.name).Print(" { <init>(");
446       printer.Print((minimal_keep) ? entry.first.signature : "...");
447       printer.Println("); }");
448     }
449     printer.Println();
450   }
451 
452   for (const auto& entry : keep_set.method_set_) {
453     if (!no_location_reference) {
454       for (const UsageLocation& location : entry.second) {
455         printer.Print("# Referenced at ").Println(location.source.to_string());
456       }
457     }
458     printer.Print("-keepclassmembers class * { *** ").Print(entry.first.name)
459         .Print("(").Print(entry.first.signature).Println("); }");
460     printer.Println();
461   }
462 }
463 
CollectLocations(const UsageLocation & location,const KeepSet & keep_set,std::set<UsageLocation> * locations)464 bool CollectLocations(const UsageLocation& location, const KeepSet& keep_set,
465                       std::set<UsageLocation>* locations) {
466   locations->insert(location);
467 
468   // TODO: allow for more reference types if we can determine its safe.
469   if (location.name.type != ResourceType::kLayout) {
470     return false;
471   }
472 
473   for (const auto& entry : keep_set.reference_set_) {
474     if (entry.first == location.name) {
475       for (auto& refLocation : entry.second) {
476         // Don't get stuck in loops
477         if (locations->find(refLocation) != locations->end()) {
478           return false;
479         }
480         if (!CollectLocations(refLocation, keep_set, locations)) {
481           return false;
482         }
483       }
484     }
485   }
486 
487   return true;
488 }
489 
490 class ReferenceVisitor : public ValueVisitor {
491  public:
492   using ValueVisitor::Visit;
493 
ReferenceVisitor(aapt::IAaptContext * context,ResourceName from,KeepSet * keep_set)494   ReferenceVisitor(aapt::IAaptContext* context, ResourceName from, KeepSet* keep_set)
495       : context_(context), from_(from), keep_set_(keep_set) {
496   }
497 
Visit(Reference * reference)498   void Visit(Reference* reference) override {
499     if (reference->name) {
500       ResourceName reference_name = reference->name.value();
501       if (reference_name.package.empty()) {
502         reference_name = ResourceName(context_->GetCompilationPackage(), reference_name.type,
503                                       reference_name.entry);
504       }
505       keep_set_->AddReference({from_, reference->GetSource()}, reference_name);
506     }
507   }
508 
509  private:
510   aapt::IAaptContext* context_;
511   ResourceName from_;
512   KeepSet* keep_set_;
513 };
514 
CollectResourceReferences(aapt::IAaptContext * context,ResourceTable * table,KeepSet * keep_set)515 bool CollectResourceReferences(aapt::IAaptContext* context, ResourceTable* table,
516                                KeepSet* keep_set) {
517   for (auto& pkg : table->packages) {
518     for (auto& type : pkg->types) {
519       for (auto& entry : type->entries) {
520         for (auto& config_value : entry->values) {
521           ResourceName from(pkg->name, type->type, entry->name);
522           ReferenceVisitor visitor(context, from, keep_set);
523           config_value->value->Accept(&visitor);
524         }
525       }
526     }
527   }
528   return true;
529 }
530 
531 }  // namespace proguard
532 }  // namespace aapt
533