Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit dc21dea9 authored by Adam Koski's avatar Adam Koski
Browse files

AAPT2: Produce Conditional Proguard Keep Rules

Add the option to produce keep rules that conditional keep based on
usage of R identifiers. This allows Proguard to potentially shrink more
code if resources are not used.

Currently only produces conditional rules for classes referenced in
layout resources because they are the most common and has the easiest
transitive usage chain to analyze.

Bug: 63628451
Test: make aapt2_tests and manual testing
Change-Id: I6c1af7affd64af40c80e004d8506a9463444b2c3
parent 0d769d80
Loading
Loading
Loading
Loading
+11 −6
Original line number Diff line number Diff line
@@ -96,6 +96,7 @@ struct LinkOptions {
  Maybe<std::string> generate_text_symbols_path;
  Maybe<std::string> generate_proguard_rules_path;
  Maybe<std::string> generate_main_dex_proguard_rules_path;
  bool generate_conditional_proguard_rules = false;
  bool generate_non_final_ids = false;
  std::vector<std::string> javadoc_annotations;
  Maybe<std::string> private_symbols;
@@ -499,7 +500,7 @@ std::vector<std::unique_ptr<xml::XmlResource>> ResourceFileFlattener::LinkAndVer
    return {};
  }

  if (options_.update_proguard_spec && !proguard::CollectProguardRules(src, doc, keep_set_)) {
  if (options_.update_proguard_spec && !proguard::CollectProguardRules(doc, keep_set_)) {
    return {};
  }

@@ -538,6 +539,8 @@ bool ResourceFileFlattener::Flatten(ResourceTable* table, IArchiveWriter* archiv
  bool error = false;
  std::map<std::pair<ConfigDescription, StringPiece>, FileOperation> config_sorted_files;

  proguard::CollectResourceReferences(context_, table, keep_set_);

  for (auto& pkg : table->packages) {
    CHECK(!pkg->name.empty()) << "Packages must have names when being linked";

@@ -1719,7 +1722,8 @@ class LinkCommand {
      }
    }

    proguard::KeepSet proguard_keep_set;
    proguard::KeepSet proguard_keep_set =
        proguard::KeepSet(options_.generate_conditional_proguard_rules);
    proguard::KeepSet proguard_main_dex_keep_set;

    if (context_->GetPackageType() == PackageType::kStaticLib) {
@@ -1795,14 +1799,12 @@ class LinkCommand {
      XmlReferenceLinker manifest_linker;
      if (manifest_linker.Consume(context_, manifest_xml.get())) {
        if (options_.generate_proguard_rules_path &&
            !proguard::CollectProguardRulesForManifest(Source(options_.manifest_path),
                                                       manifest_xml.get(), &proguard_keep_set)) {
            !proguard::CollectProguardRulesForManifest(manifest_xml.get(), &proguard_keep_set)) {
          error = true;
        }

        if (options_.generate_main_dex_proguard_rules_path &&
            !proguard::CollectProguardRulesForManifest(Source(options_.manifest_path),
                                                       manifest_xml.get(),
            !proguard::CollectProguardRulesForManifest(manifest_xml.get(),
                                                       &proguard_main_dex_keep_set, true)) {
          error = true;
        }
@@ -1919,6 +1921,9 @@ int Link(const std::vector<StringPiece>& args, IDiagnostics* diagnostics) {
          .OptionalFlag("--proguard-main-dex",
                        "Output file for generated Proguard rules for the main dex.",
                        &options.generate_main_dex_proguard_rules_path)
          .OptionalSwitch("--proguard-conditional-keep-rules",
                          "Generate conditional Proguard keep rules.",
                          &options.generate_conditional_proguard_rules)
          .OptionalSwitch("--no-auto-version",
                          "Disables automatic style and layout SDK versioning.",
                          &options.no_auto_version)
+3 −3
Original line number Diff line number Diff line
@@ -61,7 +61,7 @@ static bool IsValidSymbol(const StringPiece& symbol) {

// Java symbols can not contain . or -, but those are valid in a resource name.
// Replace those with '_'.
static std::string TransformToFieldName(const StringPiece& symbol) {
std::string JavaClassGenerator::TransformToFieldName(const StringPiece& symbol) {
  std::string output = symbol.to_string();
  for (char& c : output) {
    if (c == '.' || c == '-') {
@@ -89,9 +89,9 @@ static std::string TransformNestedAttr(const ResourceNameRef& attr_name,
  // the package.
  if (!attr_name.package.empty() &&
      package_name_to_generate != attr_name.package) {
    output += "_" + TransformToFieldName(attr_name.package);
    output += "_" + JavaClassGenerator::TransformToFieldName(attr_name.package);
  }
  output += "_" + TransformToFieldName(attr_name.entry);
  output += "_" + JavaClassGenerator::TransformToFieldName(attr_name.entry);
  return output;
}

+3 −0
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@

#include "ResourceTable.h"
#include "ResourceValues.h"
#include "androidfw/StringPiece.h"
#include "process/IResourceTableConsumer.h"
#include "process/SymbolTable.h"

@@ -78,6 +79,8 @@ class JavaClassGenerator {

  const std::string& getError() const;

  static std::string TransformToFieldName(const android::StringPiece& symbol);

 private:
  bool SkipSymbol(SymbolState state);
  bool SkipSymbol(const Maybe<SymbolTable::Symbol>& symbol);
+147 −27
Original line number Diff line number Diff line
@@ -21,6 +21,10 @@

#include "android-base/macros.h"

#include "JavaClassGenerator.h"
#include "ResourceUtils.h"
#include "ValueVisitor.h"
#include "androidfw/StringPiece.h"
#include "util/Util.h"
#include "xml/XmlDom.h"

@@ -31,7 +35,7 @@ class BaseVisitor : public xml::Visitor {
 public:
  using xml::Visitor::Visit;

  BaseVisitor(const Source& source, KeepSet* keep_set) : source_(source), keep_set_(keep_set) {
  BaseVisitor(const ResourceFile& file, KeepSet* keep_set) : file_(file), keep_set_(keep_set) {
  }

  void Visit(xml::Element* node) override {
@@ -52,27 +56,47 @@ class BaseVisitor : public xml::Visitor {
    for (const auto& child : node->children) {
      child->Accept(this);
    }

    for (const auto& attr : node->attributes) {
      if (attr.compiled_value) {
        auto ref = ValueCast<Reference>(attr.compiled_value.get());
        if (ref) {
          AddReference(node->line_number, ref);
        }
      }
    }
  }

 protected:
  void AddClass(size_t line_number, const std::string& class_name) {
    keep_set_->AddClass(Source(source_.path, line_number), class_name);
  ResourceFile file_;
  KeepSet* keep_set_;

  virtual void AddClass(size_t line_number, const std::string& class_name) {
    keep_set_->AddConditionalClass({file_.name, file_.source.WithLine(line_number)}, class_name);
  }

  void AddMethod(size_t line_number, const std::string& method_name) {
    keep_set_->AddMethod(Source(source_.path, line_number), method_name);
    keep_set_->AddMethod({file_.name, file_.source.WithLine(line_number)}, method_name);
  }

  void AddReference(size_t line_number, Reference* ref) {
    if (ref && ref->name) {
      ResourceName ref_name = ref->name.value();
      if (ref_name.package.empty()) {
        ref_name = ResourceName(file_.name.package, ref_name.type, ref_name.entry);
      }
      keep_set_->AddReference({file_.name, file_.source.WithLine(line_number)}, ref_name);
    }
  }

 private:
  DISALLOW_COPY_AND_ASSIGN(BaseVisitor);

  Source source_;
  KeepSet* keep_set_;
};

class LayoutVisitor : public BaseVisitor {
 public:
  LayoutVisitor(const Source& source, KeepSet* keep_set) : BaseVisitor(source, keep_set) {
  LayoutVisitor(const ResourceFile& file, KeepSet* keep_set) : BaseVisitor(file, keep_set) {
  }

  void Visit(xml::Element* node) override {
@@ -110,7 +134,7 @@ class LayoutVisitor : public BaseVisitor {

class MenuVisitor : public BaseVisitor {
 public:
  MenuVisitor(const Source& source, KeepSet* keep_set) : BaseVisitor(source, keep_set) {
  MenuVisitor(const ResourceFile& file, KeepSet* keep_set) : BaseVisitor(file, keep_set) {
  }

  void Visit(xml::Element* node) override {
@@ -136,7 +160,7 @@ class MenuVisitor : public BaseVisitor {

class XmlResourceVisitor : public BaseVisitor {
 public:
  XmlResourceVisitor(const Source& source, KeepSet* keep_set) : BaseVisitor(source, keep_set) {
  XmlResourceVisitor(const ResourceFile& file, KeepSet* keep_set) : BaseVisitor(file, keep_set) {
  }

  void Visit(xml::Element* node) override {
@@ -163,7 +187,7 @@ class XmlResourceVisitor : public BaseVisitor {

class TransitionVisitor : public BaseVisitor {
 public:
  TransitionVisitor(const Source& source, KeepSet* keep_set) : BaseVisitor(source, keep_set) {
  TransitionVisitor(const ResourceFile& file, KeepSet* keep_set) : BaseVisitor(file, keep_set) {
  }

  void Visit(xml::Element* node) override {
@@ -185,8 +209,9 @@ class TransitionVisitor : public BaseVisitor {

class ManifestVisitor : public BaseVisitor {
 public:
  ManifestVisitor(const Source& source, KeepSet* keep_set, bool main_dex_only)
      : BaseVisitor(source, keep_set), main_dex_only_(main_dex_only) {}
  ManifestVisitor(const ResourceFile& file, KeepSet* keep_set, bool main_dex_only)
      : BaseVisitor(file, keep_set), main_dex_only_(main_dex_only) {
  }

  void Visit(xml::Element* node) override {
    if (node->namespace_uri.empty()) {
@@ -241,6 +266,10 @@ class ManifestVisitor : public BaseVisitor {
    BaseVisitor::Visit(node);
  }

  virtual void AddClass(size_t line_number, const std::string& class_name) override {
    keep_set_->AddManifestClass({file_.name, file_.source.WithLine(line_number)}, class_name);
  }

 private:
  DISALLOW_COPY_AND_ASSIGN(ManifestVisitor);

@@ -249,9 +278,8 @@ class ManifestVisitor : public BaseVisitor {
  std::string default_process_;
};

bool CollectProguardRulesForManifest(const Source& source, xml::XmlResource* res, KeepSet* keep_set,
                                     bool main_dex_only) {
  ManifestVisitor visitor(source, keep_set, main_dex_only);
bool CollectProguardRulesForManifest(xml::XmlResource* res, KeepSet* keep_set, bool main_dex_only) {
  ManifestVisitor visitor(res->file, keep_set, main_dex_only);
  if (res->root) {
    res->root->Accept(&visitor);
    return true;
@@ -259,58 +287,150 @@ bool CollectProguardRulesForManifest(const Source& source, xml::XmlResource* res
  return false;
}

bool CollectProguardRules(const Source& source, xml::XmlResource* res, KeepSet* keep_set) {
bool CollectProguardRules(xml::XmlResource* res, KeepSet* keep_set) {
  if (!res->root) {
    return false;
  }

  switch (res->file.name.type) {
    case ResourceType::kLayout: {
      LayoutVisitor visitor(source, keep_set);
      LayoutVisitor visitor(res->file, keep_set);
      res->root->Accept(&visitor);
      break;
    }

    case ResourceType::kXml: {
      XmlResourceVisitor visitor(source, keep_set);
      XmlResourceVisitor visitor(res->file, keep_set);
      res->root->Accept(&visitor);
      break;
    }

    case ResourceType::kTransition: {
      TransitionVisitor visitor(source, keep_set);
      TransitionVisitor visitor(res->file, keep_set);
      res->root->Accept(&visitor);
      break;
    }

    case ResourceType::kMenu: {
      MenuVisitor visitor(source, keep_set);
      MenuVisitor visitor(res->file, keep_set);
      res->root->Accept(&visitor);
      break;
    }

    default:
    default: {
      BaseVisitor visitor(res->file, keep_set);
      res->root->Accept(&visitor);
      break;
    }
  }
  return true;
}

bool WriteKeepSet(std::ostream* out, const KeepSet& keep_set) {
  for (const auto& entry : keep_set.keep_set_) {
    for (const Source& source : entry.second) {
      *out << "# Referenced at " << source << "\n";
  for (const auto& entry : keep_set.manifest_class_set_) {
    for (const UsageLocation& location : entry.second) {
      *out << "# Referenced at " << location.source << "\n";
    }
    *out << "-keep class " << entry.first << " { <init>(...); }\n" << std::endl;
  }

  for (const auto& entry : keep_set.keep_method_set_) {
    for (const Source& source : entry.second) {
      *out << "# Referenced at " << source << "\n";
  for (const auto& entry : keep_set.conditional_class_set_) {
    std::set<UsageLocation> locations;
    bool can_be_conditional = true;
    for (const UsageLocation& location : entry.second) {
      can_be_conditional &= CollectLocations(location, keep_set, &locations);
    }

    for (const UsageLocation& location : entry.second) {
      *out << "# Referenced at " << location.source << "\n";
    }
    if (keep_set.conditional_keep_rules_ && can_be_conditional) {
      *out << "-keep class " << entry.first << " {\n  ifused class **.R$layout {\n";
      for (const UsageLocation& location : locations) {
        auto transformed_name = JavaClassGenerator::TransformToFieldName(location.name.entry);
        *out << "    int " << transformed_name << ";\n";
      }
      *out << "  };\n  <init>(...);\n}\n" << std::endl;
    } else {
      *out << "-keep class " << entry.first << " { <init>(...); }\n" << std::endl;
    }
  }

  for (const auto& entry : keep_set.method_set_) {
    for (const UsageLocation& location : entry.second) {
      *out << "# Referenced at " << location.source << "\n";
    }
    *out << "-keepclassmembers class * { *** " << entry.first << "(...); }\n" << std::endl;
  }
  return true;
}

bool CollectLocations(const UsageLocation& location, const KeepSet& keep_set,
                      std::set<UsageLocation>* locations) {
  locations->insert(location);

  // TODO: allow for more reference types if we can determine its safe.
  if (location.name.type != ResourceType::kLayout) {
    return false;
  }

  for (const auto& entry : keep_set.reference_set_) {
    if (entry.first == location.name) {
      for (auto& refLocation : entry.second) {
        // Don't get stuck in loops
        if (locations->find(refLocation) != locations->end()) {
          return false;
        }
        if (!CollectLocations(refLocation, keep_set, locations)) {
          return false;
        }
      }
    }
  }

  return true;
}

class ReferenceVisitor : public ValueVisitor {
 public:
  using ValueVisitor::Visit;

  ReferenceVisitor(aapt::IAaptContext* context, ResourceName from, KeepSet* keep_set)
      : context_(context), from_(from), keep_set_(keep_set) {
  }

  void Visit(Reference* reference) override {
    if (reference->name) {
      ResourceName reference_name = reference->name.value();
      if (reference_name.package.empty()) {
        reference_name = ResourceName(context_->GetCompilationPackage(), reference_name.type,
                                      reference_name.entry);
      }
      keep_set_->AddReference({from_, reference->GetSource()}, reference_name);
    }
  }

 private:
  aapt::IAaptContext* context_;
  ResourceName from_;
  KeepSet* keep_set_;
};

bool CollectResourceReferences(aapt::IAaptContext* context, ResourceTable* table,
                               KeepSet* keep_set) {
  for (auto& pkg : table->packages) {
    for (auto& type : pkg->types) {
      for (auto& entry : type->entries) {
        for (auto& config_value : entry->values) {
          ResourceName from(pkg->name, type->type, entry->name);
          ReferenceVisitor visitor(context, from, keep_set);
          config_value->value->Accept(&visitor);
        }
      }
    }
  }
  return true;
}

}  // namespace proguard
}  // namespace aapt
+53 −10
Original line number Diff line number Diff line
@@ -23,37 +23,80 @@
#include <string>

#include "Resource.h"
#include "ResourceTable.h"
#include "Source.h"
#include "ValueVisitor.h"
#include "androidfw/StringPiece.h"
#include "process/IResourceTableConsumer.h"
#include "xml/XmlDom.h"

namespace aapt {
namespace proguard {

struct UsageLocation {
  ResourceName name;
  Source source;
};

class KeepSet {
 public:
  inline void AddClass(const Source& source, const std::string& class_name) {
    keep_set_[class_name].insert(source);
  KeepSet() = default;

  KeepSet(bool conditional_keep_rules) : conditional_keep_rules_(conditional_keep_rules) {
  }

  inline void AddManifestClass(const UsageLocation& file, const std::string& class_name) {
    manifest_class_set_[class_name].insert(file);
  }

  inline void AddConditionalClass(const UsageLocation& file, const std::string& class_name) {
    conditional_class_set_[class_name].insert(file);
  }

  inline void AddMethod(const UsageLocation& file, const std::string& method_name) {
    method_set_[method_name].insert(file);
  }

  inline void AddMethod(const Source& source, const std::string& method_name) {
    keep_method_set_[method_name].insert(source);
  inline void AddReference(const UsageLocation& file, const ResourceName& resource_name) {
    reference_set_[resource_name].insert(file);
  }

 private:
  friend bool WriteKeepSet(std::ostream* out, const KeepSet& keep_set);

  std::map<std::string, std::set<Source>> keep_set_;
  std::map<std::string, std::set<Source>> keep_method_set_;
  friend bool CollectLocations(const UsageLocation& location, const KeepSet& keep_set,
                               std::set<UsageLocation>* locations);

  bool conditional_keep_rules_ = false;
  std::map<std::string, std::set<UsageLocation>> manifest_class_set_;
  std::map<std::string, std::set<UsageLocation>> method_set_;
  std::map<std::string, std::set<UsageLocation>> conditional_class_set_;
  std::map<ResourceName, std::set<UsageLocation>> reference_set_;
};

bool CollectProguardRulesForManifest(const Source& source,
                                     xml::XmlResource* res, KeepSet* keep_set,
bool CollectProguardRulesForManifest(xml::XmlResource* res, KeepSet* keep_set,
                                     bool main_dex_only = false);
bool CollectProguardRules(const Source& source, xml::XmlResource* res,
bool CollectProguardRules(xml::XmlResource* res, KeepSet* keep_set);
bool CollectResourceReferences(aapt::IAaptContext* context, ResourceTable* table,
                               KeepSet* keep_set);

bool WriteKeepSet(std::ostream* out, const KeepSet& keep_set);

bool CollectLocations(const UsageLocation& location, const KeepSet& keep_set,
                      std::set<UsageLocation>* locations);

//
// UsageLocation implementation.
//

inline bool operator==(const UsageLocation& lhs, const UsageLocation& rhs) {
  return lhs.name == rhs.name;
}

inline int operator<(const UsageLocation& lhs, const UsageLocation& rhs) {
  return lhs.name.compare(rhs.name);
}

}  // namespace proguard
}  // namespace aapt

Loading