diff --git a/service/method-inliner/ConstructorAnalysis.cpp b/service/method-inliner/ConstructorAnalysis.cpp index d2f09e4680..3d7e8813ea 100644 --- a/service/method-inliner/ConstructorAnalysis.cpp +++ b/service/method-inliner/ConstructorAnalysis.cpp @@ -13,14 +13,18 @@ #include #include "BaseIRAnalyzer.h" +#include "ConcurrentContainers.h" #include "ControlFlow.h" #include "DexClass.h" #include "IRCode.h" #include "IRInstruction.h" +#include "Lazy.h" +#include "LiveRange.h" #include "MethodUtil.h" #include "ReachingDefinitions.h" #include "Resolver.h" #include "Show.h" +#include "Walkers.h" using namespace sparta; @@ -331,4 +335,43 @@ bool can_inline_inits_in_same_class(DexMethod* caller_method, } } +std::unordered_set find_complex_init_inlined_types( + const std::vector& scope) { + InsertOnlyConcurrentSet items; + // Calling this on an unknown type is apparently OK for verification. + auto object_init = DexMethod::get_method("Ljava/lang/Object;.:()V"); + walk::parallel::methods(scope, [&](DexMethod* method) { + auto code = method->get_code(); + if (code == nullptr) { + return; + } + always_assert(code->editable_cfg_built()); + auto& cfg = code->cfg(); + Lazy live_ranges( + [&]() { return std::make_unique(cfg); }); + for (auto* block : cfg.blocks()) { + for (auto& mie : InstructionIterable(block)) { + auto insn = mie.insn; + if (insn->opcode() == OPCODE_NEW_INSTANCE) { + auto new_instance_type = insn->get_type(); + auto search = live_ranges->def_use_chains->find(insn); + if (search != live_ranges->def_use_chains->end()) { + for (auto& use : search->second) { + if (use.insn->has_method()) { + auto use_method = use.insn->get_method(); + if (use.src_index == 0 && method::is_init(use_method) && + use_method != object_init && + use_method->get_class() != new_instance_type) { + items.insert(new_instance_type); + } + } + } + } + } + } + } + }); + std::unordered_set result(items.begin(), items.end()); + return result; +} } // namespace constructor_analysis diff --git a/service/method-inliner/ConstructorAnalysis.h b/service/method-inliner/ConstructorAnalysis.h index 8340ac00c0..744a830a4c 100644 --- a/service/method-inliner/ConstructorAnalysis.h +++ b/service/method-inliner/ConstructorAnalysis.h @@ -8,9 +8,12 @@ #pragma once #include +#include class DexField; class DexMethod; +class DexType; +class DexClass; class IRInstruction; namespace constructor_analysis { @@ -35,4 +38,11 @@ bool can_inline_inits_in_same_class(DexMethod* caller_method, const DexMethod* callee_method, IRInstruction* callsite_insn); +// Iterates the scope to find any types that have apparently been optimized by +// relaxed init inlining (either by Redex or the input dex of an application). +// Complex is defined such that the called method on a new-instance is +// defined on some other type in the hierarchy, aside from java.lang.Object's +// default constructor. +std::unordered_set find_complex_init_inlined_types( + const std::vector& scope); } // namespace constructor_analysis diff --git a/test/unit/ConstructorAnalysisTest.cpp b/test/unit/ConstructorAnalysisTest.cpp index eb836fed39..406b9da90d 100644 --- a/test/unit/ConstructorAnalysisTest.cpp +++ b/test/unit/ConstructorAnalysisTest.cpp @@ -16,15 +16,20 @@ #include "IRAssembler.h" #include "IRCode.h" #include "RedexTest.h" +#include "Show.h" struct ConstructorAnalysisTest : public RedexTest {}; -DexClass* create_a_class(const char* description) { +DexClass* create_a_class(const char* description, DexType* super) { ClassCreator cc(DexType::make_type(description)); - cc.set_super(type::java_lang_Object()); + cc.set_super(super); return cc.create(); } +DexClass* create_a_class(const char* description) { + return create_a_class(description, type::java_lang_Object()); +} + /** * Create a method like * void (object, ..(num_param_types many).., object) { @@ -159,3 +164,56 @@ TEST_F(ConstructorAnalysisTest, can_inline_init_supertype_relaxed) { EXPECT_FALSE(constructor_analysis::can_inline_init( foo_init1, /* finalizable_fields */ nullptr, /* relaxed */ true)); } + +TEST_F(ConstructorAnalysisTest, can_detect_relaxed_inlined_init) { + // Set up a couple of classes, and usages of them (some of which will look + // like its constructor was inlined). + auto foo_cls = create_a_class("Lfoo;"); + create_an_init_method( + foo_cls, DexMethod::make_method("Ljava/lang/Object;.:()V"), 0, {}); + + auto bar_cls = create_a_class("Lbar;"); + auto bar_init = create_an_init_method( + bar_cls, DexMethod::make_method("Ljava/lang/Object;.:()V"), 0, {}); + + auto baz_cls = create_a_class("Lbaz;", bar_cls->get_type()); + create_an_init_method(baz_cls, bar_init, 0, {}); + + auto use_cls = assembler::class_from_string(R"( + (class (public) "Luse;" + (method (public static) "Luse;.a:(I)V" + ( + ; not complex + (new-instance "Lfoo;") + (move-result-pseudo-object v1) + (invoke-direct (v1) "Ljava/lang/Object;.:()V") + + ; totally normal + (new-instance "Lbar;") + (move-result-pseudo-object v2) + (invoke-direct (v2) "Lbar;.:()V") + + ; complex + (new-instance "Lbaz;") + (move-result-pseudo-object v3) + (invoke-direct (v3) "Lbar;.:()V") + (return-void) + ) + ) + ) + )"); + + Scope scope{foo_cls, bar_cls, baz_cls, use_cls}; + for (auto cls : scope) { + for (auto m : cls->get_all_methods()) { + auto code = m->get_code(); + if (code != nullptr) { + code->build_cfg(); + } + } + } + auto result = constructor_analysis::find_complex_init_inlined_types(scope); + EXPECT_EQ(result.size(), 1); + EXPECT_EQ(*result.begin(), baz_cls->get_type()) + << "GOT " << show(*result.begin()); +}