opt/class-merging/ModelSpecGenerator.cpp (200 lines of code) (raw):

/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #include "ModelSpecGenerator.h" #include "Model.h" #include "PassManager.h" #include "Show.h" #include "TypeUtil.h" #include "Walkers.h" namespace { /** * Return true if the name matches "$$Lambda$", "$$ExternalSyntheticLambda", or * "$[0-9]". */ bool maybe_anonymous_class(const DexClass* cls) { static constexpr std::array<const char*, 2> patterns = { // https://r8.googlesource.com/r8/+/refs/tags/3.1.34/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java#140 "$$ExternalSyntheticLambda", // Desugared lambda classes from older versions of D8. "$$Lambda$", }; const auto& name = cls->get_deobfuscated_name_or_empty(); auto pos = name.rfind('$'); if (pos == std::string::npos) { return false; } pos++; return (pos < name.size() && name[pos] >= '0' && name[pos] <= '9') || std::any_of(patterns.begin(), patterns.end(), [&name](const std::string& pattern) { return name.find(pattern) != std::string::npos; }); } /** * The methods and fields may have associated keeping rules, exclude the classes * if they or their methods/fields are not deleteable. For example, methods * annotated with @android.webkit.JavascriptInterface are invoked reflectively, * and we should keep them according to their keeping rules. * * In practice, we find some constructors of anonymous classes are kept by * overly-conservative rules. So we loose the checking for the constructors of * anonymous classes. */ bool can_delete_class(const DexClass* cls, bool is_anonymous_class) { if (!can_delete(cls)) { return false; } auto& vmethods = cls->get_vmethods(); if (std::any_of(vmethods.begin(), vmethods.end(), [](const DexMethod* m) { return !can_delete(m); })) { return false; } auto& dmethods = cls->get_dmethods(); if (std::any_of(dmethods.begin(), dmethods.end(), [&is_anonymous_class](const DexMethod* m) { return (!is_anonymous_class || !is_constructor(m)) && !can_delete(m); })) { return false; } auto& ifields = cls->get_ifields(); if (std::any_of(ifields.begin(), ifields.end(), [](const DexField* f) { return !can_delete(f); })) { return false; } auto& sfields = cls->get_sfields(); if (std::any_of(sfields.begin(), sfields.end(), [](const DexField* f) { return !can_delete(f); })) { return false; } return true; } bool is_from_allowed_packages( const std::unordered_set<std::string>& allowed_packages, const DexClass* cls) { if (allowed_packages.empty()) { return true; } const auto& name = cls->get_deobfuscated_name_or_empty(); for (auto& prefix : allowed_packages) { if (boost::starts_with(name, prefix)) { return true; } } return false; } } // namespace namespace class_merging { void find_all_mergeables_and_roots(const TypeSystem& type_system, const Scope& scope, size_t global_min_count, ModelSpec* merging_spec) { std::unordered_map<const DexTypeList*, std::vector<const DexType*>> intfs_implementors; std::unordered_map<const DexType*, std::vector<const DexType*>> parent_children; TypeSet throwable; type_system.get_all_children(type::java_lang_Throwable(), throwable); for (const auto* cls : scope) { auto cur_type = cls->get_type(); if (is_interface(cls) || is_abstract(cls) || cls->rstate.is_generated() || cls->get_clinit() || throwable.count(cur_type)) { continue; } bool is_anonymous_class = maybe_anonymous_class(cls); // TODO: Can merge named classes. if (!is_anonymous_class) { continue; } TypeSet children; type_system.get_all_children(cur_type, children); if (!children.empty()) { continue; } if (!can_delete_class(cls, is_anonymous_class)) { continue; } auto* intfs = cls->get_interfaces(); auto super_cls = cls->get_super_class(); if (super_cls != type::java_lang_Object()) { parent_children[super_cls].push_back(cur_type); } else if (intfs->size()) { intfs_implementors[intfs].push_back(cur_type); } else { // TODO: Investigate error P444184021 when merging simple classes without // interfaces. } } for (const auto& pair : parent_children) { auto parent = pair.first; auto& children = pair.second; if (children.size() >= global_min_count) { TRACE(CLMG, 9, "Discover root %s with %zu child classes", SHOW(parent), children.size()); merging_spec->roots.insert(parent); merging_spec->merging_targets.insert(children.begin(), children.end()); } } for (const auto& pair : intfs_implementors) { auto intfs = pair.first; auto& implementors = pair.second; if (implementors.size() >= global_min_count) { TRACE(CLMG, 9, "Discover interface root %s with %zu implementors", SHOW(intfs), pair.second.size()); auto first_implementor = type_class(implementors[0]); merging_spec->roots.insert(first_implementor->get_super_class()); merging_spec->merging_targets.insert(implementors.begin(), implementors.end()); } } TRACE(CLMG, 9, "Discover %zu mergeables from %zu roots", merging_spec->merging_targets.size(), merging_spec->roots.size()); } void discover_mergeable_anonymous_classes( const DexStoresVector& stores, const std::unordered_set<std::string>& allowed_packages, size_t min_implementors, ModelSpec* merging_spec, PassManager* mgr) { auto root_store_classes = get_root_store_types(stores, merging_spec->include_primary_dex); const auto object_type = type::java_lang_Object(); std::unordered_map<const DexType*, std::vector<const DexType*>> parents; for (auto* type : root_store_classes) { auto cls = type_class(type); auto* itfs = cls->get_interfaces(); if (is_interface(cls) || itfs->size() > 1 || cls->get_clinit() || !is_from_allowed_packages(allowed_packages, cls)) { continue; } bool is_anonymous_class = maybe_anonymous_class(cls); if (!is_anonymous_class) { continue; } if (!can_delete_class(cls, is_anonymous_class)) { continue; } auto super_cls = cls->get_super_class(); if (itfs->size() == 1) { auto* intf = *itfs->begin(); if (type_class(intf)) { if (itfs->size() == 1) { parents[intf].push_back(cls->get_type()); continue; } } } else { parents[super_cls].push_back(cls->get_type()); } } for (const auto& pair : parents) { auto parent = pair.first; if (!merging_spec->exclude_types.count(parent) && pair.second.size() >= min_implementors) { TRACE(CLMG, 9, "Discover %sroot %s with %zu anonymous classes", (is_interface(type_class(parent)) ? "interface " : ""), SHOW(parent), pair.second.size()); if (parent == object_type) { // TODO: Currently not able to merge classes that only extend // java.lang.Object. continue; } mgr->incr_metric("cls_" + show(parent), pair.second.size()); if (is_interface(type_class(parent))) { auto first_mergeable = type_class(pair.second[0]); merging_spec->roots.insert(first_mergeable->get_super_class()); } else { merging_spec->roots.insert(parent); } merging_spec->merging_targets.insert(pair.second.begin(), pair.second.end()); } } } } // namespace class_merging