libredex/ReferencedState.h (297 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. */ #pragma once #include <atomic> #include <boost/optional.hpp> #include <limits> #include <mutex> #include <string> #include "Debug.h" #include "KeepReason.h" namespace ir_meta_io { class IRMetaIO; } // namespace ir_meta_io namespace keep_rules { namespace impl { class KeepState; } // namespace impl } // namespace keep_rules using InterdexSubgroupIdx = uint32_t; class ReferencedState { private: struct InnerStruct { int8_t m_api_level{-1}; // Whether this DexMember is referenced by one of the strings in the native // libraries. Note that this doesn't allow us to distinguish // native -> Java references from Java -> native refs. bool m_by_string : 1; // Whether it is referenced from an XML layout. bool m_by_resources : 1; // Whether it is a json serializer/deserializer class for a reachable class. bool m_is_serde : 1; // ProGuard keep settings // // Whether any keep rule has matched this. This applies for both `-keep` and // `-keepnames`. bool m_keep : 1; // assumenosideeffects allows certain methods to be removed. bool m_assumenosideeffects : 1; // If m_whyareyoukeeping is true then report debugging information // about why this class or member is being kept. bool m_whyareyoukeeping : 1; // For keep modifiers: -keep,allowshrinking and -keep,allowobfuscation. // // Instead of m_allowshrinking and m_allowobfuscation, we need to have // set/unset pairs for easier parallelization. The unset has a high // priority. See the comments in apply_keep_modifiers. bool m_set_allowshrinking : 1; bool m_unset_allowshrinking : 1; bool m_set_allowobfuscation : 1; bool m_unset_allowobfuscation : 1; bool m_no_optimizations : 1; bool m_generated : 1; // For inlining configurations. bool m_dont_inline : 1; bool m_force_inline : 1; // This is set by the ImmutableGetters pass. It indicates that a method // is pure. bool m_immutable_getter : 1; // This is set by the AnalyzePureMethodsPass. It indicates that a method // is pure as defined in Purity.h. // *WARNING* // Any optimisation that might alter the semantics in such a way that it is // no longer pure should invalidate this flag. It could also be invalidated // by running AnalyzePureMethodsPass which would recompute it. bool m_pure_method : 1; // Whether this member is an outlined class or method. bool m_outlined : 1; // Is this member is a kotlin class or method bool m_is_kotlin : 1; bool m_name_used : 1; // Whether a field is used to indicate that an sget cannot be removed // because it signals that the class must be initialized at this point. bool m_init_class : 1; // This may be set on classes, indicating that its static initializer has no // side effects. bool m_clinit_has_no_side_effects : 1; InnerStruct() { // Initializers in bit fields are C++20... m_by_string = false; m_by_resources = false; m_is_serde = false; m_keep = false; m_assumenosideeffects = false; m_whyareyoukeeping = false; m_set_allowshrinking = false; m_unset_allowshrinking = false; m_set_allowobfuscation = false; m_unset_allowobfuscation = false; m_no_optimizations = false; m_generated = false; m_dont_inline = false; m_force_inline = false; m_immutable_getter = false; m_pure_method = false; m_outlined = false; m_is_kotlin = false; m_name_used = false; m_init_class = false; m_clinit_has_no_side_effects = false; } } inner_struct; // InterDex subgroup, if any. // NOTE: Will be set ONLY for generated classes. static constexpr InterdexSubgroupIdx kNoSubgroup = std::numeric_limits<InterdexSubgroupIdx>::max(); InterdexSubgroupIdx m_interdex_subgroup{kNoSubgroup}; // Going through hoops here to reduce the size of ReferencedState while // keeping memory requirements still small in non-default case. struct KeepReasons { std::mutex m_keep_reasons_mtx; keep_reason::ReasonPtrSet m_keep_reasons; }; mutable std::atomic<KeepReasons*> m_keep_reasons{nullptr}; public: ReferencedState() = default; ReferencedState(const ReferencedState&) = delete; ~ReferencedState() { delete m_keep_reasons.load(); } ReferencedState& operator=(const ReferencedState& other) { if (this != &other) { this->inner_struct = other.inner_struct; if (other.m_keep_reasons.load() != nullptr) { auto& k = ensure_keep_reasons(); k.m_keep_reasons = other.m_keep_reasons.load()->m_keep_reasons; } } return *this; } void join_with(const ReferencedState& other) { if (this != &other) { this->inner_struct.m_by_string = this->inner_struct.m_by_string | other.inner_struct.m_by_string; this->inner_struct.m_by_resources = this->inner_struct.m_by_resources | other.inner_struct.m_by_resources; this->inner_struct.m_is_serde = this->inner_struct.m_is_serde | other.inner_struct.m_is_serde; this->inner_struct.m_keep = this->inner_struct.m_keep | other.inner_struct.m_keep; this->inner_struct.m_assumenosideeffects = this->inner_struct.m_assumenosideeffects & other.inner_struct.m_assumenosideeffects; this->inner_struct.m_whyareyoukeeping = this->inner_struct.m_whyareyoukeeping | other.inner_struct.m_whyareyoukeeping; this->inner_struct.m_set_allowshrinking = this->inner_struct.m_set_allowshrinking & other.inner_struct.m_set_allowshrinking; this->inner_struct.m_unset_allowshrinking = this->inner_struct.m_unset_allowshrinking | other.inner_struct.m_unset_allowshrinking; this->inner_struct.m_set_allowobfuscation = this->inner_struct.m_set_allowobfuscation & other.inner_struct.m_set_allowobfuscation; this->inner_struct.m_unset_allowobfuscation = this->inner_struct.m_unset_allowobfuscation | other.inner_struct.m_unset_allowobfuscation; this->inner_struct.m_no_optimizations = this->inner_struct.m_no_optimizations | other.inner_struct.m_no_optimizations; this->inner_struct.m_dont_inline = this->inner_struct.m_dont_inline | other.inner_struct.m_dont_inline; this->inner_struct.m_force_inline = this->inner_struct.m_force_inline & other.inner_struct.m_force_inline; this->inner_struct.m_immutable_getter = this->inner_struct.m_immutable_getter & other.inner_struct.m_immutable_getter; this->inner_struct.m_pure_method = this->inner_struct.m_pure_method & other.inner_struct.m_pure_method; this->inner_struct.m_is_kotlin = this->inner_struct.m_is_kotlin & other.inner_struct.m_is_kotlin; this->inner_struct.m_outlined = this->inner_struct.m_outlined & other.inner_struct.m_outlined; this->inner_struct.m_init_class = this->inner_struct.m_init_class | other.inner_struct.m_init_class; } } std::string str() const; // ProGuard keep options // -keep bool can_delete() const { return (!inner_struct.m_keep || allowshrinking()) && !inner_struct.m_by_resources; } // -keepnames bool can_rename() const { return can_rename_if_also_renaming_xml() && !inner_struct.m_by_resources && !inner_struct.m_name_used; } /* * Returns whether :member can be renamed if references to it from XML * resources are also updated accordingly. The optimizing pass in question * will be responsible for updating the XML resources. */ bool can_rename_if_also_renaming_xml() const { return (!inner_struct.m_name_used && !inner_struct.m_keep) || allowobfuscation(); } bool assumenosideeffects() const { return inner_struct.m_assumenosideeffects; } bool report_whyareyoukeeping() const { return inner_struct.m_whyareyoukeeping; } // For example, a classname in a layout, e.g. <com.facebook.MyCustomView /> or // Class c = Class.forName("com.facebook.FooBar"); void ref_by_string() { inner_struct.m_by_string = true; } bool is_referenced_by_string() const { return inner_struct.m_by_string; } // A class referenced by resource XML can take the following forms in .xml // files under the res/ directory: // <com.facebook.FooView /> // <fragment android:name="com.facebook.BarFragment" /> // // This differs from "by_string" reference since it is possible to rename // these string references, and potentially eliminate dead resource .xml files void set_referenced_by_resource_xml() { inner_struct.m_by_resources = true; if (keep_reason::Reason::record_keep_reasons()) { add_keep_reason(keep_reason::Reason::make_keep_reason(keep_reason::XML)); } } void unset_referenced_by_resource_xml() { inner_struct.m_by_resources = false; // TODO: Remove the XML-related keep reasons } bool is_referenced_by_resource_xml() const { return inner_struct.m_by_resources; } void set_is_serde() { inner_struct.m_is_serde = true; } bool is_serde() const { return inner_struct.m_is_serde; } void set_root() { set_root(keep_reason::UNKNOWN); } /* * Mark this DexMember as an entry point that should not be deleted or * renamed. * * The ...args are arguments to the keep_reason::Reason constructor. * The typical Redex run does not care to keep the extra diagnostic * information of the keep reasons, so it seems worthwhile to forward these * arguments to avoid the construction of unused Reason objects when * record_keep_reasons() is false. */ template <class... Args> void set_root(Args&&... args) { inner_struct.m_keep = true; unset_allowshrinking(); unset_allowobfuscation(); if (keep_reason::Reason::record_keep_reasons()) { add_keep_reason( keep_reason::Reason::make_keep_reason(std::forward<Args>(args)...)); } } void unset_root() { inner_struct.m_keep = false; inner_struct.m_unset_allowshrinking = false; inner_struct.m_unset_allowobfuscation = false; } const keep_reason::ReasonPtrSet& keep_reasons() const { if (!keep_reason::Reason::record_keep_reasons()) { // We really should not allow this. static keep_reason::ReasonPtrSet SINGLETON; return SINGLETON; } auto& keep_reasons = ensure_keep_reasons(); return keep_reasons.m_keep_reasons; } template <class... Args> void set_keepnames(Args&&... args) { set_has_keep(std::forward<Args>(args)...); set_allowshrinking(); unset_allowobfuscation(); } void set_keepnames() { set_keepnames(keep_reason::UNKNOWN); } // This one should only be used by UnmarkProguardKeepPass to unmark proguard // keep rule after proguard file processing is finished. Because // ProguardMatcher uses parallel processing, using this will result in race // condition. void force_unset_allowshrinking() { inner_struct.m_set_allowshrinking = true; inner_struct.m_unset_allowshrinking = false; } void set_assumenosideeffects() { inner_struct.m_assumenosideeffects = true; } void set_whyareyoukeeping() { inner_struct.m_whyareyoukeeping = true; } void set_interdex_subgroup( const boost::optional<InterdexSubgroupIdx>& interdex_subgroup) { m_interdex_subgroup = interdex_subgroup ? *interdex_subgroup : kNoSubgroup; } InterdexSubgroupIdx get_interdex_subgroup() const { return m_interdex_subgroup; } bool has_interdex_subgroup() const { return m_interdex_subgroup != kNoSubgroup; } // -1 means unknown, e.g. for a method created by Redex int8_t get_api_level() const { return inner_struct.m_api_level; } void set_api_level(int32_t api_level) { redex_assert(api_level <= std::numeric_limits<int8_t>::max()); inner_struct.m_api_level = api_level; } bool no_optimizations() const { return inner_struct.m_no_optimizations; } void set_no_optimizations() { inner_struct.m_no_optimizations = true; } // Methods and classes marked as "generated" tend to not have stable names, // and don't properly participate in coldstart tracking. bool is_generated() const { return inner_struct.m_generated; } void set_generated() { inner_struct.m_generated = true; } bool force_inline() const { return inner_struct.m_force_inline; } void set_force_inline() { inner_struct.m_force_inline = true; } bool dont_inline() const { return inner_struct.m_dont_inline; } void set_dont_inline() { inner_struct.m_dont_inline = true; } bool immutable_getter() const { return inner_struct.m_immutable_getter; } void set_immutable_getter() { inner_struct.m_immutable_getter = true; } bool pure_method() const { return inner_struct.m_pure_method; } void set_pure_method() { inner_struct.m_pure_method = true; } void reset_pure_method() { inner_struct.m_pure_method = false; } bool outlined() const { return inner_struct.m_outlined; } void set_outlined() { inner_struct.m_outlined = true; } void reset_outlined() { inner_struct.m_outlined = false; } bool is_cls_kotlin() const { return inner_struct.m_is_kotlin; } void set_cls_kotlin() { inner_struct.m_is_kotlin = true; } void set_name_used() { inner_struct.m_name_used = true; } bool name_used() { return inner_struct.m_name_used; } bool init_class() const { return inner_struct.m_init_class; } void set_init_class() { inner_struct.m_init_class = true; } void set_clinit_has_no_side_effects() { inner_struct.m_clinit_has_no_side_effects = true; } bool clinit_has_no_side_effects() const { return inner_struct.m_clinit_has_no_side_effects; } private: // Does any keep rule (whether -keep or -keepnames) match this DexMember? bool has_keep() const { return inner_struct.m_keep; } /* * This should only be called from ProguardMatcher, and is used whenever we * encounter a keep rule (regardless of whether it's `-keep` or `-keepnames`). */ template <class... Args> void set_has_keep(Args&&... args) { inner_struct.m_keep = true; if (keep_reason::Reason::record_keep_reasons()) { add_keep_reason( keep_reason::Reason::make_keep_reason(std::forward<Args>(args)...)); } } // There's generally no need to call this; use can_delete() instead. bool allowshrinking() const { return !inner_struct.m_unset_allowshrinking && inner_struct.m_set_allowshrinking; } void set_allowshrinking() { inner_struct.m_set_allowshrinking = true; } void unset_allowshrinking() { inner_struct.m_unset_allowshrinking = true; } // There's generally no need to call this; use can_rename() instead. bool allowobfuscation() const { return !inner_struct.m_unset_allowobfuscation && inner_struct.m_set_allowobfuscation; } void set_allowobfuscation() { inner_struct.m_set_allowobfuscation = true; } void unset_allowobfuscation() { inner_struct.m_unset_allowobfuscation = true; } KeepReasons& ensure_keep_reasons() const { always_assert(keep_reason::Reason::record_keep_reasons()); // First see whether it's already done to avoid allocating. auto attempt = m_keep_reasons.load(); if (attempt != nullptr) { return *attempt; } // OK, allocate and CAS. auto keep_reasons = std::make_unique<KeepReasons>(); KeepReasons* expected = nullptr; if (m_keep_reasons.compare_exchange_strong(expected, keep_reasons.get())) { return *keep_reasons.release(); } return *expected; } void add_keep_reason(const keep_reason::Reason* reason) { always_assert(keep_reason::Reason::record_keep_reasons()); auto& keep_reasons = ensure_keep_reasons(); std::lock_guard<std::mutex> lock(keep_reasons.m_keep_reasons_mtx); keep_reasons.m_keep_reasons.emplace(reason); } friend class keep_rules::impl::KeepState; // IR serialization class friend class ir_meta_io::IRMetaIO; };