libredex/TypeInference.cpp (1,009 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 "TypeInference.h"
#include <numeric>
#include <ostream>
#include <sstream>
#include "Show.h"
#include "Trace.h"
std::ostream& operator<<(std::ostream& output, const IRType& type) {
switch (type) {
case BOTTOM: {
output << "BOTTOM";
break;
}
case ZERO: {
output << "ZERO";
break;
}
case CONST: {
output << "CONST";
break;
}
case CONST1: {
output << "CONST1";
break;
}
case CONST2: {
output << "CONST2";
break;
}
case REFERENCE: {
output << "REF";
break;
}
case INT: {
output << "INT";
break;
}
case FLOAT: {
output << "FLOAT";
break;
}
case LONG1: {
output << "LONG1";
break;
}
case LONG2: {
output << "LONG2";
break;
}
case DOUBLE1: {
output << "DOUBLE1";
break;
}
case DOUBLE2: {
output << "DOUBLE2";
break;
}
case SCALAR: {
output << "SCALAR";
break;
}
case SCALAR1: {
output << "SCALAR1";
break;
}
case SCALAR2: {
output << "SCALAR2";
break;
}
case TOP: {
output << "TOP";
break;
}
}
return output;
}
namespace type_inference {
TypeLattice type_lattice(
{BOTTOM, ZERO, CONST, CONST1, CONST2, REFERENCE, INT, FLOAT, LONG1, LONG2,
DOUBLE1, DOUBLE2, SCALAR, SCALAR1, SCALAR2, TOP},
{{BOTTOM, ZERO}, {BOTTOM, CONST1}, {BOTTOM, CONST2},
{ZERO, REFERENCE}, {ZERO, CONST}, {CONST, INT},
{CONST, FLOAT}, {CONST1, LONG1}, {CONST1, DOUBLE1},
{CONST2, LONG2}, {CONST2, DOUBLE2}, {INT, SCALAR},
{FLOAT, SCALAR}, {LONG1, SCALAR1}, {DOUBLE1, SCALAR1},
{LONG2, SCALAR2}, {DOUBLE2, SCALAR2}, {REFERENCE, TOP},
{SCALAR, TOP}, {SCALAR1, TOP}, {SCALAR2, TOP}});
void set_type(TypeEnvironment* state, reg_t reg, const TypeDomain& type) {
state->set_type(reg, type);
}
void set_integer(TypeEnvironment* state, reg_t reg) {
state->set_type(reg, TypeDomain(INT));
state->reset_dex_type(reg);
}
void set_float(TypeEnvironment* state, reg_t reg) {
state->set_type(reg, TypeDomain(FLOAT));
state->reset_dex_type(reg);
}
void set_scalar(TypeEnvironment* state, reg_t reg) {
state->set_type(reg, TypeDomain(SCALAR));
state->reset_dex_type(reg);
}
void set_reference(TypeEnvironment* state,
reg_t reg,
const boost::optional<const DexType*>& dex_type_opt) {
state->set_type(reg, TypeDomain(REFERENCE));
const DexTypeDomain dex_type =
dex_type_opt ? DexTypeDomain(*dex_type_opt) : DexTypeDomain::top();
state->set_dex_type(reg, dex_type);
}
void set_reference(TypeEnvironment* state,
reg_t reg,
const DexTypeDomain& dex_type) {
state->set_type(reg, TypeDomain(REFERENCE));
state->set_dex_type(reg, dex_type);
}
void set_long(TypeEnvironment* state, reg_t reg) {
state->set_type(reg, TypeDomain(LONG1));
state->set_type(reg + 1, TypeDomain(LONG2));
state->reset_dex_type(reg);
state->reset_dex_type(reg + 1);
}
void set_double(TypeEnvironment* state, reg_t reg) {
state->set_type(reg, TypeDomain(DOUBLE1));
state->set_type(reg + 1, TypeDomain(DOUBLE2));
state->reset_dex_type(reg);
state->reset_dex_type(reg + 1);
}
void set_wide_scalar(TypeEnvironment* state, reg_t reg) {
state->set_type(reg, TypeDomain(SCALAR1));
state->set_type(reg + 1, TypeDomain(SCALAR2));
state->reset_dex_type(reg);
state->reset_dex_type(reg + 1);
}
// This is used for the operand of a comparison operation with zero. The
// complexity here is that this operation may be performed on either an
// integer or a reference.
void refine_comparable_with_zero(TypeEnvironment* state, reg_t reg) {
if (state->is_bottom()) {
// There's nothing to do for unreachable code.
return;
}
IRType t = state->get_type(reg).element();
if (t == SCALAR) {
// We can't say anything conclusive about a register that has SCALAR type,
// so we just bail out.
return;
}
if (!(TypeDomain(t).leq(TypeDomain(REFERENCE)) ||
TypeDomain(t).leq(TypeDomain(INT)))) {
// The type is incompatible with the operation and hence, the code that
// follows is unreachable.
state->set_to_bottom();
return;
}
}
// This is used for the operands of a comparison operation between two
// registers. The complexity here is that this operation may be performed on
// either two integers or two references.
void refine_comparable(TypeEnvironment* state, reg_t reg1, reg_t reg2) {
if (state->is_bottom()) {
// There's nothing to do for unreachable code.
return;
}
IRType t1 = state->get_type(reg1).element();
IRType t2 = state->get_type(reg2).element();
if (!((TypeDomain(t1).leq(TypeDomain(REFERENCE)) &&
TypeDomain(t2).leq(TypeDomain(REFERENCE))) ||
(TypeDomain(t1).leq(TypeDomain(SCALAR)) &&
TypeDomain(t2).leq(TypeDomain(SCALAR)) && (t1 != FLOAT) &&
(t2 != FLOAT)))) {
// Two values can be used in a comparison operation if they either both
// have the REFERENCE type or have non-float scalar types. Note that in
// the case where one or both types have the SCALAR type, we can't
// definitely rule out the absence of a type error.
// The types are incompatible and hence, the code that follows is
// unreachable.
state->set_to_bottom();
return;
}
}
template <typename DexTypeIt>
const DexType* merge_dex_types(const DexTypeIt& begin,
const DexTypeIt& end,
const DexType* default_type) {
if (begin == end) {
return default_type;
}
return std::accumulate(
begin, end, static_cast<const DexType*>(nullptr),
[&default_type](const DexType* t1, const DexType* t2) -> const DexType* {
if (!t1) {
return t2;
}
if (!t2) {
return t1;
}
DexTypeDomain d1(t1);
DexTypeDomain d2(t2);
d1.join_with(d2);
auto maybe_dextype = d1.get_dex_type();
// In case of the join giving up, bail to a default type
return maybe_dextype ? *maybe_dextype : default_type;
});
}
TypeDomain TypeInference::refine_type(const TypeDomain& type,
IRType expected,
IRType const_type,
IRType scalar_type) const {
auto refined_type = type.meet(TypeDomain(expected));
// If constants are not considered polymorphic (the default behavior of the
// Android verifier), we lift the constant to the type expected in the given
// context. This only makes sense if the expected type is fully determined
// by the context, i.e., is not a scalar type (SCALAR/SCALAR1/SCALAR2).
if (type.leq(TypeDomain(const_type)) && expected != scalar_type) {
return refined_type.is_bottom() ? refined_type : TypeDomain(expected);
}
return refined_type;
}
void TypeInference::refine_type(TypeEnvironment* state,
reg_t reg,
IRType expected) const {
state->update_type(reg, [this, expected](const TypeDomain& type) {
return refine_type(type, expected, /* const_type */ CONST,
/* scalar_type */ SCALAR);
});
}
void TypeInference::refine_wide_type(TypeEnvironment* state,
reg_t reg,
IRType expected1,
IRType expected2) const {
state->update_type(reg, [this, expected1](const TypeDomain& type) {
return refine_type(type,
expected1,
/* const_type */ CONST1,
/* scalar_type */ SCALAR1);
});
state->update_type(reg + 1, [this, expected2](const TypeDomain& type) {
return refine_type(type,
expected2,
/* const_type */ CONST2,
/* scalar_type */ SCALAR2);
});
}
void TypeInference::refine_reference(TypeEnvironment* state, reg_t reg) const {
refine_type(state,
reg,
/* expected */ REFERENCE);
}
void TypeInference::refine_scalar(TypeEnvironment* state, reg_t reg) const {
refine_type(state,
reg,
/* expected */ SCALAR);
state->reset_dex_type(reg);
}
void TypeInference::refine_integer(TypeEnvironment* state, reg_t reg) const {
refine_type(state, reg, /* expected */ INT);
state->reset_dex_type(reg);
}
void TypeInference::refine_float(TypeEnvironment* state, reg_t reg) const {
refine_type(state, reg, /* expected */ FLOAT);
state->reset_dex_type(reg);
}
void TypeInference::refine_wide_scalar(TypeEnvironment* state,
reg_t reg) const {
refine_wide_type(state, reg, /* expected1 */ SCALAR1,
/* expected2 */ SCALAR2);
state->reset_dex_type(reg);
state->reset_dex_type(reg + 1);
}
void TypeInference::refine_long(TypeEnvironment* state, reg_t reg) const {
refine_wide_type(state, reg, /* expected1 */ LONG1, /* expected2 */ LONG2);
state->reset_dex_type(reg);
state->reset_dex_type(reg + 1);
}
void TypeInference::refine_double(TypeEnvironment* state, reg_t reg) const {
refine_wide_type(state, reg, /* expected1 */ DOUBLE1,
/* expected2 */ DOUBLE2);
state->reset_dex_type(reg);
state->reset_dex_type(reg + 1);
}
void TypeInference::run(const DexMethod* dex_method) {
run(is_static(dex_method), dex_method->get_class(),
dex_method->get_proto()->get_args());
}
void TypeInference::run(bool is_static,
DexType* declaring_type,
DexTypeList* args) {
// We need to compute the initial environment by assigning the parameter
// registers their correct types derived from the method's signature. The
// IOPCODE_LOAD_PARAM_* instructions are pseudo-operations that are used to
// specify the formal parameters of the method. They must be interpreted
// separately.
auto init_state = TypeEnvironment::top();
auto sig_it = args->begin();
bool first_param = true;
for (const auto& mie : InstructionIterable(m_cfg.get_param_instructions())) {
IRInstruction* insn = mie.insn;
switch (insn->opcode()) {
case IOPCODE_LOAD_PARAM_OBJECT: {
if (first_param && !is_static) {
// If the method is not static, the first parameter corresponds to
// `this`.
first_param = false;
set_reference(&init_state, insn->dest(), declaring_type);
} else {
// This is a regular parameter of the method.
always_assert(sig_it != args->end());
const DexType* type = *sig_it;
set_reference(&init_state, insn->dest(), type);
++sig_it;
}
break;
}
case IOPCODE_LOAD_PARAM: {
always_assert(sig_it != args->end());
if (type::is_float(*sig_it++)) {
set_float(&init_state, insn->dest());
} else {
set_integer(&init_state, insn->dest());
}
break;
}
case IOPCODE_LOAD_PARAM_WIDE: {
always_assert(sig_it != args->end());
if (type::is_double(*sig_it++)) {
set_double(&init_state, insn->dest());
} else {
set_long(&init_state, insn->dest());
}
break;
}
default:
not_reached();
}
}
MonotonicFixpointIterator::run(init_state);
populate_type_environments();
}
// This method analyzes an instruction and updates the type environment
// accordingly during the fixpoint iteration.
//
// Similarly, the various refine_* functions are used to refine the
// type of a register depending on the context (e.g., from SCALAR to INT).
void TypeInference::analyze_instruction(const IRInstruction* insn,
TypeEnvironment* current_state,
const cfg::Block* current_block) const {
switch (insn->opcode()) {
case IOPCODE_LOAD_PARAM:
case IOPCODE_LOAD_PARAM_OBJECT:
case IOPCODE_LOAD_PARAM_WIDE: {
// IOPCODE_LOAD_PARAM_* instructions have been processed before the
// analysis.
break;
}
case OPCODE_NOP: {
break;
}
case OPCODE_MOVE: {
refine_scalar(current_state, insn->src(0));
set_type(current_state, insn->dest(),
current_state->get_type(insn->src(0)));
break;
}
case OPCODE_MOVE_OBJECT: {
refine_reference(current_state, insn->src(0));
if (current_state->get_type(insn->src(0)) == TypeDomain(REFERENCE)) {
const auto dex_type = current_state->get_type_domain(insn->src(0));
set_reference(current_state, insn->dest(), dex_type);
} else {
set_type(current_state, insn->dest(),
current_state->get_type(insn->src(0)));
}
break;
}
case OPCODE_MOVE_WIDE: {
refine_wide_scalar(current_state, insn->src(0));
TypeDomain td1 = current_state->get_type(insn->src(0));
TypeDomain td2 = current_state->get_type(insn->src(0) + 1);
set_type(current_state, insn->dest(), td1);
set_type(current_state, insn->dest() + 1, td2);
break;
}
case IOPCODE_MOVE_RESULT_PSEUDO:
case OPCODE_MOVE_RESULT: {
refine_scalar(current_state, RESULT_REGISTER);
set_type(current_state, insn->dest(),
current_state->get_type(RESULT_REGISTER));
break;
}
case IOPCODE_MOVE_RESULT_PSEUDO_OBJECT:
case OPCODE_MOVE_RESULT_OBJECT: {
refine_reference(current_state, RESULT_REGISTER);
set_reference(current_state,
insn->dest(),
current_state->get_type_domain(RESULT_REGISTER));
break;
}
case IOPCODE_MOVE_RESULT_PSEUDO_WIDE:
case OPCODE_MOVE_RESULT_WIDE: {
refine_wide_scalar(current_state, RESULT_REGISTER);
set_type(current_state, insn->dest(),
current_state->get_type(RESULT_REGISTER));
set_type(current_state,
insn->dest() + 1,
current_state->get_type(RESULT_REGISTER + 1));
break;
}
case OPCODE_MOVE_EXCEPTION: {
if (!current_block) {
// We bail just in case the current block is dangling.
TRACE(TYPE, 2,
"Warning: Can't infer exception type from unknown catch block.");
set_reference(current_state, insn->dest(), type::java_lang_Throwable());
break;
}
// The block that contained this instruction must be a catch block.
const auto& preds = current_block->preds();
if (preds.empty()) {
// We bail just in case the current block is dangling.
TRACE(TYPE, 2,
"Warning: Catch block doesn't have at least one predecessor.");
set_reference(current_state, insn->dest(), type::java_lang_Throwable());
break;
}
std::unordered_set<DexType*> catch_types;
for (cfg::Edge* edge : preds) {
if (edge->type() != cfg::EDGE_THROW) {
continue;
}
DexType* catch_type = edge->throw_info()->catch_type;
if (catch_type) {
catch_types.emplace(catch_type);
} else {
// catch all
catch_types.emplace(type::java_lang_Throwable());
}
}
auto merged_catch_type =
merge_dex_types(catch_types.begin(), catch_types.end(),
/* default */ type::java_lang_Throwable());
set_reference(current_state, insn->dest(), merged_catch_type);
break;
}
case OPCODE_RETURN_VOID: {
break;
}
case OPCODE_RETURN: {
refine_scalar(current_state, insn->src(0));
break;
}
case OPCODE_RETURN_WIDE: {
refine_wide_scalar(current_state, insn->src(0));
break;
}
case OPCODE_RETURN_OBJECT: {
refine_reference(current_state, insn->src(0));
break;
}
case OPCODE_CONST: {
if (insn->get_literal() == 0) {
current_state->set_dex_type(insn->dest(), DexTypeDomain::null());
set_type(current_state, insn->dest(), TypeDomain(ZERO));
} else {
set_type(current_state, insn->dest(), TypeDomain(CONST));
}
break;
}
case OPCODE_CONST_WIDE: {
set_type(current_state, insn->dest(), TypeDomain(CONST1));
set_type(current_state, insn->dest() + 1, TypeDomain(CONST2));
break;
}
case OPCODE_CONST_STRING: {
set_reference(current_state, RESULT_REGISTER, type::java_lang_String());
break;
}
case OPCODE_CONST_CLASS: {
set_reference(current_state, RESULT_REGISTER, type::java_lang_Class());
break;
}
case OPCODE_MONITOR_ENTER:
case OPCODE_MONITOR_EXIT: {
refine_reference(current_state, insn->src(0));
break;
}
case OPCODE_CHECK_CAST: {
refine_reference(current_state, insn->src(0));
auto to_type = insn->get_type();
auto to_cls = type_class(to_type);
if (m_skip_check_cast_to_intf && to_cls && is_interface(to_cls)) {
set_reference(current_state, RESULT_REGISTER,
current_state->get_type_domain(insn->src(0)));
} else {
set_reference(current_state, RESULT_REGISTER, insn->get_type());
}
break;
}
case OPCODE_INSTANCE_OF:
case OPCODE_ARRAY_LENGTH: {
refine_reference(current_state, insn->src(0));
set_integer(current_state, RESULT_REGISTER);
break;
}
case OPCODE_NEW_INSTANCE: {
set_reference(current_state, RESULT_REGISTER, insn->get_type());
break;
}
case OPCODE_NEW_ARRAY: {
refine_integer(current_state, insn->src(0));
set_reference(current_state, RESULT_REGISTER, insn->get_type());
break;
}
case OPCODE_FILLED_NEW_ARRAY: {
const DexType* element_type =
type::get_array_component_type(insn->get_type());
// We assume that structural constraints on the bytecode are satisfied,
// i.e., the type is indeed an array type.
always_assert(element_type != nullptr);
bool is_array_of_references = type::is_object(element_type);
for (size_t i = 0; i < insn->srcs_size(); ++i) {
if (is_array_of_references) {
refine_reference(current_state, insn->src(i));
} else {
refine_scalar(current_state, insn->src(i));
}
}
set_reference(current_state, RESULT_REGISTER, insn->get_type());
break;
}
case OPCODE_FILL_ARRAY_DATA: {
break;
}
case OPCODE_THROW: {
refine_reference(current_state, insn->src(0));
break;
}
case OPCODE_GOTO: {
break;
}
case OPCODE_SWITCH: {
refine_integer(current_state, insn->src(0));
break;
}
case OPCODE_CMPL_FLOAT:
case OPCODE_CMPG_FLOAT: {
refine_float(current_state, insn->src(0));
refine_float(current_state, insn->src(1));
set_integer(current_state, insn->dest());
break;
}
case OPCODE_CMPL_DOUBLE:
case OPCODE_CMPG_DOUBLE: {
refine_double(current_state, insn->src(0));
refine_double(current_state, insn->src(1));
set_integer(current_state, insn->dest());
break;
}
case OPCODE_CMP_LONG: {
refine_long(current_state, insn->src(0));
refine_long(current_state, insn->src(1));
set_integer(current_state, insn->dest());
break;
}
case OPCODE_IF_EQ:
case OPCODE_IF_NE: {
refine_comparable(current_state, insn->src(0), insn->src(1));
break;
}
case OPCODE_IF_LT:
case OPCODE_IF_GE:
case OPCODE_IF_GT:
case OPCODE_IF_LE: {
refine_integer(current_state, insn->src(0));
refine_integer(current_state, insn->src(1));
break;
}
case OPCODE_IF_EQZ:
case OPCODE_IF_NEZ: {
refine_comparable_with_zero(current_state, insn->src(0));
break;
}
case OPCODE_IF_LTZ:
case OPCODE_IF_GEZ:
case OPCODE_IF_GTZ:
case OPCODE_IF_LEZ: {
refine_integer(current_state, insn->src(0));
break;
}
case OPCODE_AGET: {
refine_reference(current_state, insn->src(0));
refine_integer(current_state, insn->src(1));
set_scalar(current_state, RESULT_REGISTER);
break;
}
case OPCODE_AGET_BOOLEAN:
case OPCODE_AGET_BYTE:
case OPCODE_AGET_CHAR:
case OPCODE_AGET_SHORT: {
refine_reference(current_state, insn->src(0));
refine_integer(current_state, insn->src(1));
set_integer(current_state, RESULT_REGISTER);
break;
}
case OPCODE_AGET_WIDE: {
refine_reference(current_state, insn->src(0));
refine_integer(current_state, insn->src(1));
set_wide_scalar(current_state, RESULT_REGISTER);
break;
}
case OPCODE_AGET_OBJECT: {
refine_reference(current_state, insn->src(0));
refine_integer(current_state, insn->src(1));
const auto dex_type_opt = current_state->get_dex_type(insn->src(0));
if (dex_type_opt && *dex_type_opt && type::is_array(*dex_type_opt)) {
const auto etype = type::get_array_component_type(*dex_type_opt);
set_reference(current_state, RESULT_REGISTER, etype);
} else {
set_reference(current_state, RESULT_REGISTER, DexTypeDomain::top());
}
break;
}
case OPCODE_APUT: {
refine_scalar(current_state, insn->src(0));
refine_reference(current_state, insn->src(1));
refine_integer(current_state, insn->src(2));
break;
}
case OPCODE_APUT_BOOLEAN:
case OPCODE_APUT_BYTE:
case OPCODE_APUT_CHAR:
case OPCODE_APUT_SHORT: {
refine_integer(current_state, insn->src(0));
refine_reference(current_state, insn->src(1));
refine_integer(current_state, insn->src(2));
break;
}
case OPCODE_APUT_WIDE: {
refine_wide_scalar(current_state, insn->src(0));
refine_reference(current_state, insn->src(1));
refine_integer(current_state, insn->src(2));
break;
}
case OPCODE_APUT_OBJECT: {
refine_reference(current_state, insn->src(0));
refine_reference(current_state, insn->src(1));
refine_integer(current_state, insn->src(2));
break;
}
case OPCODE_IGET: {
refine_reference(current_state, insn->src(0));
const DexType* type = insn->get_field()->get_type();
if (type::is_float(type)) {
set_float(current_state, RESULT_REGISTER);
} else {
set_integer(current_state, RESULT_REGISTER);
}
break;
}
case OPCODE_IGET_BOOLEAN:
case OPCODE_IGET_BYTE:
case OPCODE_IGET_CHAR:
case OPCODE_IGET_SHORT: {
refine_reference(current_state, insn->src(0));
set_integer(current_state, RESULT_REGISTER);
break;
}
case OPCODE_IGET_WIDE: {
refine_reference(current_state, insn->src(0));
const DexType* type = insn->get_field()->get_type();
if (type::is_double(type)) {
set_double(current_state, RESULT_REGISTER);
} else {
set_long(current_state, RESULT_REGISTER);
}
break;
}
case OPCODE_IGET_OBJECT: {
refine_reference(current_state, insn->src(0));
always_assert(insn->has_field());
const auto field = insn->get_field();
set_reference(current_state, RESULT_REGISTER, field->get_type());
break;
}
case OPCODE_IPUT: {
const DexType* type = insn->get_field()->get_type();
if (type::is_float(type)) {
refine_float(current_state, insn->src(0));
} else {
refine_integer(current_state, insn->src(0));
}
refine_reference(current_state, insn->src(1));
break;
}
case OPCODE_IPUT_BOOLEAN:
case OPCODE_IPUT_BYTE:
case OPCODE_IPUT_CHAR:
case OPCODE_IPUT_SHORT: {
refine_integer(current_state, insn->src(0));
refine_reference(current_state, insn->src(1));
break;
}
case OPCODE_IPUT_WIDE: {
refine_wide_scalar(current_state, insn->src(0));
refine_reference(current_state, insn->src(1));
break;
}
case OPCODE_IPUT_OBJECT: {
refine_reference(current_state, insn->src(0));
refine_reference(current_state, insn->src(1));
break;
}
case OPCODE_SGET: {
DexType* type = insn->get_field()->get_type();
if (type::is_float(type)) {
set_float(current_state, RESULT_REGISTER);
} else {
set_integer(current_state, RESULT_REGISTER);
}
break;
}
case OPCODE_SGET_BOOLEAN:
case OPCODE_SGET_BYTE:
case OPCODE_SGET_CHAR:
case OPCODE_SGET_SHORT: {
set_integer(current_state, RESULT_REGISTER);
break;
}
case OPCODE_SGET_WIDE: {
DexType* type = insn->get_field()->get_type();
if (type::is_double(type)) {
set_double(current_state, RESULT_REGISTER);
} else {
set_long(current_state, RESULT_REGISTER);
}
break;
}
case OPCODE_SGET_OBJECT: {
always_assert(insn->has_field());
const auto field = insn->get_field();
set_reference(current_state, RESULT_REGISTER, field->get_type());
break;
}
case OPCODE_SPUT: {
const DexType* type = insn->get_field()->get_type();
if (type::is_float(type)) {
refine_float(current_state, insn->src(0));
} else {
refine_integer(current_state, insn->src(0));
}
break;
}
case OPCODE_SPUT_BOOLEAN:
case OPCODE_SPUT_BYTE:
case OPCODE_SPUT_CHAR:
case OPCODE_SPUT_SHORT: {
refine_integer(current_state, insn->src(0));
break;
}
case OPCODE_SPUT_WIDE: {
refine_wide_scalar(current_state, insn->src(0));
break;
}
case OPCODE_SPUT_OBJECT: {
refine_reference(current_state, insn->src(0));
break;
}
case OPCODE_INVOKE_CUSTOM:
case OPCODE_INVOKE_POLYMORPHIC: {
// TODO(T59277083)
not_reached_log(
"TypeInference::analyze_instruction does not support "
"invoke-custom and invoke-polymorphic yet");
break;
}
case OPCODE_INVOKE_VIRTUAL:
case OPCODE_INVOKE_SUPER:
case OPCODE_INVOKE_DIRECT:
case OPCODE_INVOKE_STATIC:
case OPCODE_INVOKE_INTERFACE: {
DexMethodRef* dex_method = insn->get_method();
const auto* arg_types = dex_method->get_proto()->get_args();
size_t expected_args =
(insn->opcode() != OPCODE_INVOKE_STATIC ? 1 : 0) + arg_types->size();
always_assert_log(insn->srcs_size() == expected_args, "%s", SHOW(insn));
size_t src_idx{0};
if (insn->opcode() != OPCODE_INVOKE_STATIC) {
// The first argument is a reference to the object instance on which the
// method is invoked.
refine_reference(current_state, insn->src(src_idx++));
}
for (DexType* arg_type : *arg_types) {
if (type::is_object(arg_type)) {
refine_reference(current_state, insn->src(src_idx++));
continue;
}
if (type::is_integer(arg_type)) {
refine_integer(current_state, insn->src(src_idx++));
continue;
}
if (type::is_long(arg_type)) {
refine_long(current_state, insn->src(src_idx++));
continue;
}
if (type::is_float(arg_type)) {
refine_float(current_state, insn->src(src_idx++));
continue;
}
always_assert(type::is_double(arg_type));
refine_double(current_state, insn->src(src_idx++));
}
DexType* return_type = dex_method->get_proto()->get_rtype();
if (type::is_void(return_type)) {
break;
}
if (type::is_object(return_type)) {
set_reference(current_state, RESULT_REGISTER, return_type);
break;
}
if (type::is_integer(return_type)) {
set_integer(current_state, RESULT_REGISTER);
break;
}
if (type::is_long(return_type)) {
set_long(current_state, RESULT_REGISTER);
break;
}
if (type::is_float(return_type)) {
set_float(current_state, RESULT_REGISTER);
break;
}
always_assert(type::is_double(return_type));
set_double(current_state, RESULT_REGISTER);
break;
}
case OPCODE_NEG_INT:
case OPCODE_NOT_INT: {
refine_integer(current_state, insn->src(0));
set_integer(current_state, insn->dest());
break;
}
case OPCODE_NEG_LONG:
case OPCODE_NOT_LONG: {
refine_long(current_state, insn->src(0));
set_long(current_state, insn->dest());
break;
}
case OPCODE_NEG_FLOAT: {
refine_float(current_state, insn->src(0));
set_float(current_state, insn->dest());
break;
}
case OPCODE_NEG_DOUBLE: {
refine_double(current_state, insn->src(0));
set_double(current_state, insn->dest());
break;
}
case OPCODE_INT_TO_BYTE:
case OPCODE_INT_TO_CHAR:
case OPCODE_INT_TO_SHORT: {
refine_integer(current_state, insn->src(0));
set_integer(current_state, insn->dest());
break;
}
case OPCODE_LONG_TO_INT: {
refine_long(current_state, insn->src(0));
set_integer(current_state, insn->dest());
break;
}
case OPCODE_FLOAT_TO_INT: {
refine_float(current_state, insn->src(0));
set_integer(current_state, insn->dest());
break;
}
case OPCODE_DOUBLE_TO_INT: {
refine_double(current_state, insn->src(0));
set_integer(current_state, insn->dest());
break;
}
case OPCODE_INT_TO_LONG: {
refine_integer(current_state, insn->src(0));
set_long(current_state, insn->dest());
break;
}
case OPCODE_FLOAT_TO_LONG: {
refine_float(current_state, insn->src(0));
set_long(current_state, insn->dest());
break;
}
case OPCODE_DOUBLE_TO_LONG: {
refine_double(current_state, insn->src(0));
set_long(current_state, insn->dest());
break;
}
case OPCODE_INT_TO_FLOAT: {
refine_integer(current_state, insn->src(0));
set_float(current_state, insn->dest());
break;
}
case OPCODE_LONG_TO_FLOAT: {
refine_long(current_state, insn->src(0));
set_float(current_state, insn->dest());
break;
}
case OPCODE_DOUBLE_TO_FLOAT: {
refine_double(current_state, insn->src(0));
set_float(current_state, insn->dest());
break;
}
case OPCODE_INT_TO_DOUBLE: {
refine_integer(current_state, insn->src(0));
set_double(current_state, insn->dest());
break;
}
case OPCODE_LONG_TO_DOUBLE: {
refine_long(current_state, insn->src(0));
set_double(current_state, insn->dest());
break;
}
case OPCODE_FLOAT_TO_DOUBLE: {
refine_float(current_state, insn->src(0));
set_double(current_state, insn->dest());
break;
}
case OPCODE_ADD_INT:
case OPCODE_SUB_INT:
case OPCODE_MUL_INT:
case OPCODE_AND_INT:
case OPCODE_OR_INT:
case OPCODE_XOR_INT:
case OPCODE_SHL_INT:
case OPCODE_SHR_INT:
case OPCODE_USHR_INT: {
refine_integer(current_state, insn->src(0));
refine_integer(current_state, insn->src(1));
set_integer(current_state, insn->dest());
break;
}
case OPCODE_DIV_INT:
case OPCODE_REM_INT: {
refine_integer(current_state, insn->src(0));
refine_integer(current_state, insn->src(1));
set_integer(current_state, RESULT_REGISTER);
break;
}
case OPCODE_ADD_LONG:
case OPCODE_SUB_LONG:
case OPCODE_MUL_LONG:
case OPCODE_AND_LONG:
case OPCODE_OR_LONG:
case OPCODE_XOR_LONG: {
refine_long(current_state, insn->src(0));
refine_long(current_state, insn->src(1));
set_long(current_state, insn->dest());
break;
}
case OPCODE_DIV_LONG:
case OPCODE_REM_LONG: {
refine_long(current_state, insn->src(0));
refine_long(current_state, insn->src(1));
set_long(current_state, RESULT_REGISTER);
break;
}
case OPCODE_SHL_LONG:
case OPCODE_SHR_LONG:
case OPCODE_USHR_LONG: {
refine_long(current_state, insn->src(0));
refine_integer(current_state, insn->src(1));
set_long(current_state, insn->dest());
break;
}
case OPCODE_ADD_FLOAT:
case OPCODE_SUB_FLOAT:
case OPCODE_MUL_FLOAT:
case OPCODE_DIV_FLOAT:
case OPCODE_REM_FLOAT: {
refine_float(current_state, insn->src(0));
refine_float(current_state, insn->src(1));
set_float(current_state, insn->dest());
break;
}
case OPCODE_ADD_DOUBLE:
case OPCODE_SUB_DOUBLE:
case OPCODE_MUL_DOUBLE:
case OPCODE_DIV_DOUBLE:
case OPCODE_REM_DOUBLE: {
refine_double(current_state, insn->src(0));
refine_double(current_state, insn->src(1));
set_double(current_state, insn->dest());
break;
}
case OPCODE_ADD_INT_LIT16:
case OPCODE_RSUB_INT:
case OPCODE_MUL_INT_LIT16:
case OPCODE_AND_INT_LIT16:
case OPCODE_OR_INT_LIT16:
case OPCODE_XOR_INT_LIT16:
case OPCODE_ADD_INT_LIT8:
case OPCODE_RSUB_INT_LIT8:
case OPCODE_MUL_INT_LIT8:
case OPCODE_AND_INT_LIT8:
case OPCODE_OR_INT_LIT8:
case OPCODE_XOR_INT_LIT8:
case OPCODE_SHL_INT_LIT8:
case OPCODE_SHR_INT_LIT8:
case OPCODE_USHR_INT_LIT8: {
refine_integer(current_state, insn->src(0));
set_integer(current_state, insn->dest());
break;
}
case OPCODE_DIV_INT_LIT16:
case OPCODE_REM_INT_LIT16:
case OPCODE_DIV_INT_LIT8:
case OPCODE_REM_INT_LIT8: {
refine_integer(current_state, insn->src(0));
set_integer(current_state, RESULT_REGISTER);
break;
}
case IOPCODE_INIT_CLASS: {
break;
}
}
// If the opcode does not set the RESULT_REGISTER, clear it.
if (!insn->has_move_result_any()) {
set_type(current_state, RESULT_REGISTER, TypeDomain::top());
current_state->reset_dex_type(RESULT_REGISTER);
}
}
void TypeInference::print(std::ostream& output) const {
for (cfg::Block* block : m_cfg.blocks()) {
for (auto& mie : InstructionIterable(block)) {
IRInstruction* insn = mie.insn;
auto it = m_type_envs.find(insn);
always_assert(it != m_type_envs.end());
output << SHOW(insn) << " -- " << it->second << std::endl;
}
}
}
void TypeInference::traceState(TypeEnvironment* state) const {
if (!traceEnabled(TYPE, 9)) {
return;
}
std::ostringstream out;
out << *state << std::endl;
TRACE(TYPE, 9, "%s", out.str().c_str());
}
void TypeInference::populate_type_environments() {
// We reserve enough space for the map in order to avoid repeated rehashing
// during the computation.
m_type_envs.reserve(m_cfg.blocks().size() * 16);
for (cfg::Block* block : m_cfg.blocks()) {
TypeEnvironment current_state = get_entry_state_at(block);
for (auto& mie : InstructionIterable(block)) {
IRInstruction* insn = mie.insn;
m_type_envs.emplace(insn, current_state);
analyze_instruction(insn, ¤t_state, block);
}
}
}
} // namespace type_inference