Skip to content

Commit

Permalink
GTA: analyze_s[get|put]
Browse files Browse the repository at this point in the history
Summary:
Analyzing sgets/sputs used to be supported by GTA in Redex but was removed.

Reasons for original removal include:
* Not adding much value (to redex).
* Class initialization order may affect soundness.

These downsides are less applicable to Mariana Trench so we are bringing this back.

____

Reviewed By: anwesht

Differential Revision: D68180664

fbshipit-source-id: 0dc3f444358ffffe83222966811ec63262c8d750
  • Loading branch information
Yuh Shin Ong authored and facebook-github-bot committed Jan 17, 2025
1 parent 773c720 commit 41a9702
Show file tree
Hide file tree
Showing 5 changed files with 72 additions and 10 deletions.
16 changes: 12 additions & 4 deletions source/tests/integration/type-analysis/GlobalTypeAnalysisTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -191,8 +191,11 @@ TEST_F(GlobalTypeAnalysisTest, ClinitFieldAnalyzerTest) {
auto field_sbase =
get_field("TestH;.BASE:Lcom/facebook/redextest/TestH$Base;");
auto ftype = wps.get_field_type(field_sbase);
EXPECT_TRUE(ftype.is_top());
EXPECT_FALSE(ftype.is_top());
EXPECT_TRUE(ftype.is_nullable());
EXPECT_EQ(ftype.get_single_domain(),
SingletonDexTypeDomain(get_type("TestH$Base")));
EXPECT_EQ(ftype.get_set_domain(), get_small_set_domain({"TestH$Base"}));

auto field_mbase =
get_field("TestH;.mBase:Lcom/facebook/redextest/TestH$Base;");
Expand All @@ -215,8 +218,11 @@ TEST_F(GlobalTypeAnalysisTest, ClinitFieldAnalyzerTest) {
auto meth_baz =
get_method("TestH;.baz", "", "Lcom/facebook/redextest/TestH$Base;");
rtype = wps.get_return_type(meth_baz);
EXPECT_TRUE(rtype.is_top());
EXPECT_FALSE(rtype.is_top());
EXPECT_TRUE(rtype.is_nullable());
EXPECT_EQ(rtype.get_single_domain(),
SingletonDexTypeDomain(get_type("TestH$Base")));
EXPECT_EQ(rtype.get_set_domain(), get_small_set_domain({"TestH$Base"}));
}

TEST_F(GlobalTypeAnalysisTest, IFieldsNullnessTest) {
Expand Down Expand Up @@ -453,8 +459,10 @@ TEST_F(GlobalTypeAnalysisTest, StaticFieldTypes) {
auto rtype = wps.get_return_type(meth);
EXPECT_TRUE(rtype.is_nullable());
const auto& single_domain = rtype.get_single_domain();
EXPECT_TRUE(single_domain.is_top());
EXPECT_EQ(single_domain, SingletonDexTypeDomain(get_type("TestQ$Base")));
const auto& set_domain = rtype.get_set_domain();
EXPECT_TRUE(set_domain.is_top());
EXPECT_EQ(
set_domain.get_types(),
get_type_set({get_type("TestQ$Derived1"), get_type("TestQ$Derived2")}));
}
} // namespace marianatrench
10 changes: 7 additions & 3 deletions source/tests/type-analysis/GlobalTypeAnalysisTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -363,12 +363,16 @@ TEST_F(GlobalTypeAnalysisTest, ClinitSimpleTest) {
auto analysis = GlobalTypeAnalysis::make_default();
auto gta = analysis.analyze(scope, *options);
auto wps = gta->get_whole_program_state();
EXPECT_TRUE(wps.get_field_type(field_1).is_top());
EXPECT_TRUE(wps.get_return_type(meth_bar).is_top());
EXPECT_EQ(wps.get_field_type(field_1),
get_type_domain("LO;").join(DexTypeDomain::null()));
EXPECT_EQ(wps.get_return_type(meth_bar),
get_type_domain("LO;").join(DexTypeDomain::null()));

auto lta = gta->get_replayable_local_analysis(meth_foo);
auto code = meth_foo->get_code();
auto foo_exit_env = lta->get_exit_state_at(code->cfg().exit_block());
EXPECT_TRUE(foo_exit_env.get_reg_environment().get(1).is_top());
EXPECT_EQ(foo_exit_env.get_reg_environment().get(1),
get_type_domain("LO;").join(DexTypeDomain::null()));
}

TEST_F(GlobalTypeAnalysisTest, StaticFieldWithEncodedValueTest) {
Expand Down
11 changes: 8 additions & 3 deletions source/type-analysis/GlobalTypeAnalyzer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,8 @@ bool GlobalTypeAnalyzer::is_reachable(const DexMethod* method) const {
}

using CombinedAnalyzer =
InstructionAnalyzerCombiner<WholeProgramAwareAnalyzer,
InstructionAnalyzerCombiner<local::ClinitFieldAnalyzer,
WholeProgramAwareAnalyzer,
local::CtorFieldAnalyzer,
local::RegisterTypeAnalyzer>;

Expand All @@ -292,8 +293,11 @@ std::unique_ptr<local::LocalTypeAnalyzer> GlobalTypeAnalyzer::analyze_method(
}

auto env = env_with_params(&code, args);
DexType* clinit_type{nullptr};
DexType* ctor_type{nullptr};
if (method::is_init(method)) {
if (method::is_clinit(method)) {
clinit_type = method->get_class();
} else if (method::is_init(method)) {
ctor_type = method->get_class();
}
TRACE(TYPE, 5, "%s", SHOW(code.cfg()));
Expand All @@ -302,7 +306,8 @@ std::unique_ptr<local::LocalTypeAnalyzer> GlobalTypeAnalyzer::analyze_method(
? std::make_unique<local::LocalTypeAnalyzer>(
code.cfg(), CombinedReplayAnalyzer(&wps, nullptr))
: std::make_unique<local::LocalTypeAnalyzer>(
code.cfg(), CombinedAnalyzer(&wps, ctor_type, nullptr));
code.cfg(),
CombinedAnalyzer(clinit_type, &wps, ctor_type, nullptr));
local_ta->run(env);

return local_ta;
Expand Down
12 changes: 12 additions & 0 deletions source/type-analysis/LocalTypeAnalyzer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,18 @@ bool field_put_helper(const DexType* class_under_init,

} // namespace

bool ClinitFieldAnalyzer::analyze_sget(const DexType* class_under_init,
const IRInstruction* insn,
DexTypeEnvironment* env) {
return field_get_helper(class_under_init, insn, env);
}

bool ClinitFieldAnalyzer::analyze_sput(const DexType* class_under_init,
const IRInstruction* insn,
DexTypeEnvironment* env) {
return field_put_helper(class_under_init, insn, env);
}

bool CtorFieldAnalyzer::analyze_default(const DexType* class_under_init,
const IRInstruction* insn,
DexTypeEnvironment* env) {
Expand Down
33 changes: 33 additions & 0 deletions source/type-analysis/LocalTypeAnalyzer.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,39 @@ class RegisterTypeAnalyzer final
DexTypeEnvironment* env);
};

/*
* Unlike in other methods where we always propagate field type info from
* the WholeProgramState, in <clinit>s, we directly propagate static field type
* info through the local FieldTypeEnvironment. This is similar to what we do
* for constant values in IPCP.
*
* The reason is that the <clinit> is the 1st method of the class being executed
* after class loading. Therefore, the field 'writes' in the <clinit> happens
* before other 'writes' to the same field. In other words, the field type state
* of <clinit>s are self-contained. Note that we are limiting ourselves to
* static fields belonging to the same class here.
*
* We don't throw away our results if there're invoke-statics in the <clinit>.
* Since the field 'write' in the invoke-static callee will be aggregated in the
* final type mapping in WholeProgramState. Before that happens, we do not
* propagate incomplete field type info to other methods. As stated above, we
* only propagate field type info from WholeProgramState computed in the
* previous global iteration.
*/
class ClinitFieldAnalyzer final
: public InstructionAnalyzerBase<ClinitFieldAnalyzer,
DexTypeEnvironment,
DexType* /* class_under_init */> {
public:
static bool analyze_sget(const DexType* class_under_init,
const IRInstruction* insn,
DexTypeEnvironment* env);

static bool analyze_sput(const DexType* class_under_init,
const IRInstruction* insn,
DexTypeEnvironment* env);
};

/*
* Similarly CtorFieldAnalyzer populates local FieldTypeEnvironment when
* analyzing a ctor. We only do so for instance fields that belong to the class
Expand Down

0 comments on commit 41a9702

Please sign in to comment.