opt/remove-apilevel-checks/RemoveApiLevelChecks.cpp (239 lines of code) (raw):
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "RemoveApiLevelChecks.h"
#include <boost/optional.hpp>
#include "ControlFlow.h"
#include "PassManager.h"
#include "ReachingDefinitions.h"
#include "ScopedCFG.h"
#include "Show.h"
#include "Walkers.h"
namespace {
using namespace cfg;
std::unordered_set<const IRInstruction*> find_sdk_int_sgets(
const ControlFlowGraph& cfg, const DexFieldRef* sdk_int_field) {
std::unordered_set<const IRInstruction*> ret;
for (const auto& it : ConstInstructionIterable(cfg)) {
if (it.insn->opcode() == OPCODE_SGET) {
auto f = it.insn->get_field();
if (f == sdk_int_field) {
ret.insert(it.insn);
}
}
}
return ret;
}
IROpcode get_symmetric_cond(IROpcode op) {
switch (op) {
case OPCODE_IF_EQ:
return OPCODE_IF_EQ;
case OPCODE_IF_NE:
return OPCODE_IF_NE;
case OPCODE_IF_LT:
return OPCODE_IF_GT;
case OPCODE_IF_GE:
return OPCODE_IF_LE;
case OPCODE_IF_GT:
return OPCODE_IF_LT;
case OPCODE_IF_LE:
return OPCODE_IF_GE;
default:
not_reached_log("Invalid conditional opcode %s", SHOW(op));
}
}
boost::optional<bool> analyze1(
IROpcode op,
IRInstruction* i0,
const std::unordered_set<const IRInstruction*>& sgets,
int32_t min_sdk) {
if (sgets.count(i0) == 0) {
return boost::none;
}
// Is "x (op) cmp_val = result" for all "x >= min_sdk" ?
switch (op) {
case OPCODE_IF_EQZ:
if (min_sdk > 0) {
return false;
}
break;
case OPCODE_IF_NEZ:
if (min_sdk > 0) {
return true;
}
break;
case OPCODE_IF_LEZ:
if (min_sdk > 0) {
return false;
}
break;
case OPCODE_IF_LTZ:
if (min_sdk >= 0) {
return false;
}
break;
case OPCODE_IF_GEZ:
if (min_sdk >= 0) {
return true;
}
break;
case OPCODE_IF_GTZ:
if (min_sdk > 0) {
return true;
}
break;
default:
break;
}
return boost::none;
};
boost::optional<bool> analyze2(
IROpcode op,
IRInstruction* i0,
IRInstruction* i1,
const std::unordered_set<const IRInstruction*>& sgets,
int32_t min_sdk) {
if (sgets.count(i0) == 0 && sgets.count(i1) == 0) {
return boost::none;
}
// Normalize: want "min_sdk (op) value."
IRInstruction* cmp = i1;
if (sgets.count(i1) > 0) {
// This is not inversion!
cmp = i0;
op = get_symmetric_cond(op);
}
if (cmp->opcode() != OPCODE_CONST) {
return boost::none;
}
int64_t cmp_val = cmp->get_literal();
// Is "x (op) cmp_val = result" for all "x >= min_sdk" ?
switch (op) {
case OPCODE_IF_LT:
if (min_sdk >= cmp_val) {
return false;
}
break;
case OPCODE_IF_LE:
if (min_sdk > cmp_val) {
return false;
}
break;
case OPCODE_IF_GT:
if (min_sdk > cmp_val) {
return true;
}
break;
case OPCODE_IF_GE:
if (min_sdk >= cmp_val) {
return true;
}
break;
default:
break;
}
return boost::none;
};
size_t analyze_and_rewrite(
ControlFlowGraph& cfg,
const std::unordered_set<const IRInstruction*>& sgets,
int32_t min_sdk) {
std::unique_ptr<reaching_defs::MoveAwareFixpointIterator> rdefs;
auto get_defs = [&](Block* b, IRInstruction* i) {
if (!rdefs) {
rdefs.reset(new reaching_defs::MoveAwareFixpointIterator(cfg));
rdefs->run(reaching_defs::Environment());
}
auto defs_in = rdefs->get_entry_state_at(b);
for (const auto& it : ir_list::InstructionIterable{b}) {
if (it.insn == i) {
break;
}
rdefs->analyze_instruction(it.insn, &defs_in);
}
return defs_in;
};
auto get_singleton = [](auto& defs, reg_t reg) -> IRInstruction* {
const auto& defs0 = defs.get(reg);
if (defs0.is_top() || defs0.is_bottom()) {
return nullptr;
}
if (defs0.elements().size() != 1) {
return nullptr;
}
return *defs0.elements().begin();
};
size_t removed = 0;
for (auto* b : cfg.blocks()) {
auto it = b->get_last_insn();
if (it == b->end()) {
continue;
}
auto* insn = it->insn;
IROpcode op = insn->opcode();
if (!opcode::is_a_conditional_branch(op)) {
continue;
}
auto defs = get_defs(b, insn);
IRInstruction* i0 = get_singleton(defs, insn->src(0));
if (i0 == nullptr) {
continue;
}
boost::optional<bool> result = boost::none;
if (insn->srcs_size() == 1) {
result = analyze1(op, i0, sgets, min_sdk);
} else if (insn->srcs_size() == 2) {
IRInstruction* i1 = get_singleton(defs, insn->src(1));
if (i1 == nullptr) {
continue;
}
result = analyze2(op, i0, i1, sgets, min_sdk);
}
if (!result) {
continue;
}
if (*result) {
// Change the GOTO edge to point to the BRANCH edge's target.
auto branch_edge = cfg.get_succ_edge_of_type(b, EDGE_BRANCH);
redex_assert(branch_edge);
auto goto_edge = cfg.get_succ_edge_of_type(b, EDGE_GOTO);
redex_assert(goto_edge);
cfg.set_edge_target(goto_edge, branch_edge->target());
}
cfg.remove_insn(b->to_cfg_instruction_iterator(it));
++removed;
}
return removed;
}
} // namespace
const DexFieldRef* RemoveApiLevelChecksPass::get_sdk_int_field() {
return DexField::get_field(DexType::make_type("Landroid/os/Build$VERSION;"),
DexString::make_string("SDK_INT"),
DexType::get_type("I"));
}
RemoveApiLevelChecksPass::ApiLevelStats RemoveApiLevelChecksPass::run(
IRCode* code, int32_t min_sdk, const DexFieldRef* sdk_int_field) {
if (!code) {
return ApiLevelStats{};
}
cfg::ScopedCFG cfg(code);
auto sdk_int_sgets = find_sdk_int_sgets(*cfg, sdk_int_field);
if (sdk_int_sgets.empty()) {
return ApiLevelStats();
}
ApiLevelStats ret;
ret.num_field_gets = sdk_int_sgets.size();
ret.num_methods = 1;
ret.num_removed = analyze_and_rewrite(*cfg, sdk_int_sgets, min_sdk);
return ret;
}
void RemoveApiLevelChecksPass::run_pass(DexStoresVector& stores,
ConfigFiles&,
PassManager& mgr) {
auto scope = build_class_scope(stores);
auto min_sdk = mgr.get_redex_options().min_sdk;
const auto* sdk_int_field = get_sdk_int_field();
redex_assert(sdk_int_field != nullptr);
ApiLevelStats stats = walk::parallel::methods<ApiLevelStats>(
scope, [min_sdk, sdk_int_field](DexMethod* method) -> ApiLevelStats {
auto code = method->get_code();
return run(code, min_sdk, sdk_int_field);
});
mgr.set_metric("min_sdk", min_sdk);
mgr.incr_metric("num_field_gets", stats.num_field_gets);
mgr.incr_metric("num_methods", stats.num_methods);
mgr.incr_metric("num_optimized", stats.num_removed);
}
static RemoveApiLevelChecksPass s_pass;