Home | History | Annotate | Download | only in java
      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 
     42   BaseVisitor(const ResourceFile& file, KeepSet* keep_set) : file_(file), keep_set_(keep_set) {
     43   }
     44 
     45   void Visit(xml::Element* node) override {
     46     if (!node->namespace_uri.empty()) {
     47       Maybe<xml::ExtractedPackage> maybe_package =
     48           xml::ExtractPackageFromNamespace(node->namespace_uri);
     49       if (maybe_package) {
     50         // This is a custom view, let's figure out the class name from this.
     51         std::string package = maybe_package.value().package + "." + node->name;
     52         if (util::IsJavaClassName(package)) {
     53           AddClass(node->line_number, package);
     54         }
     55       }
     56     } else if (util::IsJavaClassName(node->name)) {
     57       AddClass(node->line_number, node->name);
     58     }
     59 
     60     for (const auto& child : node->children) {
     61       child->Accept(this);
     62     }
     63 
     64     for (const auto& attr : node->attributes) {
     65       if (attr.compiled_value) {
     66         auto ref = ValueCast<Reference>(attr.compiled_value.get());
     67         if (ref) {
     68           AddReference(node->line_number, ref);
     69         }
     70       }
     71     }
     72   }
     73 
     74  protected:
     75   ResourceFile file_;
     76   KeepSet* keep_set_;
     77 
     78   virtual void AddClass(size_t line_number, const std::string& class_name) {
     79     keep_set_->AddConditionalClass({file_.name, file_.source.WithLine(line_number)}, class_name);
     80   }
     81 
     82   void AddMethod(size_t line_number, const std::string& method_name) {
     83     keep_set_->AddMethod({file_.name, file_.source.WithLine(line_number)}, method_name);
     84   }
     85 
     86   void AddReference(size_t line_number, Reference* ref) {
     87     if (ref && ref->name) {
     88       ResourceName ref_name = ref->name.value();
     89       if (ref_name.package.empty()) {
     90         ref_name = ResourceName(file_.name.package, ref_name.type, ref_name.entry);
     91       }
     92       keep_set_->AddReference({file_.name, file_.source.WithLine(line_number)}, ref_name);
     93     }
     94   }
     95 
     96  private:
     97   DISALLOW_COPY_AND_ASSIGN(BaseVisitor);
     98 
     99 };
    100 
    101 class LayoutVisitor : public BaseVisitor {
    102  public:
    103   LayoutVisitor(const ResourceFile& file, KeepSet* keep_set) : BaseVisitor(file, keep_set) {
    104   }
    105 
    106   void Visit(xml::Element* node) override {
    107     bool check_class = false;
    108     bool check_name = false;
    109     if (node->namespace_uri.empty()) {
    110       if (node->name == "view") {
    111         check_class = true;
    112       } else if (node->name == "fragment") {
    113         check_class = check_name = true;
    114       }
    115     } else if (node->namespace_uri == xml::kSchemaAndroid) {
    116       check_name = node->name == "fragment";
    117     }
    118 
    119     for (const auto& attr : node->attributes) {
    120       if (check_class && attr.namespace_uri.empty() && attr.name == "class" &&
    121           util::IsJavaClassName(attr.value)) {
    122         AddClass(node->line_number, attr.value);
    123       } else if (check_name && attr.namespace_uri == xml::kSchemaAndroid &&
    124                  attr.name == "name" && util::IsJavaClassName(attr.value)) {
    125         AddClass(node->line_number, attr.value);
    126       } else if (attr.namespace_uri == xml::kSchemaAndroid &&
    127                  attr.name == "onClick") {
    128         AddMethod(node->line_number, attr.value);
    129       }
    130     }
    131 
    132     BaseVisitor::Visit(node);
    133   }
    134 
    135  private:
    136   DISALLOW_COPY_AND_ASSIGN(LayoutVisitor);
    137 };
    138 
    139 class MenuVisitor : public BaseVisitor {
    140  public:
    141   MenuVisitor(const ResourceFile& file, KeepSet* keep_set) : BaseVisitor(file, keep_set) {
    142   }
    143 
    144   void Visit(xml::Element* node) override {
    145     if (node->namespace_uri.empty() && node->name == "item") {
    146       for (const auto& attr : node->attributes) {
    147         if (attr.namespace_uri == xml::kSchemaAndroid) {
    148           if ((attr.name == "actionViewClass" || attr.name == "actionProviderClass") &&
    149               util::IsJavaClassName(attr.value)) {
    150             AddClass(node->line_number, attr.value);
    151           } else if (attr.name == "onClick") {
    152             AddMethod(node->line_number, attr.value);
    153           }
    154         }
    155       }
    156     }
    157 
    158     BaseVisitor::Visit(node);
    159   }
    160 
    161  private:
    162   DISALLOW_COPY_AND_ASSIGN(MenuVisitor);
    163 };
    164 
    165 class XmlResourceVisitor : public BaseVisitor {
    166  public:
    167   XmlResourceVisitor(const ResourceFile& file, KeepSet* keep_set) : BaseVisitor(file, keep_set) {
    168   }
    169 
    170   void Visit(xml::Element* node) override {
    171     bool check_fragment = false;
    172     if (node->namespace_uri.empty()) {
    173       check_fragment =
    174           node->name == "PreferenceScreen" || node->name == "header";
    175     }
    176 
    177     if (check_fragment) {
    178       xml::Attribute* attr =
    179           node->FindAttribute(xml::kSchemaAndroid, "fragment");
    180       if (attr && util::IsJavaClassName(attr->value)) {
    181         AddClass(node->line_number, attr->value);
    182       }
    183     }
    184 
    185     BaseVisitor::Visit(node);
    186   }
    187 
    188  private:
    189   DISALLOW_COPY_AND_ASSIGN(XmlResourceVisitor);
    190 };
    191 
    192 class TransitionVisitor : public BaseVisitor {
    193  public:
    194   TransitionVisitor(const ResourceFile& file, KeepSet* keep_set) : BaseVisitor(file, keep_set) {
    195   }
    196 
    197   void Visit(xml::Element* node) override {
    198     bool check_class =
    199         node->namespace_uri.empty() && (node->name == "transition" || node->name == "pathMotion");
    200     if (check_class) {
    201       xml::Attribute* attr = node->FindAttribute({}, "class");
    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(TransitionVisitor);
    212 };
    213 
    214 class ManifestVisitor : public BaseVisitor {
    215  public:
    216   ManifestVisitor(const ResourceFile& file, KeepSet* keep_set, bool main_dex_only)
    217       : BaseVisitor(file, keep_set), main_dex_only_(main_dex_only) {
    218   }
    219 
    220   void Visit(xml::Element* node) override {
    221     if (node->namespace_uri.empty()) {
    222       bool get_name = false;
    223       if (node->name == "manifest") {
    224         xml::Attribute* attr = node->FindAttribute({}, "package");
    225         if (attr) {
    226           package_ = attr->value;
    227         }
    228       } else if (node->name == "application") {
    229         get_name = true;
    230         xml::Attribute* attr = node->FindAttribute(xml::kSchemaAndroid, "backupAgent");
    231         if (attr) {
    232           Maybe<std::string> result = util::GetFullyQualifiedClassName(package_, attr->value);
    233           if (result) {
    234             AddClass(node->line_number, result.value());
    235           }
    236         }
    237         if (main_dex_only_) {
    238           xml::Attribute* default_process = node->FindAttribute(xml::kSchemaAndroid, "process");
    239           if (default_process) {
    240             default_process_ = default_process->value;
    241           }
    242         }
    243       } else if (node->name == "activity" || node->name == "service" ||
    244                  node->name == "receiver" || node->name == "provider") {
    245         get_name = true;
    246 
    247         if (main_dex_only_) {
    248           xml::Attribute* component_process = node->FindAttribute(xml::kSchemaAndroid, "process");
    249 
    250           const std::string& process =
    251               component_process ? component_process->value : default_process_;
    252           get_name = !process.empty() && process[0] != ':';
    253         }
    254       } else if (node->name == "instrumentation") {
    255         get_name = true;
    256       }
    257 
    258       if (get_name) {
    259         xml::Attribute* attr = node->FindAttribute(xml::kSchemaAndroid, "name");
    260         get_name = attr != nullptr;
    261 
    262         if (get_name) {
    263           Maybe<std::string> result = util::GetFullyQualifiedClassName(package_, attr->value);
    264           if (result) {
    265             AddClass(node->line_number, result.value());
    266           }
    267         }
    268       }
    269     }
    270     BaseVisitor::Visit(node);
    271   }
    272 
    273   virtual void AddClass(size_t line_number, const std::string& class_name) override {
    274     keep_set_->AddManifestClass({file_.name, file_.source.WithLine(line_number)}, class_name);
    275   }
    276 
    277  private:
    278   DISALLOW_COPY_AND_ASSIGN(ManifestVisitor);
    279 
    280   std::string package_;
    281   const bool main_dex_only_;
    282   std::string default_process_;
    283 };
    284 
    285 bool CollectProguardRulesForManifest(xml::XmlResource* res, KeepSet* keep_set, bool main_dex_only) {
    286   ManifestVisitor visitor(res->file, keep_set, main_dex_only);
    287   if (res->root) {
    288     res->root->Accept(&visitor);
    289     return true;
    290   }
    291   return false;
    292 }
    293 
    294 bool CollectProguardRules(xml::XmlResource* res, KeepSet* keep_set) {
    295   if (!res->root) {
    296     return false;
    297   }
    298 
    299   switch (res->file.name.type) {
    300     case ResourceType::kLayout: {
    301       LayoutVisitor visitor(res->file, keep_set);
    302       res->root->Accept(&visitor);
    303       break;
    304     }
    305 
    306     case ResourceType::kXml: {
    307       XmlResourceVisitor visitor(res->file, keep_set);
    308       res->root->Accept(&visitor);
    309       break;
    310     }
    311 
    312     case ResourceType::kTransition: {
    313       TransitionVisitor visitor(res->file, keep_set);
    314       res->root->Accept(&visitor);
    315       break;
    316     }
    317 
    318     case ResourceType::kMenu: {
    319       MenuVisitor visitor(res->file, keep_set);
    320       res->root->Accept(&visitor);
    321       break;
    322     }
    323 
    324     default: {
    325       BaseVisitor visitor(res->file, keep_set);
    326       res->root->Accept(&visitor);
    327       break;
    328     }
    329   }
    330   return true;
    331 }
    332 
    333 void WriteKeepSet(const KeepSet& keep_set, OutputStream* out) {
    334   Printer printer(out);
    335   for (const auto& entry : keep_set.manifest_class_set_) {
    336     for (const UsageLocation& location : entry.second) {
    337       printer.Print("# Referenced at ").Println(location.source.to_string());
    338     }
    339     printer.Print("-keep class ").Print(entry.first).Println(" { <init>(...); }");
    340   }
    341 
    342   for (const auto& entry : keep_set.conditional_class_set_) {
    343     std::set<UsageLocation> locations;
    344     bool can_be_conditional = true;
    345     for (const UsageLocation& location : entry.second) {
    346       can_be_conditional &= CollectLocations(location, keep_set, &locations);
    347     }
    348 
    349     if (keep_set.conditional_keep_rules_ && can_be_conditional) {
    350       for (const UsageLocation& location : locations) {
    351         printer.Print("# Referenced at ").Println(location.source.to_string());
    352         printer.Print("-if class **.R$layout { int ")
    353             .Print(JavaClassGenerator::TransformToFieldName(location.name.entry))
    354             .Println("; }");
    355         printer.Print("-keep class ").Print(entry.first).Println(" { <init>(...); }");
    356       }
    357     } else {
    358       for (const UsageLocation& location : entry.second) {
    359         printer.Print("# Referenced at ").Println(location.source.to_string());
    360       }
    361       printer.Print("-keep class ").Print(entry.first).Println(" { <init>(...); }");
    362     }
    363     printer.Println();
    364   }
    365 
    366   for (const auto& entry : keep_set.method_set_) {
    367     for (const UsageLocation& location : entry.second) {
    368       printer.Print("# Referenced at ").Println(location.source.to_string());
    369     }
    370     printer.Print("-keepclassmembers class * { *** ").Print(entry.first).Println("(...); }");
    371     printer.Println();
    372   }
    373 }
    374 
    375 bool CollectLocations(const UsageLocation& location, const KeepSet& keep_set,
    376                       std::set<UsageLocation>* locations) {
    377   locations->insert(location);
    378 
    379   // TODO: allow for more reference types if we can determine its safe.
    380   if (location.name.type != ResourceType::kLayout) {
    381     return false;
    382   }
    383 
    384   for (const auto& entry : keep_set.reference_set_) {
    385     if (entry.first == location.name) {
    386       for (auto& refLocation : entry.second) {
    387         // Don't get stuck in loops
    388         if (locations->find(refLocation) != locations->end()) {
    389           return false;
    390         }
    391         if (!CollectLocations(refLocation, keep_set, locations)) {
    392           return false;
    393         }
    394       }
    395     }
    396   }
    397 
    398   return true;
    399 }
    400 
    401 class ReferenceVisitor : public ValueVisitor {
    402  public:
    403   using ValueVisitor::Visit;
    404 
    405   ReferenceVisitor(aapt::IAaptContext* context, ResourceName from, KeepSet* keep_set)
    406       : context_(context), from_(from), keep_set_(keep_set) {
    407   }
    408 
    409   void Visit(Reference* reference) override {
    410     if (reference->name) {
    411       ResourceName reference_name = reference->name.value();
    412       if (reference_name.package.empty()) {
    413         reference_name = ResourceName(context_->GetCompilationPackage(), reference_name.type,
    414                                       reference_name.entry);
    415       }
    416       keep_set_->AddReference({from_, reference->GetSource()}, reference_name);
    417     }
    418   }
    419 
    420  private:
    421   aapt::IAaptContext* context_;
    422   ResourceName from_;
    423   KeepSet* keep_set_;
    424 };
    425 
    426 bool CollectResourceReferences(aapt::IAaptContext* context, ResourceTable* table,
    427                                KeepSet* keep_set) {
    428   for (auto& pkg : table->packages) {
    429     for (auto& type : pkg->types) {
    430       for (auto& entry : type->entries) {
    431         for (auto& config_value : entry->values) {
    432           ResourceName from(pkg->name, type->type, entry->name);
    433           ReferenceVisitor visitor(context, from, keep_set);
    434           config_value->value->Accept(&visitor);
    435         }
    436       }
    437     }
    438   }
    439   return true;
    440 }
    441 
    442 }  // namespace proguard
    443 }  // namespace aapt
    444