libredex/ProguardMatcher.cpp (769 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 <algorithm> #include <boost/regex.hpp> #include <iostream> #include <mutex> #include <sstream> #include <thread> #include <unordered_set> #include "ClassHierarchy.h" #include "ConcurrentContainers.h" #include "DexAnnotation.h" #include "DexUtil.h" #include "IRCode.h" #include "ProguardMatcher.h" #include "ProguardPrintConfiguration.h" #include "ProguardRegex.h" #include "ProguardReporting.h" #include "ReachableClasses.h" #include "RedexContext.h" #include "Show.h" #include "StringBuilder.h" #include "Timer.h" #include "Trace.h" #include "WorkQueue.h" using namespace keep_rules; namespace { using RegexMap = std::unordered_map<std::string, boost::regex>; std::unique_ptr<boost::regex> make_rx(const std::string& s, bool convert = true) { if (s.empty()) return nullptr; auto wc = convert ? proguard_parser::convert_wildcard_type(s) : s; auto rx = proguard_parser::form_type_regex(wc); return std::make_unique<boost::regex>(rx); } std::vector<std::unique_ptr<boost::regex>> make_rxs( const std::vector<ClassSpecification::ClassNameSpec>& strs) { std::vector<std::unique_ptr<boost::regex>> rxs; rxs.reserve(strs.size()); for (const auto& str : strs) { rxs.push_back(make_rx(str.name)); } return rxs; } const std::string& get_deobfuscated_name(const DexType* type) { auto cls = type_class(type); if (cls == nullptr) { return type->str(); } return cls->get_deobfuscated_name().str(); } bool match_annotation_rx(const DexClass* cls, const boost::regex& annorx) { const auto* annos = cls->get_anno_set(); if (!annos) return false; for (const auto& anno : annos->get_annotations()) { if (boost::regex_match(get_deobfuscated_name(anno->type()), annorx)) { return true; } } return false; } /** * Helper class that holds the conditions for a class-level match on a keep * rule. */ struct ClassMatcher { explicit ClassMatcher(const KeepSpec& ks) : setFlags_(ks.class_spec.setAccessFlags), unsetFlags_(ks.class_spec.unsetAccessFlags), m_class_names(ks.class_spec.classNames), m_cls(make_rxs(ks.class_spec.classNames)), m_anno(make_rx(ks.class_spec.annotationType, false)), m_extends(make_rx(ks.class_spec.extendsClassName)), m_extends_anno(make_rx(ks.class_spec.extendsAnnotationType, false)) {} bool match(const DexClass* cls) { for (std::size_t i = 0; i < m_class_names.size(); i++) { const auto& class_name = m_class_names[i]; // Check for class name match `match_name` is really slow; let's // short-circuit it for wildcard-only matches. if (class_name.name != "*" && class_name.name != "**" && !match_name(cls, i)) { continue; } if (class_name.negated) { return false; } // Check for access match if (!match_access(cls)) { return false; } // Check to see if an annotation guard needs to be matched. if (!match_annotation(cls)) { return false; } // Check to see if an extends clause needs to be matched. if (!match_extends(cls)) { return false; } return true; } return false; } private: bool match_name(const DexClass* cls, int index) const { const auto& deob_name = cls->get_deobfuscated_name(); const auto& rx = *m_cls[index]; return boost::regex_match(deob_name.str(), rx); } bool match_access(const DexClass* cls) const { return access_matches(setFlags_, unsetFlags_, cls->get_access()); } bool match_annotation(const DexClass* cls) const { if (!m_anno) return true; return match_annotation_rx(cls, *m_anno); } bool match_extends(const DexClass* cls) { if (!m_extends) return true; return search_extends_and_interfaces(cls); } bool type_and_annotation_match(const DexClass* cls) const { if (cls == nullptr) return false; if (cls->get_type() == type::java_lang_Object()) return false; // First check to see if an annotation type needs to be matched. if (m_extends_anno) { if (!match_annotation_rx(cls, *m_extends_anno)) { return false; } } const auto& deob_name = cls->get_deobfuscated_name(); return boost::regex_match(deob_name.str(), *m_extends); } bool search_interfaces(const DexClass* cls) { const auto* interfaces = cls->get_interfaces(); if (!interfaces) return false; for (const auto& impl : *interfaces) { auto impl_class = type_class(impl); if (impl_class) { if (type_and_annotation_match(impl_class) || search_extends_and_interfaces(impl_class)) { return true; } } } return false; } bool search_extends_and_interfaces(const DexClass* cls) { auto cached_it = m_extends_result_cache.find(cls); if (cached_it != m_extends_result_cache.end()) { return cached_it->second; } auto result = search_extends_and_interfaces_nocache(cls); m_extends_result_cache.emplace(cls, result); return result; } bool search_extends_and_interfaces_nocache(const DexClass* cls) { always_assert(cls != nullptr); // Do any of the classes and interfaces above match? auto super_type = cls->get_super_class(); if (super_type && super_type != type::java_lang_Object()) { auto super_class = type_class(super_type); if (super_class) { if (type_and_annotation_match(super_class) || search_extends_and_interfaces(super_class)) { return true; } } } // Do any of the interfaces from here and up match? return search_interfaces(cls); } DexAccessFlags setFlags_; DexAccessFlags unsetFlags_; std::vector<ClassSpecification::ClassNameSpec> m_class_names; std::vector<std::unique_ptr<boost::regex>> m_cls; std::unique_ptr<boost::regex> m_anno; std::unique_ptr<boost::regex> m_extends; std::unique_ptr<boost::regex> m_extends_anno; std::unordered_map<const DexClass*, bool> m_extends_result_cache; }; enum class RuleType { WHY_ARE_YOU_KEEPING, KEEP, ASSUME_NO_SIDE_EFFECTS, }; std::string to_string(RuleType rule_type) { switch (rule_type) { case RuleType::WHY_ARE_YOU_KEEPING: return "whyareyoukeeping"; case RuleType::KEEP: return "classes and members"; case RuleType::ASSUME_NO_SIDE_EFFECTS: return "assumenosideeffects"; } } /* * Build a DAG of class -> subclass and implementors. This is fairly similar to * build_type_hierarchy and friends, but Proguard doesn't distinguish between * subclasses and interface implementors, so this function combines them * together. */ void build_extends_or_implements_hierarchy(const Scope& scope, ClassHierarchy* hierarchy) { for (const auto& cls : scope) { const auto* type = cls->get_type(); // ensure an entry for the DexClass is created (*hierarchy)[type]; const auto* super = cls->get_super_class(); if (super != nullptr) { (*hierarchy)[super].insert(type); } for (const auto& impl : *cls->get_interfaces()) { (*hierarchy)[impl].insert(type); } } } /* * This class contains the logic for matching against a single keep rule. */ class KeepRuleMatcher { public: KeepRuleMatcher(RuleType rule_type, const KeepSpec& keep_rule, RegexMap& regex_map) : m_rule_type(rule_type), m_keep_rule(keep_rule), m_regex_map(regex_map) {} ~KeepRuleMatcher() { TRACE(PGR, 3, "%s matched %lu classes and %lu members", show_keep(m_keep_rule).c_str(), m_class_matches, m_member_matches); } void keep_processor(DexClass*); void mark_class_and_members_for_keep(DexClass* cls); bool any_method_matches(const DexClass* cls, const MemberSpecification& method_keep, const boost::regex& method_regex); // Check that each method keep matches at least one method in :cls. bool all_method_keeps_match( const std::vector<MemberSpecification>& method_keeps, const DexClass* cls); bool any_field_matches(const DexClass* cls, const MemberSpecification& field_keep); // Check that each field keep matches at least one field in :cls. bool all_field_keeps_match( const std::vector<MemberSpecification>& field_keeps, const DexClass* cls); void process_whyareyoukeeping(DexClass* cls); bool process_mark_conditionally(const DexClass* cls); void process_assumenosideeffects(DexClass* cls); template <class DexMember> void apply_rule(DexMember*); void apply_field_keeps(const DexClass* cls); void apply_method_keeps(const DexClass* cls); template <class Container> void keep_fields(const Container& fields, const MemberSpecification& fieldSpecification, const boost::regex& fieldname_regex); template <class Container> void keep_methods(const MemberSpecification& methodSpecification, const Container& methods, const boost::regex& method_regex); bool field_level_match(const MemberSpecification& fieldSpecification, const DexField* field, const boost::regex& fieldname_regex); bool method_level_match(const MemberSpecification& methodSpecification, const DexMethod* method, const boost::regex& method_regex); template <class DexMember> bool has_annotation(const DexMember* member, const std::string& annotation) const; const boost::regex& register_matcher(const std::string& regex) const { auto it = m_regex_map.find(regex); if (it == m_regex_map.end()) { it = m_regex_map.emplace(regex, boost::regex{regex}).first; } return it->second; } bool is_unused() const { return m_class_matches == 0 && m_member_matches == 0; } private: void maybe_warn(const std::string& warning) { std::unique_lock<std::mutex> lock{m_warn_mutex}; if (m_already_warned.count(warning) > 0) { return; } m_already_warned.emplace(warning); std::cerr << warning << std::endl; } size_t m_member_matches{0}; size_t m_class_matches{0}; RuleType m_rule_type; const KeepSpec& m_keep_rule; RegexMap& m_regex_map; std::mutex m_warn_mutex; std::unordered_set<std::string> m_already_warned; }; class ProguardMatcher { public: ProguardMatcher(const ProguardMap& pg_map, const Scope& classes, const Scope& external_classes) : m_pg_map(pg_map), m_classes(classes), m_external_classes(external_classes) { build_extends_or_implements_hierarchy(m_classes, &m_hierarchy); // We need to include external classes in the hierarchy because keep rules // may, for instance, forbid renaming of all classes that inherit from a // given external class. build_extends_or_implements_hierarchy(m_external_classes, &m_hierarchy); } void process_proguard_rules(const ProguardConfiguration& pg_config); void mark_all_annotation_classes_as_keep(); void process_keep(const KeepSpecSet& keep_rules, RuleType rule_type, bool process_external = false); DexClass* find_single_class(const std::string& descriptor) const; const ConcurrentSet<const KeepSpec*>& get_unused_rules() const { return m_unused_rules; } private: const ProguardMap& m_pg_map; const Scope& m_classes; const Scope& m_external_classes; ClassHierarchy m_hierarchy; ConcurrentSet<const KeepSpec*> m_unused_rules; }; template <class DexMember> void apply_assume_field_return_value(const KeepSpec& k, DexMember* member) { for (auto& field_spec : k.class_spec.fieldSpecifications) { auto field_val = field_spec.return_value; switch (field_val.value_type) { case keep_rules::AssumeReturnValue::ValueBool: always_assert(type::is_boolean(member->get_type())); g_redex->set_field_value(member, field_val); continue; case keep_rules::AssumeReturnValue::ValueNone: g_redex->unset_field_value(member); continue; } } } template <class DexMember> void apply_assume_method_return_value(const KeepSpec& k, DexMember* member) { for (auto& method_spec : k.class_spec.methodSpecifications) { auto return_val = method_spec.return_value; switch (return_val.value_type) { case keep_rules::AssumeReturnValue::ValueBool: always_assert(type::is_boolean(member->get_proto()->get_rtype())); g_redex->set_return_value(member, return_val); continue; case keep_rules::AssumeReturnValue::ValueNone: g_redex->unset_return_value(member); continue; } } } // Updates a class, field or method to add keep modifiers. // Note: includedescriptorclasses and allowoptimization are not implemented. template <class DexMember> void apply_keep_modifiers(const KeepSpec& k, DexMember* member) { // We only set allowshrinking when no other keep rule has been applied to this // class or member. // // Note that multiple keep rules could set or unset the modifier // *conflictingly*. It would be best if all the keep rules are never // contradictory each other. But verifying the integrity takes some time, and // programmers must fix the rules. Instead, we pick a conservative choice: // don't shrink or don't obfuscate. if (k.allowshrinking) { if (!impl::KeepState::has_keep(member)) { impl::KeepState::set_allowshrinking(member); } else { // We already observed a keep rule for this member. So, even if another // "-keep,allowshrinking" tries to set allowshrinking, we must ignore it. } } else { // Otherwise reset it: don't allow shrinking. impl::KeepState::unset_allowshrinking(member); } // The same case: unsetting allowobfuscation has a priority. if (k.allowobfuscation) { if (!impl::KeepState::has_keep(member) && strcmp(member->get_name()->c_str(), "<init>") != 0) { impl::KeepState::set_allowobfuscation(member); } } else { impl::KeepState::unset_allowobfuscation(member); } } template <class DexMember> bool KeepRuleMatcher::has_annotation(const DexMember* member, const std::string& annotation) const { auto annos = member->get_anno_set(); if (annos == nullptr) { return false; } if (!proguard_parser::has_special_char(annotation)) { for (const auto& anno : annos->get_annotations()) { if (get_deobfuscated_name(anno->type()) == annotation) { return true; } } } else { auto annotation_regex = proguard_parser::form_type_regex(annotation); const boost::regex& annotation_matcher = register_matcher(annotation_regex); for (const auto& anno : annos->get_annotations()) { if (boost::regex_match(get_deobfuscated_name(anno->type()), annotation_matcher)) { return true; } } } return false; } // From a fully qualified descriptor for a field, exract just the // name of the field which occurs between the ;. and : characters. std::string extract_field_name(std::string qualified_fieldname) { auto p = qualified_fieldname.find(";."); if (p == std::string::npos) { return qualified_fieldname; } return qualified_fieldname.substr(p + 2); } std::string extract_method_name_and_type( const std::string& qualified_fieldname) { auto p = qualified_fieldname.find(";."); return qualified_fieldname.substr(p + 2); } bool KeepRuleMatcher::field_level_match( const MemberSpecification& fieldSpecification, const DexField* field, const boost::regex& fieldname_regex) { // Check for annotation guards. if (!(fieldSpecification.annotationType.empty())) { if (!has_annotation(field, fieldSpecification.annotationType)) { return false; } } // Check for access match. if (!access_matches(fieldSpecification.requiredSetAccessFlags, fieldSpecification.requiredUnsetAccessFlags, field->get_access())) { return false; } // Match field name against regex. auto dequalified_name = extract_field_name(field->get_deobfuscated_name()); return boost::regex_match(dequalified_name, fieldname_regex); } template <class Container> void KeepRuleMatcher::keep_fields(const Container& fields, const MemberSpecification& fieldSpecification, const boost::regex& fieldname_regex) { for (DexField* field : fields) { if (!field_level_match(fieldSpecification, field, fieldname_regex)) { continue; } if (m_rule_type == RuleType::KEEP) { apply_keep_modifiers(m_keep_rule, field); } if (m_rule_type == RuleType::ASSUME_NO_SIDE_EFFECTS) { apply_assume_field_return_value(m_keep_rule, field); } apply_rule(field); } } std::string field_regex(const MemberSpecification& field_spec) { string_builders::StaticStringBuilder<3> ss; ss << proguard_parser::form_member_regex(field_spec.name); ss << "\\:"; ss << proguard_parser::form_type_regex(field_spec.descriptor); return ss.str(); } void KeepRuleMatcher::apply_field_keeps(const DexClass* cls) { for (const auto& field_spec : m_keep_rule.class_spec.fieldSpecifications) { auto fieldname_regex = field_regex(field_spec); const boost::regex& matcher = register_matcher(fieldname_regex); keep_fields(cls->get_ifields(), field_spec, matcher); keep_fields(cls->get_sfields(), field_spec, matcher); } } bool KeepRuleMatcher::method_level_match( const MemberSpecification& methodSpecification, const DexMethod* method, const boost::regex& method_regex) { // Check to see if the method match is guarded by an annotation match. if (!(methodSpecification.annotationType.empty())) { if (!has_annotation(method, methodSpecification.annotationType)) { return false; } } if (!access_matches(methodSpecification.requiredSetAccessFlags, methodSpecification.requiredUnsetAccessFlags, method->get_access())) { return false; } auto dequalified_name = extract_method_name_and_type(method->get_deobfuscated_name().str()); return boost::regex_match(dequalified_name.c_str(), method_regex); } template <class Container> void KeepRuleMatcher::keep_methods( const MemberSpecification& methodSpecification, const Container& methods, const boost::regex& method_regex) { for (DexMethod* method : methods) { if (method_level_match(methodSpecification, method, method_regex)) { if (m_rule_type == RuleType::KEEP) { apply_keep_modifiers(m_keep_rule, method); } if (m_rule_type == RuleType::ASSUME_NO_SIDE_EFFECTS) { apply_assume_method_return_value(m_keep_rule, method); } apply_rule(method); } } } std::string method_regex(const MemberSpecification& method_spec) { auto qualified_method_regex = proguard_parser::form_member_regex(method_spec.name); qualified_method_regex += "\\:"; qualified_method_regex += proguard_parser::form_type_regex(method_spec.descriptor); return qualified_method_regex; } void KeepRuleMatcher::apply_method_keeps(const DexClass* cls) { auto methodSpecifications = m_keep_rule.class_spec.methodSpecifications; for (auto& method_spec : methodSpecifications) { auto qualified_method_regex = method_regex(method_spec); const boost::regex& method_regex = register_matcher(qualified_method_regex); keep_methods(method_spec, cls->get_vmethods(), method_regex); keep_methods(method_spec, cls->get_dmethods(), method_regex); } } bool classname_contains_wildcard(const std::string& classname) { for (char ch : classname) { if (ch == '*' || ch == '?' || ch == '!' || ch == '%' || ch == ',') { return true; } } return false; } bool KeepRuleMatcher::any_method_matches(const DexClass* cls, const MemberSpecification& method_keep, const boost::regex& method_regex) { auto match = [&](const DexMethod* method) { return method_level_match(method_keep, method, method_regex); }; return std::any_of(cls->get_vmethods().begin(), cls->get_vmethods().end(), match) || std::any_of(cls->get_dmethods().begin(), cls->get_dmethods().end(), match); } // Check that each method keep matches at least one method in :cls. bool KeepRuleMatcher::all_method_keeps_match( const std::vector<MemberSpecification>& method_keeps, const DexClass* cls) { return std::all_of(method_keeps.begin(), method_keeps.end(), [&](const MemberSpecification& method_keep) { auto qualified_method_regex = method_regex(method_keep); const boost::regex& matcher = register_matcher(qualified_method_regex); return any_method_matches(cls, method_keep, matcher); }); } bool KeepRuleMatcher::any_field_matches(const DexClass* cls, const MemberSpecification& field_keep) { auto fieldtype_regex = field_regex(field_keep); const boost::regex& matcher = register_matcher(fieldtype_regex); auto match = [&](const DexField* field) { return field_level_match(field_keep, field, matcher); }; return std::any_of(cls->get_ifields().begin(), cls->get_ifields().end(), match) || std::any_of(cls->get_sfields().begin(), cls->get_sfields().end(), match); } // Check that each field keep matches at least one field in :cls. bool KeepRuleMatcher::all_field_keeps_match( const std::vector<MemberSpecification>& field_keeps, const DexClass* cls) { return std::all_of(field_keeps.begin(), field_keeps.end(), [&](const MemberSpecification& field_keep) { return any_field_matches(cls, field_keep); }); } bool KeepRuleMatcher::process_mark_conditionally(const DexClass* cls) { const auto& class_spec = m_keep_rule.class_spec; if (class_spec.fieldSpecifications.empty() && class_spec.methodSpecifications.empty()) { std::cerr << "WARNING: A keepclasseswithmembers rule for class " << class_spec.class_names_str() << " has no field or member specifications.\n"; } return all_field_keeps_match(class_spec.fieldSpecifications, cls) && all_method_keeps_match(class_spec.methodSpecifications, cls); } // Once a match has been made against a class i.e. the class name // matches, the annotations match, the extends clause matches and the // access modifier filters match, then start to apply the keep control // bits to the class, members and appropriate classes and members // in the class hierarchy. // // Parallelization note: We parallelize process_keep, and this function will be // eventually executed concurrently. There are potential races in rstate: // (1) m_keep and (2) m_(un)set_allow(shrinking|obfuscation). These values are // always overwritten. These WAW (write-after-write) races are benign and do not // affect the results. void KeepRuleMatcher::mark_class_and_members_for_keep(DexClass* cls) { // First check to see if we need to mark conditionally to see if all // field and method rules match i.e. we have a -keepclasseswithmembers // rule to process. if (m_keep_rule.mark_conditionally) { // If this class does not incur at least one match for each field // and method rule, then don't mark this class or its members. if (!process_mark_conditionally(cls)) { return; } } // Mark descriptor classes if (m_keep_rule.includedescriptorclasses) { std::ostringstream oss; oss << "WARNING: 'includedescriptorclasses' keep modifier is NOT " "implemented: " << show_keep(m_keep_rule); maybe_warn(oss.str()); } if (m_keep_rule.allowoptimization) { std::ostringstream oss; oss << "WARNING: 'allowoptimization' keep modifier is NOT implemented: " << show_keep(m_keep_rule); maybe_warn(oss.str()); } if (m_keep_rule.mark_classes || m_keep_rule.mark_conditionally) { apply_keep_modifiers(m_keep_rule, cls); impl::KeepState::set_has_keep(cls, &m_keep_rule); ++m_class_matches; if (cls->rstate.report_whyareyoukeeping()) { TRACE(PGR, 2, "whyareyoukeeping Class %s kept by %s", java_names::internal_to_external(cls->get_deobfuscated_name().str()) .c_str(), show_keep(m_keep_rule).c_str()); } } // Walk up the hierarchy performing seed marking. DexClass* class_to_mark = cls; while (class_to_mark != nullptr && !class_to_mark->is_external()) { // Mark unconditionally. apply_field_keeps(class_to_mark); apply_method_keeps(class_to_mark); auto typ = class_to_mark->get_super_class(); if (typ == nullptr) { break; } class_to_mark = type_class(typ); } } // This function is also executed concurrently. void KeepRuleMatcher::process_whyareyoukeeping(DexClass* cls) { cls->rstate.set_whyareyoukeeping(); apply_field_keeps(cls); // Set any method-level keep whyareyoukeeping bits. apply_method_keeps(cls); } // This function is also executed concurrently. void KeepRuleMatcher::process_assumenosideeffects(DexClass* cls) { cls->rstate.set_assumenosideeffects(); // Apply any method-level keep specifications. apply_method_keeps(cls); } template <class DexMember> void KeepRuleMatcher::apply_rule(DexMember* member) { switch (m_rule_type) { case RuleType::WHY_ARE_YOU_KEEPING: member->rstate.set_whyareyoukeeping(); break; case RuleType::KEEP: { impl::KeepState::set_has_keep(member, &m_keep_rule); ++m_member_matches; if (member->rstate.report_whyareyoukeeping()) { TRACE(PGR, 2, "whyareyoukeeping %s kept by %s", SHOW(member), show_keep(m_keep_rule).c_str()); } break; } case RuleType::ASSUME_NO_SIDE_EFFECTS: member->rstate.set_assumenosideeffects(); break; } } void KeepRuleMatcher::keep_processor(DexClass* cls) { switch (m_rule_type) { case RuleType::WHY_ARE_YOU_KEEPING: process_whyareyoukeeping(cls); break; case RuleType::KEEP: mark_class_and_members_for_keep(cls); break; case RuleType::ASSUME_NO_SIDE_EFFECTS: process_assumenosideeffects(cls); break; } } DexClass* ProguardMatcher::find_single_class( const std::string& descriptor) const { auto const& dsc = java_names::external_to_internal(descriptor); DexType* typ = DexType::get_type(m_pg_map.translate_class(dsc).c_str()); if (typ == nullptr) { typ = DexType::get_type(dsc.c_str()); if (typ == nullptr) { return nullptr; } } return type_class(typ); } void ProguardMatcher::process_keep(const KeepSpecSet& keep_rules, RuleType rule_type, bool process_external) { Timer t("Process keep for " + to_string(rule_type)); // Classes are aligned by 8. Shard size should be (co-)prime for good // distribution. constexpr size_t LOCKS = 1039u; std::array<std::mutex, LOCKS> locks; auto get_lock = [&locks](const DexClass* cls) -> std::mutex& { return locks[reinterpret_cast<uintptr_t>(cls) % LOCKS]; }; auto process_single_keep = [process_external, &get_lock](ClassMatcher& class_match, KeepRuleMatcher& rule_matcher, DexClass* cls) { // Skip external classes. if (cls == nullptr || (!process_external && cls->is_external())) { return; } if (class_match.match(cls)) { std::unique_lock<std::mutex> lock(get_lock(cls)); rule_matcher.keep_processor(cls); } }; // We only parallelize if keep_rule needs to be applied to all classes. auto wq = workqueue_foreach<const KeepSpec*>([&](const KeepSpec* keep_rule) { RegexMap regex_map; ClassMatcher class_match(*keep_rule); KeepRuleMatcher rule_matcher(rule_type, *keep_rule, regex_map); for (const auto& cls : m_classes) { process_single_keep(class_match, rule_matcher, cls); } if (process_external) { for (const auto& cls : m_external_classes) { process_single_keep(class_match, rule_matcher, cls); } } if (rule_matcher.is_unused()) { m_unused_rules.insert(keep_rule); } }); RegexMap regex_map; for (const auto& keep_rule_ptr : keep_rules) { const auto& keep_rule = *keep_rule_ptr; ClassMatcher class_match(keep_rule); bool has_negation = std::any_of(keep_rule.class_spec.classNames.begin(), keep_rule.class_spec.classNames.end(), [](const auto& v) { return v.negated; }); if (!has_negation) { // This case is very fast. Just process it immediately in the main thread. bool class_with_wildcard = false; for (const auto& className : keep_rule.class_spec.classNames) { if (!classname_contains_wildcard(className.name)) { DexClass* cls = find_single_class(className.name); KeepRuleMatcher rule_matcher(rule_type, keep_rule, regex_map); process_single_keep(class_match, rule_matcher, cls); if (rule_matcher.is_unused()) { m_unused_rules.insert(&keep_rule); } } else { class_with_wildcard = true; } } if (!class_with_wildcard) { continue; } // This is also very fast. Process it in the main thread, too. const auto& extendsClassName = keep_rule.class_spec.extendsClassName; if (!extendsClassName.empty() && !classname_contains_wildcard(extendsClassName)) { DexClass* super = find_single_class(extendsClassName); if (super != nullptr) { KeepRuleMatcher rule_matcher(rule_type, keep_rule, regex_map); auto children = get_all_children(m_hierarchy, super->get_type()); process_single_keep(class_match, rule_matcher, super); for (auto const* type : children) { process_single_keep(class_match, rule_matcher, type_class(type)); } if (rule_matcher.is_unused()) { m_unused_rules.insert(&keep_rule); } } continue; } } TRACE(PGR, 2, "Slow rule: %s", show_keep(keep_rule).c_str()); // Otherwise, it might take a longer time. Add to the work queue. wq.add_item(&keep_rule); } wq.run_all(); } void ProguardMatcher::process_proguard_rules( const ProguardConfiguration& pg_config) { // Now process each of the different kinds of rules as well // as -assumenosideeffects and -whyareyoukeeping. process_keep(pg_config.whyareyoukeeping_rules, RuleType::WHY_ARE_YOU_KEEPING); process_keep(pg_config.keep_rules, RuleType::KEEP); process_keep(pg_config.assumenosideeffects_rules, RuleType::ASSUME_NO_SIDE_EFFECTS, /* process_external = */ true); } void ProguardMatcher::mark_all_annotation_classes_as_keep() { for (auto cls : m_classes) { if (is_annotation(cls)) { impl::KeepState::set_has_keep(cls, keep_reason::ANNO); if (cls->rstate.report_whyareyoukeeping()) { TRACE(PGR, 2, "whyareyoukeeping Class %s kept because it is an annotation " "class\n", java_names::internal_to_external( cls->get_deobfuscated_name_or_empty()) .c_str()); } } } } } // namespace namespace keep_rules { ConcurrentSet<const KeepSpec*> process_proguard_rules( const ProguardMap& pg_map, const Scope& classes, const Scope& external_classes, const ProguardConfiguration& pg_config, bool keep_all_annotation_classes) { ProguardMatcher pg_matcher(pg_map, classes, external_classes); pg_matcher.process_proguard_rules(pg_config); if (keep_all_annotation_classes) { pg_matcher.mark_all_annotation_classes_as_keep(); } return pg_matcher.get_unused_rules(); } namespace testing { bool matches(const KeepSpec& ks, const DexClass* c) { ClassMatcher cm{ks}; return cm.match(c); } } // namespace testing } // namespace keep_rules