ThrowPropagationPass::Stats ThrowPropagationPass::run()

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