in opt/throw-propagation/ThrowPropagationPass.cpp [100:289]
ThrowPropagationPass::Stats ThrowPropagationPass::run(
const Config& config,
const std::unordered_set<DexMethod*>& no_return_methods,
const method_override_graph::Graph& graph,
IRCode* code) {
ThrowPropagationPass::Stats stats;
cfg::ScopedCFG cfg(code);
auto is_no_return_invoke = [&](IRInstruction* insn) {
if (!opcode::is_an_invoke(insn->opcode())) {
return false;
}
if (insn->opcode() == OPCODE_INVOKE_SUPER) {
// TODO
return false;
}
auto method_ref = insn->get_method();
DexMethod* method = resolve_method(method_ref, opcode_to_search(insn));
if (method == nullptr) {
return false;
}
if (insn->opcode() == OPCODE_INVOKE_INTERFACE &&
is_annotation(type_class(method->get_class()))) {
TRACE(TP, 4, "annotation interface method: %s", SHOW(method));
return false;
}
bool invoke_can_return{false};
auto check_for_no_return = [&](DexMethod* other_method) {
if (!no_return_methods.count(other_method)) {
invoke_can_return = true;
}
return true;
};
if (!process_base_and_overriding_methods(
&graph, method, /* methods_to_ignore */ nullptr,
/* ignore_methods_with_assumenosideeffects */ false,
check_for_no_return)) {
return false;
}
return !invoke_can_return;
};
boost::optional<std::pair<reg_t, reg_t>> regs;
auto will_throw_or_not_terminate = [&cfg](cfg::InstructionIterator it) {
std::unordered_set<IRInstruction*> visited{it->insn};
while (true) {
it = cfg->next_following_gotos(it);
if (!visited.insert(it->insn).second) {
// We found a loop
return true;
}
switch (it->insn->opcode()) {
case OPCODE_CONST:
case OPCODE_CONST_STRING:
case OPCODE_MOVE:
case OPCODE_NOP:
case OPCODE_NEW_INSTANCE:
case IOPCODE_LOAD_PARAM:
case IOPCODE_LOAD_PARAM_OBJECT:
case IOPCODE_MOVE_RESULT_PSEUDO:
break;
case OPCODE_INVOKE_DIRECT: {
auto method = it->insn->get_method();
if (!method::is_init(method) ||
method->get_class() !=
DexType::make_type("Ljava/lang/RuntimeException;")) {
return false;
}
break;
}
case OPCODE_THROW:
return true;
default:
return false;
}
}
not_reached();
};
// Helper function that checks if there's any point in doing a transformation
// (not needed if we are already going to throw or not terminate anyway),
// and it performs block splitting if needed (see comment inline for details).
auto check_if_dead_code_present_and_prepare_block =
[&](cfg::Block* block, const ir_list::InstructionIterator& it) -> bool {
auto insn = it->insn;
TRACE(TP, 4, "no return: %s", SHOW(insn));
auto cfg_it = block->to_cfg_instruction_iterator(it);
if (insn == block->get_last_insn()->insn) {
if (will_throw_or_not_terminate(cfg_it)) {
// There's already code in place that will immediately and
// unconditionally throw an exception, and thus we don't need to
// bother rewriting the code into a throw. The main reason we are
// doing this is to not inflate our throws_inserted statistics.
return false;
}
} else {
// When the invoke instruction isn't the last in the block, then we'll
// need to some extra work. (Ideally, we could have just inserted our
// throwing instructions in the middle of the existing block, but that
// causes complications due to the possibly following and then dangling
// move-result instruction, so we'll explicitly split the block here
// in order to keep all invariant happy.)
if (will_throw_or_not_terminate(cfg_it)) {
// As above, nothing to do, since an exception will be thrown anyway.
return false;
}
always_assert(cfg->get_succ_edge_of_type(block, cfg::EDGE_THROW) ==
nullptr);
cfg->split_block(cfg_it);
always_assert(insn == block->get_last_insn()->insn);
}
return true;
};
auto insert_throw = [&](cfg::Block* block, IRInstruction* insn) {
std::string message{"Redex: Unreachable code after no-return invoke"};
if (config.debug) {
message += ":";
message += SHOW(insn);
}
if (!regs) {
regs = std::make_pair(cfg->allocate_temp(), cfg->allocate_temp());
}
auto exception_reg = regs->first;
auto string_reg = regs->second;
cfg::Block* new_block = cfg->create_block();
std::vector<IRInstruction*> insns;
auto new_instance_insn = new IRInstruction(OPCODE_NEW_INSTANCE);
auto exception_type = DexType::get_type("Ljava/lang/RuntimeException;");
always_assert(exception_type != nullptr);
new_instance_insn->set_type(exception_type);
insns.push_back(new_instance_insn);
auto move_result_pseudo_exception_insn =
new IRInstruction(IOPCODE_MOVE_RESULT_PSEUDO_OBJECT);
move_result_pseudo_exception_insn->set_dest(exception_reg);
insns.push_back(move_result_pseudo_exception_insn);
auto const_string_insn = new IRInstruction(OPCODE_CONST_STRING);
const_string_insn->set_string(DexString::make_string(message));
insns.push_back(const_string_insn);
auto move_result_pseudo_string_insn =
new IRInstruction(IOPCODE_MOVE_RESULT_PSEUDO_OBJECT);
move_result_pseudo_string_insn->set_dest(string_reg);
insns.push_back(move_result_pseudo_string_insn);
auto invoke_direct_insn = new IRInstruction(OPCODE_INVOKE_DIRECT);
auto init_method = DexMethod::get_method(
"Ljava/lang/RuntimeException;.<init>:(Ljava/lang/String;)V");
always_assert(init_method != nullptr);
invoke_direct_insn->set_method(init_method)
->set_srcs_size(2)
->set_src(0, exception_reg)
->set_src(1, string_reg);
insns.push_back(invoke_direct_insn);
auto throw_insn = new IRInstruction(OPCODE_THROW);
throw_insn->set_src(0, exception_reg);
insns.push_back(throw_insn);
new_block->push_back(insns);
cfg->copy_succ_edges_of_type(block, new_block, cfg::EDGE_THROW);
auto existing_goto_edge = cfg->get_succ_edge_of_type(block, cfg::EDGE_GOTO);
always_assert(existing_goto_edge != nullptr);
cfg->set_edge_target(existing_goto_edge, new_block);
stats.throws_inserted++;
};
for (auto block : cfg->blocks()) {
auto ii = InstructionIterable(block);
for (auto it = ii.begin(); it != ii.end(); it++) {
auto insn = it->insn;
if (!is_no_return_invoke(insn)) {
continue;
}
if (!check_if_dead_code_present_and_prepare_block(block, it)) {
continue;
}
insert_throw(block, insn);
// Stop processing more instructions in this block
break;
}
}
if (stats.throws_inserted > 0) {
stats.unreachable_instruction_count +=
cfg->remove_unreachable_blocks().first;
cfg->recompute_registers_size();
}
return stats;
}