From 278d31f84c7119997f9da7cbe878f66f930dea69 Mon Sep 17 00:00:00 2001 From: William Sanville Date: Mon, 23 Sep 2024 16:34:29 -0700 Subject: [PATCH] Find types whose init methods appear to have been inlined in relaxed fashion Summary: The input program may have had this type of trick applied already (by d8, r8). Trudge through the code to figure out places where it could have happened, be it Redex or the input app. As described in the comments, a new instance's type on which `Ljava/lang/Object;.:()V` is invoked is not included in the result set of `find_complex_init_inlined_types()`, since I am trying to minimize the amount of places in which Redex optimizations will have to be more cautious and I have not seen in practice this type of invocation on an unresolved type causing problems (just non-Object super class invocation). Used by subsequent diff. Reviewed By: thezhangwei Differential Revision: D62672721 fbshipit-source-id: 92fa32d6ff052142aaac2acdc744b801169d25af --- .../method-inliner/ConstructorAnalysis.cpp | 43 +++++++++++++ service/method-inliner/ConstructorAnalysis.h | 10 +++ test/unit/ConstructorAnalysisTest.cpp | 62 ++++++++++++++++++- 3 files changed, 113 insertions(+), 2 deletions(-) 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()); +}