Skip to content

Commit

Permalink
Add GlobalTypeAnalyzer::get_replayable_local_analysis() for local ana…
Browse files Browse the repository at this point in the history
…lysis replay

Summary:
# Context:

The `LocalTypeAnalyzer` by default includes `ClinitFieldAnalyzer` and `CtorFieldAnalyzer` that operate on a field type environment to model the heap when collecting type info for sfields and ifields. This design serves the WholeProgramState aggregation really well to not fall back to the previous global state too quickly. In the end, we aggregate all the field writes in the WholeProgramState, so everything is good.

However, after running the global type analysis, when replaying individual local analysis, for each individual field read, we should go straight to the global state. We should in this case, ignore the partial local info, and always go to the global one for completeness.

# This Change:

In this change, we split up the `LocalTypeAnalyzer` into two. The existing one operating on local field type environment remains the same, and serves WholeProgramState collection in the same way. We add a new one for replay purpose only after the global type analysis. The new replayable local analysis does not feed local field info into the register type environment. It always rely on the WholeProgramState for that.

Reviewed By: NTillmann

Differential Revision: D51539668

fbshipit-source-id: f765f0b912b286fe604baef95fd6417c5d4799c9
  • Loading branch information
Wei Zhang (Devinfra) authored and facebook-github-bot committed Nov 28, 2023
1 parent 9a3865e commit 134979d
Show file tree
Hide file tree
Showing 11 changed files with 65 additions and 51 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ struct TypeAnalysisAwareClosureMarkerSharedState final
auto* code = const_cast<IRCode*>(method->get_code());
always_assert(code);
always_assert(code->editable_cfg_built());
auto lta = gta->get_local_analysis(method);
auto lta = gta->get_replayable_local_analysis(method);
MethodRefCache resolved_refs;
for (const auto& block : code->cfg().blocks()) {
auto env = lta->get_entry_state_at(block);
Expand Down
2 changes: 1 addition & 1 deletion opt/type-analysis/GlobalTypeAnalysisPass.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ void GlobalTypeAnalysisPass::optimize(
if (method->get_code() == nullptr) {
return Stats();
}
auto lta = gta.get_local_analysis(method);
auto lta = gta.get_replayable_local_analysis(method);

if (m_config.trace_global_local_diff) {
trace_analysis_diff(method, *lta);
Expand Down
2 changes: 1 addition & 1 deletion opt/type-analysis/TypeAnalysisCallGraphGenerationPass.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ class TypeAnalysisBasedStrategy : public MultipleCalleeBaseStrategy {
if (code == nullptr) {
return callsites;
}
auto lta = m_gta->get_local_analysis(method);
auto lta = m_gta->get_replayable_local_analysis(method);
for (const auto& block : code->cfg().blocks()) {
auto env = lta->get_entry_state_at(block);
if (env.is_bottom()) {
Expand Down
35 changes: 28 additions & 7 deletions service/type-analysis/GlobalTypeAnalyzer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ void GlobalTypeAnalyzer::analyze_node(
return;
}
auto& cfg = code->cfg();
auto intra_ta = get_local_analysis(method);
auto intra_ta = get_internal_local_analysis(method);
const auto outgoing_edges =
call_graph::GraphInterface::successors(*m_call_graph, node);
std::unordered_set<IRInstruction*> outgoing_insns;
Expand Down Expand Up @@ -193,17 +193,28 @@ ArgumentTypePartition GlobalTypeAnalyzer::analyze_edge(
}

std::unique_ptr<local::LocalTypeAnalyzer>
GlobalTypeAnalyzer::get_local_analysis(const DexMethod* method) const {
GlobalTypeAnalyzer::get_internal_local_analysis(const DexMethod* method) const {
auto args = ArgumentTypePartition::bottom();

if (m_call_graph->has_node(method)) {
args = this->get_entry_state_at(m_call_graph->node(method));
}
return analyze_method(method,
this->get_whole_program_state(),
return analyze_method(method, this->get_whole_program_state(),
args.get(CURRENT_PARTITION_LABEL));
}

std::unique_ptr<local::LocalTypeAnalyzer>
GlobalTypeAnalyzer::get_replayable_local_analysis(
const DexMethod* method) const {
auto args = ArgumentTypePartition::bottom();

if (m_call_graph->has_node(method)) {
args = this->get_entry_state_at(m_call_graph->node(method));
}
return analyze_method(method, this->get_whole_program_state(),
args.get(CURRENT_PARTITION_LABEL), true);
}

bool GlobalTypeAnalyzer::is_reachable(const DexMethod* method) const {
auto args = ArgumentTypePartition::bottom();

Expand All @@ -220,10 +231,15 @@ using CombinedAnalyzer =
local::CtorFieldAnalyzer,
local::RegisterTypeAnalyzer>;

using CombinedReplayAnalyzer =
InstructionAnalyzerCombiner<WholeProgramAwareAnalyzer,
local::RegisterTypeAnalyzer>;

std::unique_ptr<local::LocalTypeAnalyzer> GlobalTypeAnalyzer::analyze_method(
const DexMethod* method,
const WholeProgramState& wps,
ArgumentTypeEnvironment args) const {
ArgumentTypeEnvironment args,
const bool is_replayable) const {
TRACE(TYPE, 5, "[global] analyzing %s", SHOW(method));
always_assert(method->get_code() != nullptr);
auto& code = *method->get_code();
Expand All @@ -244,8 +260,13 @@ std::unique_ptr<local::LocalTypeAnalyzer> GlobalTypeAnalyzer::analyze_method(
ctor_type = method->get_class();
}
TRACE(TYPE, 5, "%s", SHOW(code.cfg()));
auto local_ta = std::make_unique<local::LocalTypeAnalyzer>(
code.cfg(), CombinedAnalyzer(clinit_type, &wps, ctor_type, nullptr));
auto local_ta =
is_replayable
? std::make_unique<local::LocalTypeAnalyzer>(
code.cfg(), CombinedReplayAnalyzer(&wps, nullptr))
: std::make_unique<local::LocalTypeAnalyzer>(
code.cfg(),
CombinedAnalyzer(clinit_type, &wps, ctor_type, nullptr));
local_ta->run(env);

return local_ta;
Expand Down
17 changes: 15 additions & 2 deletions service/type-analysis/GlobalTypeAnalyzer.h
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ class GlobalTypeAnalyzer : public sparta::ParallelMonotonicFixpointIterator<
* Run local analysis for the given method and return the LocalAnalyzer with
* the end state.
*/
std::unique_ptr<local::LocalTypeAnalyzer> get_local_analysis(
std::unique_ptr<local::LocalTypeAnalyzer> get_replayable_local_analysis(
const DexMethod*) const;

const WholeProgramState& get_whole_program_state() const { return *m_wps; }
Expand All @@ -103,10 +103,23 @@ class GlobalTypeAnalyzer : public sparta::ParallelMonotonicFixpointIterator<
std::unique_ptr<const WholeProgramState> m_wps;
std::shared_ptr<const call_graph::Graph> m_call_graph;

/*
* An unsafe variant that runs the local analysis on the given method, and
* returns the LocalAnalyzer with the end state. This is only used for
* collecting global states. This is not meant to be used to replay analysis
* after the global type analysis. It doesn't always fall back to the
* WholeProgramState.
*/
std::unique_ptr<local::LocalTypeAnalyzer> get_internal_local_analysis(
const DexMethod*) const;

std::unique_ptr<local::LocalTypeAnalyzer> analyze_method(
const DexMethod* method,
const WholeProgramState& wps,
ArgumentTypeEnvironment args) const;
ArgumentTypeEnvironment args,
const bool is_replayable = false) const;

friend class type_analyzer::WholeProgramState;
};

class GlobalTypeAnalysis {
Expand Down
2 changes: 1 addition & 1 deletion service/type-analysis/ResolveMethodRefs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ ResolveMethodRefs::ResolveMethodRefs(
return;
}
cfg::ScopedCFG cfg(code);
auto lta = gta.get_local_analysis(method);
auto lta = gta.get_replayable_local_analysis(method);
// Using the result of GTA, check if an interface can be resolved to its
// implementor at certain callsite.
analyze_method(method, *lta, xstores);
Expand Down
9 changes: 4 additions & 5 deletions service/type-analysis/WholeProgramState.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,7 @@ void set_ifields_in_partition(const DexClass* cls,
TRACE(TYPE, 5, "%s has type %s after <init>", SHOW(field), SHOW(domain));
always_assert(field->get_class() == cls->get_type());
} else {
TRACE(TYPE, 5, "%s has null type after <init>", SHOW(field));
domain = DexTypeDomain::null();
TRACE(TYPE, 5, "%s has unknown type after <init>", SHOW(field));
}
field_partition->update(field, [&domain](auto* current_type) {
current_type->join_with(domain);
Expand Down Expand Up @@ -245,7 +244,7 @@ void WholeProgramState::analyze_clinits_and_ctors(
if (clinit) {
IRCode* code = clinit->get_code();
auto& cfg = code->cfg();
auto lta = gta.get_local_analysis(clinit);
auto lta = gta.get_internal_local_analysis(clinit);
const auto& env = lta->get_exit_state_at(cfg.exit_block());
set_sfields_in_partition(cls, env, &cls_field_partition);
} else {
Expand All @@ -262,7 +261,7 @@ void WholeProgramState::analyze_clinits_and_ctors(
}
IRCode* code = ctor->get_code();
auto& cfg = code->cfg();
auto lta = gta.get_local_analysis(ctor);
auto lta = gta.get_internal_local_analysis(ctor);
const auto& env = lta->get_exit_state_at(cfg.exit_block());
set_ifields_in_partition(cls, env, &cls_field_partition);
}
Expand All @@ -286,7 +285,7 @@ void WholeProgramState::collect(const Scope& scope,
return;
}
auto& cfg = code->cfg();
auto lta = gta.get_local_analysis(method);
auto lta = gta.get_internal_local_analysis(method);
for (cfg::Block* b : cfg.blocks()) {
auto env = lta->get_entry_state_at(b);
for (auto& mie : InstructionIterable(b)) {
Expand Down
2 changes: 1 addition & 1 deletion test/instr/TypeAnalysisAssertsTestVerify.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ TEST_F(PostVerify, HasTypeChecks) {
auto test_return_m = find_vmethod_named(*test_cls, "testSetAndGet");
ASSERT_NE(nullptr, test_field_m);
ASSERT_NE(nullptr, test_return_m);
ASSERT_NE(nullptr,
ASSERT_EQ(nullptr,
find_invoke(test_field_m, DOPCODE_INVOKE_STATIC, "fieldValueError",
nullptr));
ASSERT_NE(nullptr,
Expand Down
37 changes: 9 additions & 28 deletions test/integ/GlobalTypeAnalysisTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ TEST_F(GlobalTypeAnalysisTest, ReturnTypeTest) {
EXPECT_EQ(wps.get_return_type(meth_passthrough), get_type_domain("SubTwo"));

auto meth_foo = get_method("TestA;.foo:()I");
auto lta = gta->get_local_analysis(meth_foo);
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_EQ(foo_exit_env.get_reg_environment().get(0),
Expand Down Expand Up @@ -94,8 +94,7 @@ TEST_F(GlobalTypeAnalysisTest, NullableFieldTypeTest) {
// Field holding the reference to the nullalbe anonymous class
auto field_monitor =
get_field("TestC;.mMonitor:Lcom/facebook/redextest/Receiver;");
EXPECT_EQ(*wps.get_field_type(field_monitor).get_dex_type(),
get_type("TestC$1"));
EXPECT_TRUE(wps.get_field_type(field_monitor).is_top());
EXPECT_TRUE(wps.get_field_type(field_monitor).is_nullable());

// Field on the anonymous class referencing the outer class
Expand Down Expand Up @@ -124,9 +123,7 @@ TEST_F(GlobalTypeAnalysisTest, TrueVirtualFieldTypeTest) {
auto wps1 = gta1->get_whole_program_state();

// Multiple callee call graph can propagate via true virtual calls
EXPECT_FALSE(wps1.get_field_type(field_val).is_top());
EXPECT_EQ(*wps1.get_field_type(field_val).get_dex_type(),
get_type("TestD$Sub"));
EXPECT_TRUE(wps1.get_field_type(field_val).is_top());
}

TEST_F(GlobalTypeAnalysisTest, SmallSetDexTypeDomainTest) {
Expand Down Expand Up @@ -157,7 +154,7 @@ TEST_F(GlobalTypeAnalysisTest, ConstNullnessDomainTest) {
auto gta = analysis.analyze(scope);
auto wps = gta->get_whole_program_state();
auto meth_foo = get_method("TestF;.foo", "", "I");
auto lta = gta->get_local_analysis(meth_foo);
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_FALSE(foo_exit_env.get_reg_environment().get(0).is_top());
Expand Down Expand Up @@ -204,32 +201,20 @@ TEST_F(GlobalTypeAnalysisTest, ClinitFieldAnalyzerTest) {
auto field_mbase =
get_field("TestH;.mBase:Lcom/facebook/redextest/TestH$Base;");
ftype = wps.get_field_type(field_mbase);
EXPECT_FALSE(ftype.is_top());
EXPECT_TRUE(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$SubOne", "TestH$SubTwo"}));

auto meth_foo =
get_method("TestH;.foo", "", "Lcom/facebook/redextest/TestH$Base;");
auto rtype = wps.get_return_type(meth_foo);
EXPECT_FALSE(rtype.is_top());
EXPECT_TRUE(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$SubOne", "TestH$SubTwo"}));

auto meth_bar =
get_method("TestH;.bar", "", "Lcom/facebook/redextest/TestH$Base;");
rtype = wps.get_return_type(meth_bar);
EXPECT_FALSE(rtype.is_top());
EXPECT_TRUE(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$SubOne", "TestH$SubTwo"}));

auto meth_baz =
get_method("TestH;.baz", "", "Lcom/facebook/redextest/TestH$Base;");
Expand Down Expand Up @@ -270,10 +255,8 @@ TEST_F(GlobalTypeAnalysisTest, IFieldsNullnessTest) {

auto two_m2 = get_field("TestI$Two;.m2:Lcom/facebook/redextest/TestI$Foo;");
ftype = wps.get_field_type(two_m2);
EXPECT_FALSE(ftype.is_top());
EXPECT_TRUE(ftype.is_top());
EXPECT_TRUE(ftype.is_nullable());
EXPECT_EQ(ftype.get_single_domain(),
SingletonDexTypeDomain(get_type("TestI$Foo")));
}

TEST_F(GlobalTypeAnalysisTest, PrimitiveArrayTest) {
Expand Down Expand Up @@ -316,10 +299,8 @@ TEST_F(GlobalTypeAnalysisTest, InstanceSensitiveCtorNullnessTest) {

auto field_f = get_field("TestL$Foo;.f:Lcom/facebook/redextest/TestL$A;");
auto ftype = wps.get_field_type(field_f);
EXPECT_FALSE(ftype.is_top());
EXPECT_TRUE(ftype.is_top());
EXPECT_TRUE(ftype.is_nullable());
EXPECT_EQ(ftype.get_single_domain(),
SingletonDexTypeDomain(get_type("TestL$A")));
}

TEST_F(GlobalTypeAnalysisTest, ArrayNullnessEscapeTest) {
Expand Down
6 changes: 3 additions & 3 deletions test/unit/type-analysis/GlobalTypeAnalysisTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ TEST_F(GlobalTypeAnalysisTest, ReturnTypeTest) {
auto wps = gta->get_whole_program_state();
EXPECT_EQ(wps.get_return_type(meth_bar), get_type_domain("LO;"));

auto lta = gta->get_local_analysis(meth_foo);
auto lta = gta->get_replayable_local_analysis(meth_foo);
auto code = meth_foo->get_code();
auto bar_exit_env = lta->get_exit_state_at(code->cfg().exit_block());
EXPECT_EQ(bar_exit_env.get_reg_environment().get(0), get_type_domain("LO;"));
Expand Down Expand Up @@ -280,7 +280,7 @@ TEST_F(GlobalTypeAnalysisTest, SimpleFieldTypeTest) {
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_local_analysis(meth_foo);
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_EQ(foo_exit_env.get_reg_environment().get(1),
Expand Down Expand Up @@ -361,7 +361,7 @@ TEST_F(GlobalTypeAnalysisTest, ClinitSimpleTest) {
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_local_analysis(meth_foo);
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_EQ(foo_exit_env.get_reg_environment().get(1),
Expand Down
2 changes: 1 addition & 1 deletion test/unit/type-analysis/TypeAnalysisTransformTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ struct TypeAnalysisTransformTest : public RedexTest {

type_analyzer::Transform::NullAssertionSet null_assertion_set =
kotlin_nullcheck_wrapper::get_kotlin_null_assertions();
auto lta = gta->get_local_analysis(method);
auto lta = gta->get_replayable_local_analysis(method);
Transform tf(config);
return tf.apply(*lta, wps, method, null_assertion_set);
});
Expand Down

0 comments on commit 134979d

Please sign in to comment.