Skip to content

Commit

Permalink
Find types whose init methods appear to have been inlined in relaxed …
Browse files Browse the repository at this point in the history
…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;.<init>:()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
  • Loading branch information
wsanville authored and facebook-github-bot committed Sep 23, 2024
1 parent cdd31d5 commit 278d31f
Show file tree
Hide file tree
Showing 3 changed files with 113 additions and 2 deletions.
43 changes: 43 additions & 0 deletions service/method-inliner/ConstructorAnalysis.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,18 @@
#include <sparta/ReducedProductAbstractDomain.h>

#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;

Expand Down Expand Up @@ -331,4 +335,43 @@ bool can_inline_inits_in_same_class(DexMethod* caller_method,
}
}

std::unordered_set<const DexType*> find_complex_init_inlined_types(
const std::vector<DexClass*>& scope) {
InsertOnlyConcurrentSet<const DexType*> items;
// Calling this on an unknown type is apparently OK for verification.
auto object_init = DexMethod::get_method("Ljava/lang/Object;.<init>:()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_range::LazyLiveRanges> live_ranges(
[&]() { return std::make_unique<live_range::LazyLiveRanges>(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<const DexType*> result(items.begin(), items.end());
return result;
}
} // namespace constructor_analysis
10 changes: 10 additions & 0 deletions service/method-inliner/ConstructorAnalysis.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@
#pragma once

#include <unordered_set>
#include <vector>

class DexField;
class DexMethod;
class DexType;
class DexClass;
class IRInstruction;

namespace constructor_analysis {
Expand All @@ -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 <init> 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<const DexType*> find_complex_init_inlined_types(
const std::vector<DexClass*>& scope);
} // namespace constructor_analysis
62 changes: 60 additions & 2 deletions test/unit/ConstructorAnalysisTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 <init>(object, ..(num_param_types many).., object) {
Expand Down Expand Up @@ -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;.<init>:()V"), 0, {});

auto bar_cls = create_a_class("Lbar;");
auto bar_init = create_an_init_method(
bar_cls, DexMethod::make_method("Ljava/lang/Object;.<init>:()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;.<init>:()V")
; totally normal
(new-instance "Lbar;")
(move-result-pseudo-object v2)
(invoke-direct (v2) "Lbar;.<init>:()V")
; complex
(new-instance "Lbaz;")
(move-result-pseudo-object v3)
(invoke-direct (v3) "Lbar;.<init>:()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());
}

0 comments on commit 278d31f

Please sign in to comment.