opt/synth/Synth.cpp (883 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 "Synth.h" #include <memory> #include <signal.h> #include <stdio.h> #include <string> #include <tuple> #include <unordered_map> #include <unordered_set> #include <utility> #include <vector> #include "ApiLevelChecker.h" #include "ClassHierarchy.h" #include "Debug.h" #include "DexClass.h" #include "DexLoader.h" #include "DexOutput.h" #include "DexStore.h" #include "DexUtil.h" #include "IRCode.h" #include "IRInstruction.h" #include "Mutators.h" #include "PassManager.h" #include "ReachableClasses.h" #include "RefChecker.h" #include "Resolver.h" #include "Show.h" #include "SynthConfig.h" #include "Walkers.h" constexpr const char* METRIC_GETTERS_REMOVED = "getter_methods_removed_count"; constexpr const char* METRIC_WRAPPERS_REMOVED = "wrapper_methods_removed_count"; constexpr const char* METRIC_CTORS_REMOVED = "constructors_removed_count"; constexpr const char* METRIC_PASSES = "passes_count"; constexpr const char* METRIC_METHODS_STATICIZED = "methods_staticized_count"; constexpr const char* METRIC_PATCHED_INVOKES = "patched_invokes_count"; constexpr const char* METRIC_ILLEGAL_REFS = "illegal_refs"; namespace { struct SynthMetrics { size_t getters_removed_count{0}; size_t wrappers_removed_count{0}; size_t ctors_removed_count{0}; size_t methods_staticized_count{0}; size_t patched_invokes_count{0}; }; } // anonymous namespace bool is_static_synthetic(DexMethod* meth) { return is_static(meth) && is_synthetic(meth); } bool can_optimize(DexMethod* meth, const SynthConfig& synthConfig) { return !synthConfig.synth_only || is_static_synthetic(meth); } bool can_remove(DexMethod* meth, const SynthConfig& synthConfig) { return synthConfig.remove_pub || !is_public(meth); } /* * Matches the pattern: * iget-TYPE vB, FIELD * move-result-pseudo-object vA * return-TYPE vA */ DexField* trivial_get_field_wrapper(DexMethod* m) { auto code = m->get_code(); if (code == nullptr) return nullptr; auto ii = InstructionIterable(code); auto it = ii.begin(); auto end = ii.end(); while (it != end && opcode::is_a_load_param(it->insn->opcode())) { ++it; } if (!opcode::is_an_iget(it->insn->opcode())) return nullptr; auto iget = it->insn; reg_t iget_dest = ir_list::move_result_pseudo_of(it.unwrap())->dest(); std::advance(it, 2); if (!opcode::is_a_return_value(it->insn->opcode())) return nullptr; reg_t ret_reg = it->insn->src(0); if (ret_reg != iget_dest) return nullptr; ++it; if (it != end) return nullptr; // Check to make sure we have a concrete field reference. auto def = resolve_field(iget->get_field(), FieldSearch::Instance); if (def == nullptr) return nullptr; if (!def->is_concrete()) { return nullptr; } return def; } /* * Matches the pattern: * sget-TYPE FIELD * move-result-pseudo-object vA * return-TYPE vA */ DexField* trivial_get_static_field_wrapper(DexMethod* m) { auto code = m->get_code(); if (code == nullptr) return nullptr; auto ii = InstructionIterable(code); auto it = ii.begin(); auto end = ii.end(); while (it != end && opcode::is_a_load_param(it->insn->opcode())) { ++it; } if (!opcode::is_an_sget(it->insn->opcode())) return nullptr; auto sget = it->insn; reg_t sget_dest = ir_list::move_result_pseudo_of(it.unwrap())->dest(); std::advance(it, 2); if (!opcode::is_a_return_value(it->insn->opcode())) return nullptr; reg_t ret_reg = it->insn->src(0); if (ret_reg != sget_dest) return nullptr; ++it; if (it != end) return nullptr; // Check to make sure we have a concrete field reference. auto def = resolve_field(sget->get_field(), FieldSearch::Static); if (def == nullptr) return nullptr; if (!def->is_concrete()) { return nullptr; } return def; } /* * Matches the pattern: * invoke-(direct|static) {vA, ..., vB} METHOD * ( move-result-TYPE v0 * return-TYPE v0 * | return-void ) */ DexMethod* trivial_method_wrapper(DexMethod* m, const ClassHierarchy& ch) { auto code = m->get_code(); if (code == nullptr) return nullptr; auto ii = InstructionIterable(code); auto it = ii.begin(); auto end = ii.end(); while (it != end && opcode::is_a_load_param(it->insn->opcode())) { ++it; } bool is_direct = it->insn->opcode() == OPCODE_INVOKE_DIRECT; bool is_static = it->insn->opcode() == OPCODE_INVOKE_STATIC; if (!is_direct && !is_static) return nullptr; auto invoke = it->insn; auto method = invoke->get_method(); if (is_static) { method = resolve_static(type_class(method->get_class()), method->get_name(), method->get_proto()); } if (!method) return nullptr; if (!method->is_concrete()) return nullptr; const auto method_def = static_cast<DexMethod*>(method); auto collision = find_collision_excepting(ch, method_def, method_def->get_name(), method_def->get_proto(), type_class(method_def->get_class()), true, true); if (collision) { TRACE(SYNT, 5, "wrapper blocked:%s\nwrapped method:%s\nconflicts with:%s", SHOW(m), SHOW(method_def), SHOW(collision)); return nullptr; } if (!passes_args_through(invoke, *code)) return nullptr; ++it; if (it == end) return nullptr; if (opcode::is_a_move_result(it->insn->opcode())) { ++it; if (it == end) return nullptr; if (!opcode::is_a_return_value(it->insn->opcode())) return nullptr; ++it; if (it != end) return nullptr; // exception handling code } else if (it->insn->opcode() == OPCODE_RETURN_VOID) { ++it; if (it != end) return nullptr; // exception handling code } else { return nullptr; } // The wrapper method may have a trivial exception handler. if (code->has_try_blocks()) return nullptr; return method_def; } /* * Matches the pattern: * invoke-direct {v0...} Lclass;.<init> * return-void */ DexMethod* trivial_ctor_wrapper(DexMethod* m) { auto code = m->get_code(); if (code == nullptr) return nullptr; auto ii = InstructionIterable(code); auto it = ii.begin(); auto end = ii.end(); while (it != end && opcode::is_a_load_param(it->insn->opcode())) { ++it; } if (it->insn->opcode() != OPCODE_INVOKE_DIRECT) { TRACE(SYNT, 5, "Rejecting, not direct: %s", SHOW(m)); return nullptr; } auto invoke = it->insn; if (!passes_args_through(invoke, *code, 1)) { TRACE(SYNT, 5, "Rejecting, not passthrough: %s", SHOW(m)); return nullptr; } ++it; if (it == end) return nullptr; if (it->insn->opcode() != OPCODE_RETURN_VOID) return nullptr; auto method = invoke->get_method(); if (!method->is_concrete() || !method::is_constructor(static_cast<DexMethod*>(method))) { return nullptr; } return static_cast<DexMethod*>(method); } struct WrapperMethods { ConcurrentMap<DexMethod*, DexField*> getters; ConcurrentMap<DexMethod*, DexMethod*> wrappers; ConcurrentMap<DexMethod*, DexMethod*> ctors; ConcurrentMap<DexMethod*, std::pair<DexMethod*, int>> wrapped; ConcurrentSet<DexMethod*> keepers; std::unordered_set<DexMethod*> promoted_to_static; bool next_pass = false; }; /** * Find and remove wrappers to wrappers. This removes loops and chain of * wrappers leaving only one level (and the first level) of wrappers */ void purge_wrapped_wrappers(WrapperMethods& ssms) { std::vector<DexMethod*> remove; for (auto& p : ssms.wrappers) { if (ssms.wrappers.count_unsafe(p.second)) { remove.emplace_back(p.second); } if (ssms.getters.count_unsafe(p.second)) { // a getter is a leaf so we remove it and we'll likely pick // it up next pass TRACE(SYNT, 5, "Removing wrapped getter: %s", SHOW(p.second)); ssms.getters.erase(p.second); ssms.next_pass = true; } } for (auto meth : remove) { auto wrapper = ssms.wrappers.find(meth); if (wrapper == ssms.wrappers.end()) { // Might have been a duplidate we already erased continue; } auto wrapped = ssms.wrapped.find(wrapper->second); if (wrapped != ssms.wrapped.end()) { if (--wrapped->second.second == 0) { TRACE(SYNT, 5, "Removing wrapped: %s", SHOW(wrapper->second)); ssms.wrapped.erase(wrapper->second); } } TRACE(SYNT, 5, "Removing wrapper: %s", SHOW(meth)); ssms.wrappers.erase(meth); } ssms.next_pass = ssms.next_pass || !remove.empty(); } WrapperMethods analyze(const api::AndroidSDK* min_sdk_api, XStoreRefs& xstores, const ClassHierarchy& ch, const std::vector<DexClass*>& classes, const SynthConfig& synthConfig) { Timer timer("analyze"); WrapperMethods ssms; walk::parallel::classes(classes, [&](DexClass* cls) { if (synthConfig.blocklist_types.count(cls->get_type())) { return; } for (auto dmethod : cls->get_dmethods()) { if (dmethod->rstate.dont_inline()) continue; // constructors are special and all we can remove are synthetic ones if (synthConfig.remove_constructors && is_synthetic(dmethod) && method::is_constructor(dmethod)) { auto ctor = trivial_ctor_wrapper(dmethod); if (ctor) { TRACE(SYNT, 2, "Trivial constructor wrapper: %s", SHOW(dmethod)); TRACE(SYNT, 2, " Calls constructor: %s", SHOW(ctor)); ssms.ctors.emplace(dmethod, ctor); } continue; } if (method::is_constructor(dmethod)) continue; if (is_static_synthetic(dmethod)) { auto field = trivial_get_field_wrapper(dmethod); if (field) { TRACE(SYNT, 2, "Static trivial getter: %s", SHOW(dmethod)); TRACE(SYNT, 2, " Gets field: %s", SHOW(field)); ssms.getters.emplace(dmethod, field); continue; } auto sfield = trivial_get_static_field_wrapper(dmethod); if (sfield) { TRACE(SYNT, 2, "Static trivial static field getter: %s", SHOW(dmethod)); TRACE(SYNT, 2, " Gets static field: %s", SHOW(sfield)); ssms.getters.emplace(dmethod, sfield); continue; } } if (can_optimize(dmethod, synthConfig)) { auto method = trivial_method_wrapper(dmethod, ch); if (method) { // this is not strictly needed but to avoid changing visibility of // virtuals we are skipping a wrapper to a virtual. // Incidentally we have no single method falling in that bucket // at this time if (method->is_virtual()) continue; TRACE(SYNT, 2, "Static trivial method wrapper: %s", SHOW(dmethod)); TRACE(SYNT, 2, " Calls method: %s", SHOW(method)); ssms.wrappers.emplace(dmethod, method); if (!is_static(method)) { ssms.wrapped.update( method, [&](DexMethod*, std::pair<DexMethod*, int>& p, bool exists) { if (!exists) { p = std::make_pair(dmethod, 1); } else { p.second++; } }); } } } } if (debug) { // Static synthetics should never be virtual. for (auto vmethod : cls->get_vmethods()) { (void)vmethod; redex_assert(!is_static_synthetic(vmethod)); } } }); purge_wrapped_wrappers(ssms); return ssms; } IRInstruction* make_iget(DexField* field, reg_t src) { auto const opcode = [&]() { switch (type::to_datatype(field->get_type())) { case DataType::Array: case DataType::Object: return OPCODE_IGET_OBJECT; case DataType::Boolean: return OPCODE_IGET_BOOLEAN; case DataType::Byte: return OPCODE_IGET_BYTE; case DataType::Char: return OPCODE_IGET_CHAR; case DataType::Short: return OPCODE_IGET_SHORT; case DataType::Int: case DataType::Float: return OPCODE_IGET; case DataType::Long: case DataType::Double: return OPCODE_IGET_WIDE; case DataType::Void: not_reached(); } not_reached(); }(); return (new IRInstruction(opcode))->set_field(field)->set_src(0, src); } IRInstruction* make_sget(DexField* field) { auto const opcode = opcode::sget_opcode_for_field(field); return (new IRInstruction(opcode))->set_field(field); } void replace_getter_wrapper_sequential(IRInstruction* insn, DexField* field) { TRACE(SYNT, 2, "Optimizing getter wrapper call (sequential): %s", SHOW(insn)); redex_assert(field->is_concrete()); set_public(field); always_assert(is_public(field)); } void replace_getter_wrapper_concurrent(IRCode* transform, IRInstruction* insn, IRInstruction* move_result, DexField* field) { TRACE(SYNT, 2, "Optimizing getter wrapper call (concurrent): %s", SHOW(insn)); redex_assert(field->is_concrete()); always_assert(is_public(field)); auto new_get = is_static(field) ? make_sget(field) : make_iget(field, insn->src(0)); TRACE(SYNT, 2, "Created instruction: %s", SHOW(new_get)); auto move_result_pseudo = (new IRInstruction(opcode::move_result_to_pseudo(move_result->opcode()))) ->set_dest(move_result->dest()); transform->replace_opcode(insn, {new_get, move_result_pseudo}); transform->remove_opcode(move_result); } void replace_method_wrapper_concurrent(IRCode* transform, IRInstruction* insn, DexMethod* method) { TRACE(SYNT, 2, "Optimizing method wrapper (sequential): %s", SHOW(insn)); auto op = insn->opcode(); auto new_invoke = [&] { redex_assert(op == OPCODE_INVOKE_STATIC || op == OPCODE_INVOKE_DIRECT); auto new_op = is_static(method) ? OPCODE_INVOKE_STATIC : OPCODE_INVOKE_DIRECT; auto ret = new IRInstruction(new_op); ret->set_method(method)->set_srcs_size(insn->srcs_size()); for (size_t i = 0; i < ret->srcs_size(); i++) { ret->set_src(i, insn->src(i)); } return ret; }(); TRACE(SYNT, 2, "new instruction: %s", SHOW(new_invoke)); transform->replace_opcode(insn, new_invoke); } bool can_update_wrappee(const ClassHierarchy& ch, DexMethod* wrappee, DexMethod* wrapper) { if (is_native(wrappee) || !can_rename(wrappee)) { // Can't change the signature of native methods, as well as // unrenameable ones. return false; } DexProto* old_proto = wrappee->get_proto(); auto new_args = old_proto->get_args()->push_front(wrappee->get_class()); DexProto* new_proto = DexProto::make_proto(old_proto->get_rtype(), new_args); auto new_name = wrappee->get_name(); auto new_class = type_class(wrappee->get_class()); if (find_collision(ch, new_name, new_proto, new_class, false)) { if (find_collision_excepting(ch, wrapper, new_name, new_proto, new_class, false /* is_virtual */, true /* check_direct */)) { return false; } return can_delete(wrapper); } return true; } void replace_method_wrapper_sequential(const ClassHierarchy& ch, IRInstruction* insn, DexMethod* wrapper, DexMethod* wrappee, WrapperMethods& ssms) { TRACE(SYNT, 2, "Optimizing method wrapper (sequential): %s", SHOW(insn)); TRACE(SYNT, 3, " wrapper:%p wrappee:%p", wrapper, wrappee); TRACE(SYNT, 3, " wrapper: %s", SHOW(wrapper)); TRACE(SYNT, 3, " wrappee: %s", SHOW(wrappee)); redex_assert(wrappee->is_concrete() && wrapper->is_concrete()); if (is_static(wrapper) && !is_static(wrappee)) { assert(can_update_wrappee(ch, wrappee, wrapper)); mutators::make_static(wrappee); ssms.promoted_to_static.insert(wrappee); } if (!is_private(wrapper)) { set_public(wrappee); if (wrapper->get_class() != wrappee->get_class()) { set_public(type_class(wrappee->get_class())); } } } void replace_ctor_wrapper_sequential(IRInstruction* ctor_insn, DexMethod* ctor) { TRACE(SYNT, 2, "Optimizing static ctor (sequential): %s", SHOW(ctor_insn)); redex_assert(ctor->is_concrete()); set_public(ctor); always_assert(is_public(ctor)); } void replace_ctor_wrapper_concurrent(IRCode* transform, IRInstruction* ctor_insn, DexMethod* ctor) { TRACE(SYNT, 2, "Optimizing static ctor (concurrent): %s", SHOW(ctor_insn)); redex_assert(ctor->is_concrete()); always_assert(is_public(ctor)); auto op = ctor_insn->opcode(); auto new_ctor_call = [&] { redex_assert(op == OPCODE_INVOKE_DIRECT); auto ret = new IRInstruction(OPCODE_INVOKE_DIRECT); ret->set_method(ctor)->set_srcs_size(ctor_insn->srcs_size() - 1); for (size_t i = 0; i < ret->srcs_size(); i++) { ret->set_src(i, ctor_insn->src(i)); } return ret; }(); TRACE(SYNT, 2, "new instruction: %s", SHOW(new_ctor_call)); transform->replace_opcode(ctor_insn, new_ctor_call); } struct MethodAnalysisResult { std::vector<std::tuple<IRInstruction*, IRInstruction*, DexField*>> getter_calls; std::vector<std::tuple<IRInstruction*, DexMethod*, DexMethod*>> wrapper_calls; std::vector<std::tuple<IRInstruction*, DexMethod*, DexMethod*>> wrapped_calls; std::vector<std::pair<IRInstruction*, DexMethod*>> ctor_calls; }; std::unique_ptr<MethodAnalysisResult> analyze_method_concurrent( RefChecker& ref_checker, DexMethod* caller_method, WrapperMethods& ssms, std::atomic<size_t>& illegal_refs) { auto mar = std::make_unique<MethodAnalysisResult>(); TRACE(SYNT, 4, "Analyzing %s", SHOW(caller_method)); auto ii = InstructionIterable(caller_method->get_code()); int32_t caller_api_level = api::LevelChecker::get_method_level(caller_method); auto check_callee = [&](DexMethod* callee) { int32_t callee_api_level = api::LevelChecker::get_method_level(callee); if (callee_api_level != api::LevelChecker::get_min_level() && callee_api_level > caller_api_level) { return false; } if (ref_checker.check_method(callee)) { return true; } illegal_refs++; return false; }; auto check_field = [&](DexField* field) { if (ref_checker.check_field(field)) { return true; } illegal_refs++; return false; }; for (auto it = ii.begin(); it != ii.end(); ++it) { auto insn = it->insn; if (insn->opcode() == OPCODE_INVOKE_STATIC) { // Replace calls to static getters and wrappers auto const callee = resolve_method(insn->get_method(), MethodSearch::Static); if (callee == nullptr) continue; auto const found_get = ssms.getters.find(callee); if (found_get != ssms.getters.end()) { auto next_it = std::next(it); auto const move_result = next_it->insn; auto field = found_get->second; if (!opcode::is_a_move_result(move_result->opcode()) || !check_field(field)) { ssms.keepers.emplace(callee); continue; } mar->getter_calls.emplace_back(insn, move_result, field); continue; } auto const found_wrap = ssms.wrappers.find(callee); if (found_wrap != ssms.wrappers.end()) { auto method = found_wrap->second; if (check_callee(method)) { mar->wrapper_calls.emplace_back(insn, callee, method); } else { ssms.keepers.emplace(callee); ssms.keepers.emplace(method); } continue; } always_assert_log(ssms.wrapped.find(callee) == ssms.wrapped.end(), "caller: %s\ncallee: %s\ninsn: %s\n", SHOW(caller_method), SHOW(callee), SHOW(insn)); ssms.keepers.emplace(callee); } else if (insn->opcode() == OPCODE_INVOKE_DIRECT) { auto const callee = resolve_method(insn->get_method(), MethodSearch::Direct); if (callee == nullptr) continue; auto const found_get = ssms.getters.find(callee); if (found_get != ssms.getters.end()) { auto next_it = std::next(it); auto const move_result = next_it->insn; auto field = found_get->second; if (!opcode::is_a_move_result(move_result->opcode()) || !check_field(field)) { ssms.keepers.emplace(callee); continue; } mar->getter_calls.emplace_back(insn, move_result, field); continue; } auto const found_wrap = ssms.wrappers.find(callee); if (found_wrap != ssms.wrappers.end()) { auto method = found_wrap->second; if (check_callee(method)) { mar->wrapper_calls.emplace_back(insn, callee, method); } else { ssms.keepers.emplace(callee); ssms.keepers.emplace(method); } continue; } auto const found_wrappee = ssms.wrapped.find(callee); if (found_wrappee != ssms.wrapped.end()) { auto wrapper = found_wrappee->second.first; if (check_callee(wrapper)) { mar->wrapped_calls.emplace_back(insn, callee, wrapper); } else { ssms.keepers.emplace(callee); ssms.keepers.emplace(wrapper); } continue; } auto const found_ctor = ssms.ctors.find(callee); if (found_ctor != ssms.ctors.end()) { auto ctor = found_ctor->second; if (check_callee(ctor)) { mar->ctor_calls.emplace_back(insn, ctor); } else { ssms.keepers.emplace(callee); ssms.keepers.emplace(ctor); } continue; } } } return mar; } void replace_wrappers_sequential(const ClassHierarchy& ch, DexMethod* caller_method, WrapperMethods& ssms, MethodAnalysisResult* mar) { using std::get; TRACE(SYNT, 4, "Replacing wrappers (sequential) in %s", SHOW(caller_method)); // Prune out wrappers that are invalid due to naming conflicts. std::unordered_set<DexMethod*> bad_wrappees; std::unordered_multimap<DexMethod*, DexMethod*> wrappees_to_wrappers; for (const auto& wtriple : mar->wrapper_calls) { auto call_inst = get<0>(wtriple); auto wrapper = static_cast<DexMethod*>(call_inst->get_method()); auto wrappee = get<2>(wtriple); wrappees_to_wrappers.emplace(wrappee, wrapper); if (!can_update_wrappee(ch, wrappee, wrapper)) { bad_wrappees.emplace(wrappee); } } for (const auto& wtriple : mar->wrapped_calls) { auto call_inst = get<0>(wtriple); auto wrapper = get<2>(wtriple); auto wrappee = static_cast<DexMethod*>(call_inst->get_method()); wrappees_to_wrappers.emplace(wrappee, wrapper); if (!can_update_wrappee(ch, wrappee, wrapper)) { bad_wrappees.emplace(wrappee); } } for (auto bw : bad_wrappees) { auto range = wrappees_to_wrappers.equal_range(bw); for (auto it = range.first; it != range.second; ++it) { ssms.keepers.emplace(it->second); } } mar->wrapper_calls.erase( std::remove_if( mar->wrapper_calls.begin(), mar->wrapper_calls.end(), [&](const std::tuple<IRInstruction*, DexMethod*, DexMethod*>& wtriple) { return bad_wrappees.count(get<2>(wtriple)); }), mar->wrapper_calls.end()); mar->wrapped_calls.erase( std::remove_if(mar->wrapped_calls.begin(), mar->wrapped_calls.end(), [&](const std::tuple<IRInstruction*, DexMethod*, DexMethod*>& wtriple) { auto call_inst = get<0>(wtriple); return bad_wrappees.count( static_cast<DexMethod*>(call_inst->get_method())); }), mar->wrapped_calls.end()); // Fix up everything left. for (const auto& g : mar->getter_calls) { replace_getter_wrapper_sequential(get<0>(g), get<2>(g)); } for (const auto& wtriple : mar->wrapper_calls) { auto call_inst = get<0>(wtriple); auto wrapper = get<1>(wtriple); auto wrappee = get<2>(wtriple); replace_method_wrapper_sequential(ch, call_inst, wrapper, wrappee, ssms); } for (const auto& wtriple : mar->wrapped_calls) { auto call_inst = get<0>(wtriple); auto wrappee = get<1>(wtriple); auto wrapper = get<2>(wtriple); replace_method_wrapper_sequential(ch, call_inst, wrapper, wrappee, ssms); } for (const auto& cpair : mar->ctor_calls) { replace_ctor_wrapper_sequential(cpair.first, cpair.second); } } void replace_wrappers_concurrent(DexMethod* caller_method, const MethodAnalysisResult* mar) { using std::get; auto code = caller_method->get_code(); for (const auto& g : mar->getter_calls) { replace_getter_wrapper_concurrent(code, get<0>(g), get<1>(g), get<2>(g)); } for (const auto& wtriple : mar->wrapper_calls) { auto call_inst = get<0>(wtriple); auto wrappee = get<2>(wtriple); replace_method_wrapper_concurrent(code, call_inst, wrappee); } for (const auto& wtriple : mar->wrapped_calls) { auto call_inst = get<0>(wtriple); auto wrappee = get<1>(wtriple); replace_method_wrapper_concurrent(code, call_inst, wrappee); } for (const auto& cpair : mar->ctor_calls) { replace_ctor_wrapper_concurrent(code, cpair.first, cpair.second); } } void remove_dead_methods(WrapperMethods& ssms, const SynthConfig& synthConfig, SynthMetrics& metrics) { bool any_remove = false; size_t synth_removed = 0; size_t other_removed = 0; size_t pub_meth = 0; std::unordered_map<DexClass*, std::unordered_set<DexMethod*>> methods_to_remove_by_class; auto remove_meth = [&](DexMethod* meth) { redex_assert(meth->is_concrete()); if (!can_remove(meth, synthConfig)) { return; } if (ssms.keepers.count(meth)) { TRACE(SYNT, 2, "Retaining method: %s", SHOW(meth)); return; } if (!can_delete(meth)) { TRACE(SYNT, 2, "Do not strip: %s", SHOW(meth)); return; } TRACE(SYNT, 2, "Removing method: %s", SHOW(meth)); if (is_public(meth)) pub_meth++; auto cls = type_class(meth->get_class()); methods_to_remove_by_class[cls].insert(meth); is_synthetic(meth) ? synth_removed++ : other_removed++; }; for (auto const& gp : ssms.getters) { remove_meth(gp.first); } any_remove = any_remove || (synth_removed && other_removed); TRACE(SYNT, 3, "any_remove = %d", any_remove); TRACE(SYNT, 3, "synth_removed = %zu", synth_removed); TRACE(SYNT, 3, "other_removed = %zu", other_removed); if (synth_removed) { TRACE(SYNT, 1, "Synthetic getters removed %ld", synth_removed); } if (other_removed) { TRACE(SYNT, 1, "Other getters removed %ld", other_removed); } if (pub_meth) { TRACE(SYNT, 1, "Public getters removed %ld", pub_meth); } metrics.getters_removed_count += synth_removed + other_removed; synth_removed = 0; other_removed = 0; pub_meth = 0; for (auto const& wp : ssms.wrappers) { remove_meth(wp.first); } any_remove = any_remove || (synth_removed && other_removed); if (synth_removed) { TRACE(SYNT, 1, "Synthetic wrappers removed %ld", synth_removed); } if (other_removed) { TRACE(SYNT, 1, "Other wrappers removed %ld", other_removed); } if (pub_meth) { TRACE(SYNT, 1, "Public wrappers removed %ld", pub_meth); } metrics.wrappers_removed_count += synth_removed + other_removed; synth_removed = 0; other_removed = 0; pub_meth = 0; for (auto const& ct : ssms.ctors) { remove_meth(ct.first); } any_remove = any_remove || (synth_removed && other_removed); if (synth_removed) { TRACE(SYNT, 1, "Synthetic constructor removed %ld", synth_removed); } if (pub_meth) { TRACE(SYNT, 1, "Public constructor removed %ld", pub_meth); } metrics.ctors_removed_count += synth_removed; redex_assert(other_removed == 0); ssms.next_pass = ssms.next_pass && any_remove; std::vector<DexClass*> classes; classes.reserve(methods_to_remove_by_class.size()); for (auto& p : methods_to_remove_by_class) { classes.push_back(p.first); } walk::parallel::classes(classes, [&](DexClass* clazz) { for (auto m : methods_to_remove_by_class.at(clazz)) { clazz->remove_method(m); } }); } void do_transform(const api::AndroidSDK* min_sdk_api, XStoreRefs& xstores, const ClassHierarchy& ch, const std::vector<DexClass*>& classes, WrapperMethods& ssms, const SynthConfig& synthConfig, SynthMetrics& metrics, std::atomic<size_t>& illegal_refs) { Timer timer("do_transform"); // remove wrappers. build a vector ahead of time to ensure we only visit each // method once, even if we mutate the class method lists such that we'd hit // something a second time. std::vector<DexMethod*> methods; std::unordered_map<DexMethod*, std::unique_ptr<MethodAnalysisResult>> method_analysis_results; walk::code(classes, [&](DexMethod* meth, IRCode&) { methods.emplace_back(meth); method_analysis_results.emplace(meth, std::unique_ptr<MethodAnalysisResult>()); }); std::vector<std::unique_ptr<RefChecker>> ref_checkers; ref_checkers.reserve(xstores.size()); for (size_t store_idx = 0; store_idx < xstores.size(); store_idx++) { ref_checkers.emplace_back( std::make_unique<RefChecker>(&xstores, store_idx, min_sdk_api)); } // Analyze methods in parallel (no mutation) walk::parallel::code(classes, [&](DexMethod* meth, IRCode&) { auto store_idx = xstores.get_store_idx(meth->get_class()); auto& ref_checker = ref_checkers.at(store_idx); method_analysis_results.at(meth) = analyze_method_concurrent(*ref_checker, meth, ssms, illegal_refs); }); // Mutate method signatures (sequentially, as they are subtle dependencies) for (auto const& meth : methods) { auto* mar = method_analysis_results.at(meth).get(); replace_wrappers_sequential(ch, meth, ssms, mar); } // Mutate method bodies (concurrently), and check that invokes to promoted // static method are correct std::atomic<size_t> patched_invokes{0}; walk::parallel::code(classes, [&](DexMethod* meth, IRCode& code) { auto* mar = method_analysis_results.at(meth).get(); replace_wrappers_concurrent(meth, mar); for (auto& mie : InstructionIterable(code)) { auto* insn = mie.insn; auto opcode = insn->opcode(); if (opcode != OPCODE_INVOKE_DIRECT) { continue; } auto wrappee = resolve_method(insn->get_method(), MethodSearch::Direct); if (wrappee == nullptr || ssms.promoted_to_static.count(wrappee) == 0) { continue; } // change the opcode to invoke-static insn->set_opcode(OPCODE_INVOKE_STATIC); TRACE(SYNT, 3, "Updated invoke on promoted to static %s\n in method %s", SHOW(wrappee), SHOW(meth)); patched_invokes++; } }); remove_dead_methods(ssms, synthConfig, metrics); metrics.methods_staticized_count += ssms.promoted_to_static.size(); metrics.patched_invokes_count += (size_t)patched_invokes; } bool trace_analysis(WrapperMethods& ssms) { DEBUG_ONLY size_t synth = 0; DEBUG_ONLY size_t others = 0; for (auto it : ssms.getters) { auto meth = it.first; is_synthetic(meth) ? synth++ : others++; } TRACE(SYNT, 3, "synth getters %ld", synth); TRACE(SYNT, 3, "other getters %ld", others); synth = 0; others = 0; for (auto it : ssms.ctors) { auto meth = it.first; if (is_synthetic(meth)) { synth++; } else { others++; } } TRACE(SYNT, 3, "synth ctors %ld", synth); TRACE(SYNT, 3, "other ctors %ld", others); synth = 0; others = 0; for (auto it : ssms.wrappers) { auto meth = it.first; is_synthetic(meth) ? synth++ : others++; } TRACE(SYNT, 3, "synth methods %ld", synth); TRACE(SYNT, 3, "other methods %ld", others); return true; } bool optimize(const api::AndroidSDK* min_sdk_api, XStoreRefs& xstores, const ClassHierarchy& ch, const std::vector<DexClass*>& classes, const SynthConfig& synthConfig, SynthMetrics& metrics, std::atomic<size_t>& illegal_refs) { auto ssms = analyze(min_sdk_api, xstores, ch, classes, synthConfig); redex_assert(trace_analysis(ssms)); do_transform(min_sdk_api, xstores, ch, classes, ssms, synthConfig, metrics, illegal_refs); return ssms.next_pass; } void SynthPass::run_pass(DexStoresVector& stores, ConfigFiles& conf, PassManager& mgr) { if (mgr.no_proguard_rules()) { TRACE(SYNT, 1, "SynthPass not run because no ProGuard configuration was provided."); return; } int32_t min_sdk = mgr.get_redex_options().min_sdk; mgr.incr_metric("min_sdk", min_sdk); TRACE(SYNT, 2, "min_sdk: %d", min_sdk); auto min_sdk_api_file = conf.get_android_sdk_api_file(min_sdk); const api::AndroidSDK* min_sdk_api{nullptr}; if (!min_sdk_api_file) { mgr.incr_metric("min_sdk_no_file", 1); TRACE(SYNT, 2, "Android SDK API %d file cannot be found.", min_sdk); } else { min_sdk_api = &conf.get_android_sdk_api(min_sdk); } XStoreRefs xstores(stores); Scope scope = build_class_scope(stores); ClassHierarchy ch = build_type_hierarchy(scope); SynthMetrics metrics; int passes = 0; std::atomic<size_t> illegal_refs{0}; do { TRACE(SYNT, 1, "Synth removal, pass %d", passes); bool more_opt_needed = optimize(min_sdk_api, xstores, ch, scope, m_pass_config, metrics, illegal_refs); if (!more_opt_needed) break; } while (++passes < m_pass_config.max_passes); mgr.incr_metric(METRIC_GETTERS_REMOVED, metrics.getters_removed_count); mgr.incr_metric(METRIC_WRAPPERS_REMOVED, metrics.wrappers_removed_count); mgr.incr_metric(METRIC_CTORS_REMOVED, metrics.ctors_removed_count); mgr.incr_metric(METRIC_METHODS_STATICIZED, metrics.methods_staticized_count); mgr.incr_metric(METRIC_PATCHED_INVOKES, metrics.patched_invokes_count); mgr.incr_metric(METRIC_ILLEGAL_REFS, illegal_refs); mgr.incr_metric(METRIC_PASSES, passes); } static SynthPass s_pass;