opt/singleimpl/SingleImplOptimize.cpp (604 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 <functional> #include <memory> #include <set> #include <stdio.h> #include <string> #include <unordered_map> #include <unordered_set> #include "CheckCastAnalysis.h" #include "CheckCastTransform.h" #include "ClassHierarchy.h" #include "ConstantPropagationAnalysis.h" #include "ConstantPropagationTransform.h" #include "ConstantPropagationWholeProgramState.h" #include "Debug.h" #include "DexLoader.h" #include "DexOutput.h" #include "DexUtil.h" #include "IRCode.h" #include "IRList.h" #include "IRTypeChecker.h" #include "LocalDce.h" #include "Resolver.h" #include "ScopedCFG.h" #include "SingleImpl.h" #include "SingleImplDefs.h" #include "Trace.h" #include "TypeReference.h" #include "Walkers.h" namespace { /** * Rewrite all typerefs from the interfaces to the concrete type. */ void set_type_refs(const DexType* intf, const SingleImplData& data) { for (auto opcode : data.typerefs) { TRACE(INTF, 3, "(TREF) %s", SHOW(opcode)); redex_assert(opcode->get_type() == intf); opcode->set_type(data.cls); TRACE(INTF, 3, "(TREF) \t=> %s", SHOW(opcode)); } } /** * Get or create a new proto given an original proto and an interface to be * substituted by an implementation. */ DexProto* get_or_make_proto(const DexType* intf, DexType* impl, DexProto* proto) { DexType* rtype = proto->get_rtype(); if (rtype == intf) rtype = impl; DexTypeList* new_args = nullptr; const auto args = proto->get_args(); DexTypeList::ContainerType new_arg_list; for (const auto arg : *args) { new_arg_list.push_back(arg == intf ? impl : arg); } new_args = DexTypeList::make_type_list(std::move(new_arg_list)); return DexProto::make_proto(rtype, new_args, proto->get_shorty()); } /** * Given a new method and a corresponding existing, set up the new method * with everything from the original one. */ void setup_method(DexMethod* orig_method, DexMethod* new_method) { auto method_anno = orig_method->get_anno_set(); if (method_anno) { new_method->attach_annotation_set( std::make_unique<DexAnnotationSet>(*method_anno)); } const auto& params_anno = orig_method->get_param_anno(); if (params_anno) { for (auto const& param_anno : *params_anno) { new_method->attach_param_annotation_set( param_anno.first, std::make_unique<DexAnnotationSet>(*param_anno.second)); } } new_method->make_concrete(orig_method->get_access(), orig_method->release_code(), orig_method->is_virtual()); } /** * Remove interfaces from classes. We walk the interface chain and move * down parent interfaces as needed so the contract of the class stays * the same. */ void remove_interface(const DexType* intf, const SingleImplData& data) { auto cls = type_class(data.cls); TRACE(INTF, 3, "(REMI) %s", SHOW(intf)); // the interface and all its methods are public, but the impl may not be. // We make the impl public given the impl is now a substitute of the // interface. Doing the analysis to see all accesses would allow us to // determine proper visibility but for now we conservatively flip the impl // to public set_public(cls); // removing interfaces may bring the same parent interface down to the // concrete class, so use a set to guarantee uniqueness std::unordered_set<DexType*> new_intfs; auto collect_interfaces = [&](DexClass* impl) { auto intfs = impl->get_interfaces(); for (auto type : *intfs) { if (intf != type) { // make interface public if it was not already. It may happen // the parent interface is package protected (a type cannot be // private or protected) but the type implementing it is in a // different package. Make the interface public then auto type_cls = type_class(type); if (type_cls != nullptr) { if (!is_public(cls)) { set_public(type_cls); } TRACE(INTF, 4, "(REMI) make PUBLIC - %s", SHOW(type)); } new_intfs.insert(type); continue; } } }; collect_interfaces(cls); auto intf_cls = type_class(intf); collect_interfaces(intf_cls); DexTypeList::ContainerType revisited_intfs{new_intfs.begin(), new_intfs.end()}; std::sort(revisited_intfs.begin(), revisited_intfs.end(), compare_dextypes); cls->set_interfaces(DexTypeList::make_type_list(std::move(revisited_intfs))); cls->combine_annotations_with(intf_cls); TRACE(INTF, 3, "(REMI)\t=> %s", SHOW(cls)); } bool must_rewrite_annotations(const SingleImplConfig& config) { return config.field_anno || config.intf_anno || config.meth_anno; } bool must_set_method_annotations(const SingleImplConfig& config) { return config.meth_anno; } /** * Update method proto from an old type reference to a new one. Return true if * the method is updated, return false if the method proto does not contain the * old type reference, crash if the updated method will collide with an existing * method. */ bool update_method_proto(const DexType* old_type_ref, DexType* new_type_ref, DexMethodRef* method) { auto proto = get_or_make_proto(old_type_ref, new_type_ref, method->get_proto()); if (proto == method->get_proto()) { return false; } DexMethodSpec spec; spec.proto = proto; method->change(spec, false /* rename on collision */); return true; } using CheckCastSet = std::unordered_set<const IRInstruction*>; struct OptimizationImpl { OptimizationImpl(std::unique_ptr<SingleImplAnalysis> analysis, const ClassHierarchy& ch) : single_impls(std::move(analysis)), ch(ch) {} OptimizeStats optimize(Scope& scope, const SingleImplConfig& config); private: EscapeReason can_optimize(const DexType* intf, const SingleImplData& data, bool rename_on_collision); CheckCastSet do_optimize(const DexType* intf, const SingleImplData& data); EscapeReason check_field_collision(const DexType* intf, const SingleImplData& data); EscapeReason check_method_collision(const DexType* intf, const SingleImplData& data); void drop_single_impl_collision(const DexType* intf, const SingleImplData& data, DexMethod* method); void set_field_defs(const DexType* intf, const SingleImplData& data); void set_field_refs(const DexType* intf, const SingleImplData& data); CheckCastSet fix_instructions(const DexType* intf, const SingleImplData& data); void set_method_defs(const DexType* intf, const SingleImplData& data); void set_method_refs(const DexType* intf, const SingleImplData& data); void rewrite_interface_methods(const DexType* intf, const SingleImplData& data); void rewrite_annotations(Scope& scope, const SingleImplConfig& config); void rename_possible_collisions(const DexType* intf, const SingleImplData& data); check_casts::impl::Stats post_process( const std::unordered_set<DexMethod*>& methods, std::vector<IRInstruction*>* removed_instruction); private: std::unique_ptr<SingleImplAnalysis> single_impls; // A map from interface method to implementing method. We maintain this global // map for rewriting method references in annotation. NewMethods m_intf_meth_to_impl_meth; // list of optimized types std::unordered_set<DexType*> optimized; const ClassHierarchy& ch; std::unordered_map<std::string, size_t> deobfuscated_name_counters; }; /** * Rewrite fields by creating new ones and transferring values from the * old fields to the new ones. Remove old field and add the new one * to the list of fields. */ void OptimizationImpl::set_field_defs(const DexType* intf, const SingleImplData& data) { for (const auto& field : data.fielddefs) { redex_assert(!single_impls->is_escaped(field->get_class())); auto f = static_cast<DexField*>( DexField::make_field(field->get_class(), field->get_name(), data.cls)); redex_assert(f != field); TRACE(INTF, 3, "(FDEF) %s", SHOW(field)); f->set_deobfuscated_name(field->get_deobfuscated_name()); f->rstate = field->rstate; auto field_anno = field->release_annotations(); if (field_anno) { f->attach_annotation_set(std::move(field_anno)); } f->make_concrete(field->get_access(), field->get_static_value() == nullptr ? std::unique_ptr<DexEncodedValue>() : field->get_static_value()->clone()); auto cls = type_class(field->get_class()); cls->remove_field(field); cls->add_field(f); TRACE(INTF, 3, "(FDEF)\t=> %s", SHOW(f)); } } /** * Rewrite all fieldref. */ void OptimizationImpl::set_field_refs(const DexType* intf, const SingleImplData& data) { for (const auto& fieldrefs : data.fieldrefs) { const auto field = fieldrefs.first; redex_assert(!single_impls->is_escaped(field->get_class())); DexFieldRef* f = DexField::make_field(field->get_class(), field->get_name(), data.cls); for (const auto opcode : fieldrefs.second) { TRACE(INTF, 3, "(FREF) %s", SHOW(opcode)); redex_assert(f != opcode->get_field()); opcode->set_field(f); TRACE(INTF, 3, "(FREF) \t=> %s", SHOW(opcode)); } } } /** * Change all the method definitions by updating specs. * We will never get collision here since we renamed potential colliding methods * before doing the optimization. */ void OptimizationImpl::set_method_defs(const DexType* intf, const SingleImplData& data) { for (auto method : data.methoddefs) { TRACE(INTF, 3, "(MDEF) %s", SHOW(method)); TRACE(INTF, 5, "(MDEF) Update method: %s", SHOW(method)); bool res = update_method_proto(intf, data.cls, method); always_assert(res); TRACE(INTF, 3, "(MDEF)\t=> %s", SHOW(method)); } } template <typename T, typename Fn> void for_all_methods(const T& methods, Fn fn, bool parallel = true) { if (parallel) { workqueue_run<const DexMethod*>(fn, methods); } else { for (auto* m : methods) { fn(const_cast<DexMethod*>(m)); } } } // When replacing interfaces with classes, type-correct bytecode may // become incorrect. That is due to the relaxed nature of interface // assignability: at the bytecode level, any reference can be assigned // to an interface-typed entity. Actual checks happen at an eventual // `invoke-interface`. // // Example: // void foo(ISub i) {} // void bar(ISuper i) { // foo(i); // Java source needs cast here. // } // // This method inserts check-casts for each invoke parameter and // field value. Expectation is that unnecessary insertions (e.g., // duplicate check-casts) will be eliminated, for example, in // `post_process`. CheckCastSet OptimizationImpl::fix_instructions(const DexType* intf, const SingleImplData& data) { if (data.referencing_methods.empty()) { return {}; } std::vector<const DexMethod*> methods; methods.reserve(data.referencing_methods.size()); std::transform(data.referencing_methods.begin(), data.referencing_methods.end(), std::back_inserter(methods), [](auto& p) { return p.first; }); // The typical number of methods is too small, it is actually significant // overhead to spin up pool threads to just let them die. constexpr bool PARALLEL = false; std::mutex ret_lock; CheckCastSet ret; for_all_methods( methods, [&](const DexMethod* caller_const) { auto caller = const_cast<DexMethod*>(caller_const); std::vector<reg_t> temps; // Cached temps. auto code = caller->get_code(); redex_assert(!code->editable_cfg_built()); for (const auto& insn_it_pair : data.referencing_methods.at(caller)) { auto insn_it = insn_it_pair.second; auto insn = insn_it_pair.first; auto temp_it = temps.begin(); auto add_check_cast = [&](reg_t reg) { auto check_cast = new IRInstruction(OPCODE_CHECK_CAST); check_cast->set_src(0, reg); check_cast->set_type(data.cls); code->insert_before(insn_it, *new MethodItemEntry(check_cast)); if (PARALLEL) { std::unique_lock<std::mutex> lock(ret_lock); ret.insert(check_cast); } else { ret.insert(check_cast); } // See if we need a new temp. reg_t out; if (temp_it == temps.end()) { reg_t new_temp = code->allocate_temp(); temps.push_back(new_temp); temp_it = temps.end(); out = new_temp; } else { out = *temp_it; temp_it++; } auto pseudo_move_result = new IRInstruction(IOPCODE_MOVE_RESULT_PSEUDO_OBJECT); pseudo_move_result->set_dest(out); code->insert_before(insn_it, *new MethodItemEntry(pseudo_move_result)); return out; }; if (opcode::is_an_invoke(insn->opcode())) { // We need check-casts for receiver and parameters, but not // return type. auto mref = insn->get_method(); // Receiver. if (mref->get_class() == intf) { reg_t new_receiver = add_check_cast(insn->src(0)); insn->set_src(0, new_receiver); } // Parameters. const auto* arg_list = mref->get_proto()->get_args(); size_t idx = insn->opcode() == OPCODE_INVOKE_STATIC ? 0 : 1; for (const auto arg : *arg_list) { if (arg != intf) { idx++; continue; } reg_t new_param = add_check_cast(insn->src(idx)); insn->set_src(idx, new_param); idx++; } continue; } if (opcode::is_an_iput(insn->opcode()) || opcode::is_an_sput(insn->opcode())) { // If the field type is the interface, need a check-cast. auto fdef = insn->get_field(); if (fdef->get_type() == intf) { reg_t new_param = add_check_cast(insn->src(0)); insn->set_src(0, new_param); } continue; } // Others do not need fixup. } }, PARALLEL); return ret; } /** * Rewrite all method refs. */ void OptimizationImpl::set_method_refs(const DexType* intf, const SingleImplData& data) { for (const auto& mrefit : data.methodrefs) { auto method = mrefit.first; TRACE(INTF, 3, "(MREF) update ref %s", SHOW(method)); // next 2 lines will generate no new proto or method when the ref matches // a def, which should be very common. // However it does not seem too much of an overkill and more // straightforward that any logic that manages that. // Both creation of protos or methods is "interned" and so the same // method would be returned if there is nothing to change and create. // Still we need to change the opcodes where we assert the new method // to go in the opcode is in fact different than what was there. if (update_method_proto(intf, data.cls, method)) { TRACE(INTF, 3, "(MREF)\t=> %s", SHOW(method)); } } } /** * Move all methods of the interface to the concrete (if not there already) * and rewrite all refs that were calling to the interface * (invoke-interface* -> invoke-virtual*). */ void OptimizationImpl::rewrite_interface_methods(const DexType* intf, const SingleImplData& data) { auto intf_cls = type_class(intf); auto impl = type_class(data.cls); for (auto meth : intf_cls->get_vmethods()) { // Given an interface method and a class determine whether the method // is already defined in the class and use it if so. // An interface method can be defined in some base class for "convenience" // even though the base class does not implement the interface so we walk // the chain looking for the method. // NOTICE: if we have interfaces that have methods defined up the chain // in some java, android, google or other library we are screwed. // We'll not find the method and introduce a possible abstract one that // will break things. // Hopefully we'll find that out during verification and correct things. // get the new method if one was created (interface method with a single // impl in signature) TRACE(INTF, 3, "(MITF) interface method %s", SHOW(meth)); auto new_meth = resolve_virtual(impl, meth->get_name(), meth->get_proto()); if (!new_meth) { new_meth = static_cast<DexMethod*>(DexMethod::make_method( impl->get_type(), meth->get_name(), meth->get_proto())); // new_meth may not be new, because RedexContext keeps methods around // after they are deleted. clear all pre-existing method state. // TODO: this is horrible. After we remove methods, we shouldn't // have these zombies lying around. new_meth->clear_annotations(); new_meth->make_non_concrete(); auto deoob_impl_name = impl->get_deobfuscated_name_or_empty(); auto unique = deobfuscated_name_counters[deoob_impl_name]++; auto new_deob_name = deoob_impl_name + "." + meth->get_simple_deobfuscated_name() + "$REDEX_SINGLE_IMPL$" + std::to_string(unique) + ":" + show_deobfuscated(meth->get_proto()); new_meth->set_deobfuscated_name(new_deob_name); new_meth->rstate = meth->rstate; TRACE(INTF, 5, "(MITF) created impl method %s", SHOW(new_meth)); setup_method(meth, new_meth); redex_assert(new_meth->is_virtual()); impl->add_method(new_meth); TRACE(INTF, 3, "(MITF) moved interface method %s", SHOW(new_meth)); } else { TRACE(INTF, 3, "(MITF) found method impl %s", SHOW(new_meth)); } always_assert(!m_intf_meth_to_impl_meth.count(meth)); m_intf_meth_to_impl_meth[meth] = new_meth; } // rewrite invoke-interface to invoke-virtual for (const auto& mref_it : data.intf_methodrefs) { auto m = mref_it.first; always_assert(m_intf_meth_to_impl_meth.count(m)); auto new_m = m_intf_meth_to_impl_meth[m]; redex_assert(new_m && new_m != m); TRACE(INTF, 3, "(MITFOP) %s", SHOW(new_m)); for (auto mop : mref_it.second) { TRACE(INTF, 3, "(MITFOP) %s", SHOW(mop)); mop->set_method(new_m); always_assert(mop->opcode() == OPCODE_INVOKE_INTERFACE); mop->set_opcode(OPCODE_INVOKE_VIRTUAL); SingleImplPass::s_invoke_intf_count++; TRACE(INTF, 3, "(MITFOP)\t=>%s", SHOW(mop)); } } } /** * Rewrite annotations that are referring to update methods or deleted * interfaces. */ void OptimizationImpl::rewrite_annotations(Scope& scope, const SingleImplConfig& config) { // TODO: this is a hack to fix a problem with enclosing methods only. // There are more dalvik annotations to review. // The infrastructure is here but the code for all cases not yet auto enclosingMethod = DexType::get_type("Ldalvik/annotation/EnclosingMethod;"); if (enclosingMethod == nullptr) return; // nothing to do if (!must_set_method_annotations(config)) return; for (const auto& cls : scope) { auto anno_set = cls->get_anno_set(); if (anno_set == nullptr) continue; for (auto& anno : anno_set->get_annotations()) { if (anno->type() != enclosingMethod) continue; const auto& elems = anno->anno_elems(); for (auto& elem : elems) { auto& value = elem.encoded_value; if (value->evtype() == DexEncodedValueTypes::DEVT_METHOD) { auto method_value = static_cast<DexEncodedValueMethod*>(value.get()); const auto& meth_it = m_intf_meth_to_impl_meth.find(method_value->method()); if (meth_it == m_intf_meth_to_impl_meth.end()) { if (method_value->method()->is_def()) { continue; } // All the method definitions with optimized interfaces are updated, // this is a pure ref, we are not sure if it's updated properly. TRACE(INTF, 2, "[SingleImpl]: Found pure methodref %s in annotation of " "class %s, this may not be properly supported.\n", SHOW(method_value->method()), SHOW(cls)); continue; } TRACE(INTF, 4, "REWRITE: %s", SHOW(anno)); method_value->set_method(meth_it->second); TRACE(INTF, 4, "TO: %s", SHOW(anno)); } } } } } /** * Check collisions in field definition. */ EscapeReason OptimizationImpl::check_field_collision( const DexType* intf, const SingleImplData& data) { for (const auto field : data.fielddefs) { redex_assert(!single_impls->is_escaped(field->get_class())); auto collision = resolve_field(field->get_class(), field->get_name(), data.cls); if (collision) return FIELD_COLLISION; } return NO_ESCAPE; } /** * Check collisions in method definition. */ EscapeReason OptimizationImpl::check_method_collision( const DexType* intf, const SingleImplData& data) { for (auto method : data.methoddefs) { auto proto = get_or_make_proto(intf, data.cls, method->get_proto()); redex_assert(proto != method->get_proto()); DexMethodRef* collision = DexMethod::get_method(method->get_class(), method->get_name(), proto); if (!collision) { collision = find_collision(ch, method->get_name(), proto, type_class(method->get_class()), method->is_virtual()); } if (collision) { TRACE(INTF, 9, "Found collision %s", SHOW(method)); TRACE(INTF, 9, "\t to %s", SHOW(collision)); return SIG_COLLISION; } } return NO_ESCAPE; } /** * Move all single impl in a single impl method signature to next pass. * We make a single optimization per pass over any given single impl so * I1, I2 and void I1.m(I2) * the first optimization (I1 or I2) moves the other interface to next pass. * That is not the case for methods on non optimizable classes, so for * I1, I2 and void C.m(I1, I2) * then m is changed in a single pass for both I1 and I2. */ void OptimizationImpl::drop_single_impl_collision(const DexType* intf, const SingleImplData& data, DexMethod* method) { auto check_type = [&](DexType* type) { if (type != intf && single_impls->is_single_impl(type) && !single_impls->is_escaped(type)) { single_impls->escape_interface(type, NEXT_PASS); always_assert(!optimized.count(type)); } }; auto owner = method->get_class(); if (!single_impls->is_single_impl(owner)) return; check_type(owner); auto proto = method->get_proto(); check_type(proto->get_rtype()); auto args_list = proto->get_args(); for (auto arg : *args_list) { check_type(arg); } } /** * A single impl can be optimized if: * 1- there is no collision in fields rewrite * 2- there is no collision in methods rewrite */ EscapeReason OptimizationImpl::can_optimize(const DexType* intf, const SingleImplData& data, bool rename_on_collision) { auto escape = check_field_collision(intf, data); if (escape != EscapeReason::NO_ESCAPE) return escape; escape = check_method_collision(intf, data); if (escape != EscapeReason::NO_ESCAPE) { if (rename_on_collision) { rename_possible_collisions(intf, data); escape = check_method_collision(intf, data); } if (escape != EscapeReason::NO_ESCAPE) return escape; } for (auto method : data.methoddefs) { drop_single_impl_collision(intf, data, method); } auto intf_cls = type_class(intf); for (auto method : intf_cls->get_vmethods()) { drop_single_impl_collision(intf, data, method); } return NO_ESCAPE; } /** * Remove any chance for collisions. */ void OptimizationImpl::rename_possible_collisions(const DexType* intf, const SingleImplData& data) { const auto& rename = [](DexMethodRef* meth, const DexString* name) { DexMethodSpec spec; spec.cls = meth->get_class(); spec.name = name; spec.proto = meth->get_proto(); meth->change(spec, false /* rename on collision */); }; TRACE(INTF, 9, "Changing name related to %s", SHOW(intf)); for (const auto& meth : data.methoddefs) { if (!can_rename(meth)) { TRACE(INTF, 9, "Changing name but cannot rename %s, give up", SHOW(meth)); return; } } for (const auto& meth : data.methoddefs) { if (method::is_constructor(meth)) continue; auto name = type_reference::new_name(meth); TRACE(INTF, 9, "Changing def name for %s to %s", SHOW(meth), SHOW(name)); rename(meth, name); } for (const auto& refs_it : data.methodrefs) { if (refs_it.first->is_def()) continue; always_assert(!method::is_init(refs_it.first)); auto name = type_reference::new_name(refs_it.first); TRACE(INTF, 9, "Changing ref name for %s to %s", SHOW(refs_it.first), SHOW(name)); rename(refs_it.first, name); } } /** * Perform the optimization. */ CheckCastSet OptimizationImpl::do_optimize(const DexType* intf, const SingleImplData& data) { CheckCastSet ret = fix_instructions(intf, data); set_type_refs(intf, data); set_field_defs(intf, data); set_field_refs(intf, data); set_method_defs(intf, data); set_method_refs(intf, data); rewrite_interface_methods(intf, data); remove_interface(intf, data); return ret; } /** * Run an optimization step. */ OptimizeStats OptimizationImpl::optimize(Scope& scope, const SingleImplConfig& config) { TypeList to_optimize; single_impls->get_interfaces(to_optimize); std::sort(to_optimize.begin(), to_optimize.end(), compare_dextypes); std::unordered_set<DexMethod*> for_post_processing; CheckCastSet inserted_check_casts; for (auto intf : to_optimize) { auto& intf_data = single_impls->get_single_impl_data(intf); if (intf_data.is_escaped()) continue; TRACE(INTF, 3, "(OPT) %s => %s", SHOW(intf), SHOW(intf_data.cls)); auto escape = can_optimize(intf, intf_data, config.rename_on_collision); if (escape != EscapeReason::NO_ESCAPE) { single_impls->escape_interface(intf, escape); continue; } auto check_casts = do_optimize(intf, intf_data); inserted_check_casts.insert(check_casts.begin(), check_casts.end()); for (auto& p : intf_data.referencing_methods) { for_post_processing.insert(p.first); } optimized.insert(intf); } // make a new scope deleting all single impl interfaces Scope new_scope; for (auto cls : scope) { if (optimized.find(cls->get_type()) != optimized.end()) continue; new_scope.push_back(cls); } scope.swap(new_scope); if (must_rewrite_annotations(config)) { rewrite_annotations(scope, config); } std::vector<IRInstruction*> removed_instructions; auto post_process_stats = post_process(for_post_processing, &removed_instructions); std::atomic<size_t> retained{0}; { for_all_methods(for_post_processing, [&](const DexMethod* m) { auto code = m->get_code(); size_t found = 0; for (const auto& mie : ir_list::ConstInstructionIterable(*code)) { if (inserted_check_casts.count(mie.insn) != 0) { ++found; } } retained += found; }); } for (auto* insn : removed_instructions) { delete insn; } OptimizeStats ret; ret.removed_interfaces = optimized.size(); ret.inserted_check_casts = inserted_check_casts.size(); ret.retained_check_casts = retained.load(); ret.post_process = post_process_stats; ret.deleted_removed_instructions = removed_instructions.size(); return ret; } check_casts::impl::Stats OptimizationImpl::post_process( const std::unordered_set<DexMethod*>& methods, std::vector<IRInstruction*>* removed_instructions) { // The analysis times the number of methods is easily expensive, run in // parallel. check_casts::impl::Stats stats; std::mutex mutex; for_all_methods( methods, [&stats, &mutex, removed_instructions](const DexMethod* m_const) { auto m = const_cast<DexMethod*>(m_const); auto code = m->get_code(); always_assert(!code->editable_cfg_built()); cfg::ScopedCFG cfg(code); check_casts::CheckCastConfig config; check_casts::impl::CheckCastAnalysis analysis(config, m); auto casts = analysis.collect_redundant_checks_replacement(); auto local_stats = check_casts::impl::apply(m, casts); std::lock_guard<std::mutex> lock_guard(mutex); stats += local_stats; auto insns = cfg->release_removed_instructions(); removed_instructions->insert(removed_instructions->end(), insns.begin(), insns.end()); }, /*parallel=*/true); return stats; } } // namespace /** * Entry point for an optimization pass. */ OptimizeStats optimize(std::unique_ptr<SingleImplAnalysis> analysis, const ClassHierarchy& ch, Scope& scope, const SingleImplConfig& config) { OptimizationImpl optimizer(std::move(analysis), ch); return optimizer.optimize(scope, config); }