in runtime/verifier/method_verifier.cc [1966:3640]
bool MethodVerifier::CodeFlowVerifyInstruction(uint32_t* start_guess) {
// If we're doing FindLocksAtDexPc, check whether we're at the dex pc we care about.
// We want the state _before_ the instruction, for the case where the dex pc we're
// interested in is itself a monitor-enter instruction (which is a likely place
// for a thread to be suspended).
if (monitor_enter_dex_pcs_ != nullptr && work_insn_idx_ == interesting_dex_pc_) {
monitor_enter_dex_pcs_->clear(); // The new work line is more accurate than the previous one.
for (size_t i = 0; i < work_line_->GetMonitorEnterCount(); ++i) {
monitor_enter_dex_pcs_->push_back(work_line_->GetMonitorEnterDexPc(i));
}
}
/*
* Once we finish decoding the instruction, we need to figure out where
* we can go from here. There are three possible ways to transfer
* control to another statement:
*
* (1) Continue to the next instruction. Applies to all but
* unconditional branches, method returns, and exception throws.
* (2) Branch to one or more possible locations. Applies to branches
* and switch statements.
* (3) Exception handlers. Applies to any instruction that can
* throw an exception that is handled by an encompassing "try"
* block.
*
* We can also return, in which case there is no successor instruction
* from this point.
*
* The behavior can be determined from the opcode flags.
*/
const uint16_t* insns = code_item_->insns_ + work_insn_idx_;
const Instruction* inst = Instruction::At(insns);
int opcode_flags = Instruction::FlagsOf(inst->Opcode());
int32_t branch_target = 0;
bool just_set_result = false;
if (kDebugVerify) {
// Generate processing back trace to debug verifier
LogVerifyInfo() << "Processing " << inst->DumpString(dex_file_) << "\n"
<< work_line_->Dump(this) << "\n";
}
/*
* Make a copy of the previous register state. If the instruction
* can throw an exception, we will copy/merge this into the "catch"
* address rather than work_line, because we don't want the result
* from the "successful" code path (e.g. a check-cast that "improves"
* a type) to be visible to the exception handler.
*/
if ((opcode_flags & Instruction::kThrow) != 0 && CurrentInsnFlags()->IsInTry()) {
saved_line_->CopyFromLine(work_line_.get());
} else if (kIsDebugBuild) {
saved_line_->FillWithGarbage();
}
DCHECK(!have_pending_runtime_throw_failure_); // Per-instruction flag, should not be set here.
// We need to ensure the work line is consistent while performing validation. When we spot a
// peephole pattern we compute a new line for either the fallthrough instruction or the
// branch target.
RegisterLineArenaUniquePtr branch_line;
RegisterLineArenaUniquePtr fallthrough_line;
switch (inst->Opcode()) {
case Instruction::NOP:
/*
* A "pure" NOP has no effect on anything. Data tables start with
* a signature that looks like a NOP; if we see one of these in
* the course of executing code then we have a problem.
*/
if (inst->VRegA_10x() != 0) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "encountered data table in instruction stream";
}
break;
case Instruction::MOVE:
work_line_->CopyRegister1(this, inst->VRegA_12x(), inst->VRegB_12x(), kTypeCategory1nr);
break;
case Instruction::MOVE_FROM16:
work_line_->CopyRegister1(this, inst->VRegA_22x(), inst->VRegB_22x(), kTypeCategory1nr);
break;
case Instruction::MOVE_16:
work_line_->CopyRegister1(this, inst->VRegA_32x(), inst->VRegB_32x(), kTypeCategory1nr);
break;
case Instruction::MOVE_WIDE:
work_line_->CopyRegister2(this, inst->VRegA_12x(), inst->VRegB_12x());
break;
case Instruction::MOVE_WIDE_FROM16:
work_line_->CopyRegister2(this, inst->VRegA_22x(), inst->VRegB_22x());
break;
case Instruction::MOVE_WIDE_16:
work_line_->CopyRegister2(this, inst->VRegA_32x(), inst->VRegB_32x());
break;
case Instruction::MOVE_OBJECT:
work_line_->CopyRegister1(this, inst->VRegA_12x(), inst->VRegB_12x(), kTypeCategoryRef);
break;
case Instruction::MOVE_OBJECT_FROM16:
work_line_->CopyRegister1(this, inst->VRegA_22x(), inst->VRegB_22x(), kTypeCategoryRef);
break;
case Instruction::MOVE_OBJECT_16:
work_line_->CopyRegister1(this, inst->VRegA_32x(), inst->VRegB_32x(), kTypeCategoryRef);
break;
/*
* The move-result instructions copy data out of a "pseudo-register"
* with the results from the last method invocation. In practice we
* might want to hold the result in an actual CPU register, so the
* Dalvik spec requires that these only appear immediately after an
* invoke or filled-new-array.
*
* These calls invalidate the "result" register. (This is now
* redundant with the reset done below, but it can make the debug info
* easier to read in some cases.)
*/
case Instruction::MOVE_RESULT:
work_line_->CopyResultRegister1(this, inst->VRegA_11x(), false);
break;
case Instruction::MOVE_RESULT_WIDE:
work_line_->CopyResultRegister2(this, inst->VRegA_11x());
break;
case Instruction::MOVE_RESULT_OBJECT:
work_line_->CopyResultRegister1(this, inst->VRegA_11x(), true);
break;
case Instruction::MOVE_EXCEPTION: {
// We do not allow MOVE_EXCEPTION as the first instruction in a method. This is a simple case
// where one entrypoint to the catch block is not actually an exception path.
if (work_insn_idx_ == 0) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "move-exception at pc 0x0";
break;
}
/*
* This statement can only appear as the first instruction in an exception handler. We verify
* that as part of extracting the exception type from the catch block list.
*/
const RegType& res_type = GetCaughtExceptionType();
work_line_->SetRegisterType<LockOp::kClear>(this, inst->VRegA_11x(), res_type);
break;
}
case Instruction::RETURN_VOID:
if (!IsInstanceConstructor() || work_line_->CheckConstructorReturn(this)) {
if (!GetMethodReturnType().IsConflict()) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "return-void not expected";
}
}
break;
case Instruction::RETURN:
if (!IsInstanceConstructor() || work_line_->CheckConstructorReturn(this)) {
/* check the method signature */
const RegType& return_type = GetMethodReturnType();
if (!return_type.IsCategory1Types()) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "unexpected non-category 1 return type "
<< return_type;
} else {
// Compilers may generate synthetic functions that write byte values into boolean fields.
// Also, it may use integer values for boolean, byte, short, and character return types.
const uint32_t vregA = inst->VRegA_11x();
const RegType& src_type = work_line_->GetRegisterType(this, vregA);
bool use_src = ((return_type.IsBoolean() && src_type.IsByte()) ||
((return_type.IsBoolean() || return_type.IsByte() ||
return_type.IsShort() || return_type.IsChar()) &&
src_type.IsInteger()));
/* check the register contents */
bool success =
work_line_->VerifyRegisterType(this, vregA, use_src ? src_type : return_type);
if (!success) {
AppendToLastFailMessage(StringPrintf(" return-1nr on invalid register v%d", vregA));
}
}
}
break;
case Instruction::RETURN_WIDE:
if (!IsInstanceConstructor() || work_line_->CheckConstructorReturn(this)) {
/* check the method signature */
const RegType& return_type = GetMethodReturnType();
if (!return_type.IsCategory2Types()) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "return-wide not expected";
} else {
/* check the register contents */
const uint32_t vregA = inst->VRegA_11x();
bool success = work_line_->VerifyRegisterType(this, vregA, return_type);
if (!success) {
AppendToLastFailMessage(StringPrintf(" return-wide on invalid register v%d", vregA));
}
}
}
break;
case Instruction::RETURN_OBJECT:
if (!IsInstanceConstructor() || work_line_->CheckConstructorReturn(this)) {
const RegType& return_type = GetMethodReturnType();
if (!return_type.IsReferenceTypes()) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "return-object not expected";
} else {
/* return_type is the *expected* return type, not register value */
DCHECK(!return_type.IsZero());
DCHECK(!return_type.IsUninitializedReference());
const uint32_t vregA = inst->VRegA_11x();
const RegType& reg_type = work_line_->GetRegisterType(this, vregA);
// Disallow returning undefined, conflict & uninitialized values and verify that the
// reference in vAA is an instance of the "return_type."
if (reg_type.IsUndefined()) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "returning undefined register";
} else if (reg_type.IsConflict()) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "returning register with conflict";
} else if (reg_type.IsUninitializedTypes()) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "returning uninitialized object '"
<< reg_type << "'";
} else if (!reg_type.IsReferenceTypes()) {
// We really do expect a reference here.
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "return-object returns a non-reference type "
<< reg_type;
} else if (!return_type.IsAssignableFrom(reg_type)) {
if (reg_type.IsUnresolvedTypes() || return_type.IsUnresolvedTypes()) {
Fail(VERIFY_ERROR_NO_CLASS) << " can't resolve returned type '" << return_type
<< "' or '" << reg_type << "'";
} else {
bool soft_error = false;
// Check whether arrays are involved. They will show a valid class status, even
// if their components are erroneous.
if (reg_type.IsArrayTypes() && return_type.IsArrayTypes()) {
return_type.CanAssignArray(reg_type, reg_types_, class_loader_, &soft_error);
if (soft_error) {
Fail(VERIFY_ERROR_BAD_CLASS_SOFT) << "array with erroneous component type: "
<< reg_type << " vs " << return_type;
}
}
if (!soft_error) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "returning '" << reg_type
<< "', but expected from declaration '" << return_type << "'";
}
}
}
}
}
break;
/* could be boolean, int, float, or a null reference */
case Instruction::CONST_4: {
int32_t val = static_cast<int32_t>(inst->VRegB_11n() << 28) >> 28;
work_line_->SetRegisterType<LockOp::kClear>(
this, inst->VRegA_11n(), DetermineCat1Constant(val, need_precise_constants_));
break;
}
case Instruction::CONST_16: {
int16_t val = static_cast<int16_t>(inst->VRegB_21s());
work_line_->SetRegisterType<LockOp::kClear>(
this, inst->VRegA_21s(), DetermineCat1Constant(val, need_precise_constants_));
break;
}
case Instruction::CONST: {
int32_t val = inst->VRegB_31i();
work_line_->SetRegisterType<LockOp::kClear>(
this, inst->VRegA_31i(), DetermineCat1Constant(val, need_precise_constants_));
break;
}
case Instruction::CONST_HIGH16: {
int32_t val = static_cast<int32_t>(inst->VRegB_21h() << 16);
work_line_->SetRegisterType<LockOp::kClear>(
this, inst->VRegA_21h(), DetermineCat1Constant(val, need_precise_constants_));
break;
}
/* could be long or double; resolved upon use */
case Instruction::CONST_WIDE_16: {
int64_t val = static_cast<int16_t>(inst->VRegB_21s());
const RegType& lo = reg_types_.FromCat2ConstLo(static_cast<int32_t>(val), true);
const RegType& hi = reg_types_.FromCat2ConstHi(static_cast<int32_t>(val >> 32), true);
work_line_->SetRegisterTypeWide(this, inst->VRegA_21s(), lo, hi);
break;
}
case Instruction::CONST_WIDE_32: {
int64_t val = static_cast<int32_t>(inst->VRegB_31i());
const RegType& lo = reg_types_.FromCat2ConstLo(static_cast<int32_t>(val), true);
const RegType& hi = reg_types_.FromCat2ConstHi(static_cast<int32_t>(val >> 32), true);
work_line_->SetRegisterTypeWide(this, inst->VRegA_31i(), lo, hi);
break;
}
case Instruction::CONST_WIDE: {
int64_t val = inst->VRegB_51l();
const RegType& lo = reg_types_.FromCat2ConstLo(static_cast<int32_t>(val), true);
const RegType& hi = reg_types_.FromCat2ConstHi(static_cast<int32_t>(val >> 32), true);
work_line_->SetRegisterTypeWide(this, inst->VRegA_51l(), lo, hi);
break;
}
case Instruction::CONST_WIDE_HIGH16: {
int64_t val = static_cast<uint64_t>(inst->VRegB_21h()) << 48;
const RegType& lo = reg_types_.FromCat2ConstLo(static_cast<int32_t>(val), true);
const RegType& hi = reg_types_.FromCat2ConstHi(static_cast<int32_t>(val >> 32), true);
work_line_->SetRegisterTypeWide(this, inst->VRegA_21h(), lo, hi);
break;
}
case Instruction::CONST_STRING:
work_line_->SetRegisterType<LockOp::kClear>(
this, inst->VRegA_21c(), reg_types_.JavaLangString());
break;
case Instruction::CONST_STRING_JUMBO:
work_line_->SetRegisterType<LockOp::kClear>(
this, inst->VRegA_31c(), reg_types_.JavaLangString());
break;
case Instruction::CONST_CLASS: {
// Get type from instruction if unresolved then we need an access check
// TODO: check Compiler::CanAccessTypeWithoutChecks returns false when res_type is unresolved
const RegType& res_type = ResolveClassAndCheckAccess(inst->VRegB_21c());
// Register holds class, ie its type is class, on error it will hold Conflict.
work_line_->SetRegisterType<LockOp::kClear>(
this, inst->VRegA_21c(), res_type.IsConflict() ? res_type
: reg_types_.JavaLangClass());
break;
}
case Instruction::MONITOR_ENTER:
work_line_->PushMonitor(this, inst->VRegA_11x(), work_insn_idx_);
// Check whether the previous instruction is a move-object with vAA as a source, creating
// untracked lock aliasing.
if (0 != work_insn_idx_ && !GetInstructionFlags(work_insn_idx_).IsBranchTarget()) {
uint32_t prev_idx = work_insn_idx_ - 1;
while (0 != prev_idx && !GetInstructionFlags(prev_idx).IsOpcode()) {
prev_idx--;
}
const Instruction* prev_inst = Instruction::At(code_item_->insns_ + prev_idx);
switch (prev_inst->Opcode()) {
case Instruction::MOVE_OBJECT:
case Instruction::MOVE_OBJECT_16:
case Instruction::MOVE_OBJECT_FROM16:
if (prev_inst->VRegB() == inst->VRegA_11x()) {
// Redo the copy. This won't change the register types, but update the lock status
// for the aliased register.
work_line_->CopyRegister1(this,
prev_inst->VRegA(),
prev_inst->VRegB(),
kTypeCategoryRef);
}
break;
default: // Other instruction types ignored.
break;
}
}
break;
case Instruction::MONITOR_EXIT:
/*
* monitor-exit instructions are odd. They can throw exceptions,
* but when they do they act as if they succeeded and the PC is
* pointing to the following instruction. (This behavior goes back
* to the need to handle asynchronous exceptions, a now-deprecated
* feature that Dalvik doesn't support.)
*
* In practice we don't need to worry about this. The only
* exceptions that can be thrown from monitor-exit are for a
* null reference and -exit without a matching -enter. If the
* structured locking checks are working, the former would have
* failed on the -enter instruction, and the latter is impossible.
*
* This is fortunate, because issue 3221411 prevents us from
* chasing the "can throw" path when monitor verification is
* enabled. If we can fully verify the locking we can ignore
* some catch blocks (which will show up as "dead" code when
* we skip them here); if we can't, then the code path could be
* "live" so we still need to check it.
*/
opcode_flags &= ~Instruction::kThrow;
work_line_->PopMonitor(this, inst->VRegA_11x());
break;
case Instruction::CHECK_CAST:
case Instruction::INSTANCE_OF: {
/*
* If this instruction succeeds, we will "downcast" register vA to the type in vB. (This
* could be a "upcast" -- not expected, so we don't try to address it.)
*
* If it fails, an exception is thrown, which we deal with later by ignoring the update to
* dec_insn.vA when branching to a handler.
*/
const bool is_checkcast = (inst->Opcode() == Instruction::CHECK_CAST);
const uint32_t type_idx = (is_checkcast) ? inst->VRegB_21c() : inst->VRegC_22c();
const RegType& res_type = ResolveClassAndCheckAccess(type_idx);
if (res_type.IsConflict()) {
// If this is a primitive type, fail HARD.
mirror::Class* klass = dex_cache_->GetResolvedType(type_idx);
if (klass != nullptr && klass->IsPrimitive()) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "using primitive type "
<< dex_file_->StringByTypeIdx(type_idx) << " in instanceof in "
<< GetDeclaringClass();
break;
}
DCHECK_NE(failures_.size(), 0U);
if (!is_checkcast) {
work_line_->SetRegisterType<LockOp::kClear>(this,
inst->VRegA_22c(),
reg_types_.Boolean());
}
break; // bad class
}
// TODO: check Compiler::CanAccessTypeWithoutChecks returns false when res_type is unresolved
uint32_t orig_type_reg = (is_checkcast) ? inst->VRegA_21c() : inst->VRegB_22c();
const RegType& orig_type = work_line_->GetRegisterType(this, orig_type_reg);
if (!res_type.IsNonZeroReferenceTypes()) {
if (is_checkcast) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "check-cast on unexpected class " << res_type;
} else {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "instance-of on unexpected class " << res_type;
}
} else if (!orig_type.IsReferenceTypes()) {
if (is_checkcast) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "check-cast on non-reference in v" << orig_type_reg;
} else {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "instance-of on non-reference in v" << orig_type_reg;
}
} else if (orig_type.IsUninitializedTypes()) {
if (is_checkcast) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "check-cast on uninitialized reference in v"
<< orig_type_reg;
} else {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "instance-of on uninitialized reference in v"
<< orig_type_reg;
}
} else {
if (is_checkcast) {
work_line_->SetRegisterType<LockOp::kKeep>(this, inst->VRegA_21c(), res_type);
} else {
work_line_->SetRegisterType<LockOp::kClear>(this,
inst->VRegA_22c(),
reg_types_.Boolean());
}
}
break;
}
case Instruction::ARRAY_LENGTH: {
const RegType& res_type = work_line_->GetRegisterType(this, inst->VRegB_12x());
if (res_type.IsReferenceTypes()) {
if (!res_type.IsArrayTypes() && !res_type.IsZero()) { // ie not an array or null
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "array-length on non-array " << res_type;
} else {
work_line_->SetRegisterType<LockOp::kClear>(this,
inst->VRegA_12x(),
reg_types_.Integer());
}
} else {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "array-length on non-array " << res_type;
}
break;
}
case Instruction::NEW_INSTANCE: {
const RegType& res_type = ResolveClassAndCheckAccess(inst->VRegB_21c());
if (res_type.IsConflict()) {
DCHECK_NE(failures_.size(), 0U);
break; // bad class
}
// TODO: check Compiler::CanAccessTypeWithoutChecks returns false when res_type is unresolved
// can't create an instance of an interface or abstract class */
if (!res_type.IsInstantiableTypes()) {
Fail(VERIFY_ERROR_INSTANTIATION)
<< "new-instance on primitive, interface or abstract class" << res_type;
// Soft failure so carry on to set register type.
}
const RegType& uninit_type = reg_types_.Uninitialized(res_type, work_insn_idx_);
// Any registers holding previous allocations from this address that have not yet been
// initialized must be marked invalid.
work_line_->MarkUninitRefsAsInvalid(this, uninit_type);
// add the new uninitialized reference to the register state
work_line_->SetRegisterType<LockOp::kClear>(this, inst->VRegA_21c(), uninit_type);
break;
}
case Instruction::NEW_ARRAY:
VerifyNewArray(inst, false, false);
break;
case Instruction::FILLED_NEW_ARRAY:
VerifyNewArray(inst, true, false);
just_set_result = true; // Filled new array sets result register
break;
case Instruction::FILLED_NEW_ARRAY_RANGE:
VerifyNewArray(inst, true, true);
just_set_result = true; // Filled new array range sets result register
break;
case Instruction::CMPL_FLOAT:
case Instruction::CMPG_FLOAT:
if (!work_line_->VerifyRegisterType(this, inst->VRegB_23x(), reg_types_.Float())) {
break;
}
if (!work_line_->VerifyRegisterType(this, inst->VRegC_23x(), reg_types_.Float())) {
break;
}
work_line_->SetRegisterType<LockOp::kClear>(this, inst->VRegA_23x(), reg_types_.Integer());
break;
case Instruction::CMPL_DOUBLE:
case Instruction::CMPG_DOUBLE:
if (!work_line_->VerifyRegisterTypeWide(this, inst->VRegB_23x(), reg_types_.DoubleLo(),
reg_types_.DoubleHi())) {
break;
}
if (!work_line_->VerifyRegisterTypeWide(this, inst->VRegC_23x(), reg_types_.DoubleLo(),
reg_types_.DoubleHi())) {
break;
}
work_line_->SetRegisterType<LockOp::kClear>(this, inst->VRegA_23x(), reg_types_.Integer());
break;
case Instruction::CMP_LONG:
if (!work_line_->VerifyRegisterTypeWide(this, inst->VRegB_23x(), reg_types_.LongLo(),
reg_types_.LongHi())) {
break;
}
if (!work_line_->VerifyRegisterTypeWide(this, inst->VRegC_23x(), reg_types_.LongLo(),
reg_types_.LongHi())) {
break;
}
work_line_->SetRegisterType<LockOp::kClear>(this, inst->VRegA_23x(), reg_types_.Integer());
break;
case Instruction::THROW: {
const RegType& res_type = work_line_->GetRegisterType(this, inst->VRegA_11x());
if (!reg_types_.JavaLangThrowable(false).IsAssignableFrom(res_type)) {
if (res_type.IsUninitializedTypes()) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "thrown exception not initialized";
} else if (!res_type.IsReferenceTypes()) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "thrown value of non-reference type " << res_type;
} else {
Fail(res_type.IsUnresolvedTypes() ? VERIFY_ERROR_NO_CLASS : VERIFY_ERROR_BAD_CLASS_SOFT)
<< "thrown class " << res_type << " not instanceof Throwable";
}
}
break;
}
case Instruction::GOTO:
case Instruction::GOTO_16:
case Instruction::GOTO_32:
/* no effect on or use of registers */
break;
case Instruction::PACKED_SWITCH:
case Instruction::SPARSE_SWITCH:
/* verify that vAA is an integer, or can be converted to one */
work_line_->VerifyRegisterType(this, inst->VRegA_31t(), reg_types_.Integer());
break;
case Instruction::FILL_ARRAY_DATA: {
/* Similar to the verification done for APUT */
const RegType& array_type = work_line_->GetRegisterType(this, inst->VRegA_31t());
/* array_type can be null if the reg type is Zero */
if (!array_type.IsZero()) {
if (!array_type.IsArrayTypes()) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "invalid fill-array-data with array type "
<< array_type;
} else if (array_type.IsUnresolvedTypes()) {
// If it's an unresolved array type, it must be non-primitive.
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "invalid fill-array-data for array of type "
<< array_type;
} else {
const RegType& component_type = reg_types_.GetComponentType(array_type, GetClassLoader());
DCHECK(!component_type.IsConflict());
if (component_type.IsNonZeroReferenceTypes()) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "invalid fill-array-data with component type "
<< component_type;
} else {
// Now verify if the element width in the table matches the element width declared in
// the array
const uint16_t* array_data =
insns + (insns[1] | (static_cast<int32_t>(insns[2]) << 16));
if (array_data[0] != Instruction::kArrayDataSignature) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "invalid magic for array-data";
} else {
size_t elem_width = Primitive::ComponentSize(component_type.GetPrimitiveType());
// Since we don't compress the data in Dex, expect to see equal width of data stored
// in the table and expected from the array class.
if (array_data[1] != elem_width) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "array-data size mismatch (" << array_data[1]
<< " vs " << elem_width << ")";
}
}
}
}
}
break;
}
case Instruction::IF_EQ:
case Instruction::IF_NE: {
const RegType& reg_type1 = work_line_->GetRegisterType(this, inst->VRegA_22t());
const RegType& reg_type2 = work_line_->GetRegisterType(this, inst->VRegB_22t());
bool mismatch = false;
if (reg_type1.IsZero()) { // zero then integral or reference expected
mismatch = !reg_type2.IsReferenceTypes() && !reg_type2.IsIntegralTypes();
} else if (reg_type1.IsReferenceTypes()) { // both references?
mismatch = !reg_type2.IsReferenceTypes();
} else { // both integral?
mismatch = !reg_type1.IsIntegralTypes() || !reg_type2.IsIntegralTypes();
}
if (mismatch) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "args to if-eq/if-ne (" << reg_type1 << ","
<< reg_type2 << ") must both be references or integral";
}
break;
}
case Instruction::IF_LT:
case Instruction::IF_GE:
case Instruction::IF_GT:
case Instruction::IF_LE: {
const RegType& reg_type1 = work_line_->GetRegisterType(this, inst->VRegA_22t());
const RegType& reg_type2 = work_line_->GetRegisterType(this, inst->VRegB_22t());
if (!reg_type1.IsIntegralTypes() || !reg_type2.IsIntegralTypes()) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "args to 'if' (" << reg_type1 << ","
<< reg_type2 << ") must be integral";
}
break;
}
case Instruction::IF_EQZ:
case Instruction::IF_NEZ: {
const RegType& reg_type = work_line_->GetRegisterType(this, inst->VRegA_21t());
if (!reg_type.IsReferenceTypes() && !reg_type.IsIntegralTypes()) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "type " << reg_type
<< " unexpected as arg to if-eqz/if-nez";
}
// Find previous instruction - its existence is a precondition to peephole optimization.
uint32_t instance_of_idx = 0;
if (0 != work_insn_idx_) {
instance_of_idx = work_insn_idx_ - 1;
while (0 != instance_of_idx && !GetInstructionFlags(instance_of_idx).IsOpcode()) {
instance_of_idx--;
}
if (FailOrAbort(this, GetInstructionFlags(instance_of_idx).IsOpcode(),
"Unable to get previous instruction of if-eqz/if-nez for work index ",
work_insn_idx_)) {
break;
}
} else {
break;
}
const Instruction* instance_of_inst = Instruction::At(code_item_->insns_ + instance_of_idx);
/* Check for peep-hole pattern of:
* ...;
* instance-of vX, vY, T;
* ifXXX vX, label ;
* ...;
* label:
* ...;
* and sharpen the type of vY to be type T.
* Note, this pattern can't be if:
* - if there are other branches to this branch,
* - when vX == vY.
*/
if (!CurrentInsnFlags()->IsBranchTarget() &&
(Instruction::INSTANCE_OF == instance_of_inst->Opcode()) &&
(inst->VRegA_21t() == instance_of_inst->VRegA_22c()) &&
(instance_of_inst->VRegA_22c() != instance_of_inst->VRegB_22c())) {
// Check the type of the instance-of is different than that of registers type, as if they
// are the same there is no work to be done here. Check that the conversion is not to or
// from an unresolved type as type information is imprecise. If the instance-of is to an
// interface then ignore the type information as interfaces can only be treated as Objects
// and we don't want to disallow field and other operations on the object. If the value
// being instance-of checked against is known null (zero) then allow the optimization as
// we didn't have type information. If the merge of the instance-of type with the original
// type is assignable to the original then allow optimization. This check is performed to
// ensure that subsequent merges don't lose type information - such as becoming an
// interface from a class that would lose information relevant to field checks.
const RegType& orig_type = work_line_->GetRegisterType(this, instance_of_inst->VRegB_22c());
const RegType& cast_type = ResolveClassAndCheckAccess(instance_of_inst->VRegC_22c());
if (!orig_type.Equals(cast_type) &&
!cast_type.IsUnresolvedTypes() && !orig_type.IsUnresolvedTypes() &&
cast_type.HasClass() && // Could be conflict type, make sure it has a class.
!cast_type.GetClass()->IsInterface() &&
(orig_type.IsZero() ||
orig_type.IsStrictlyAssignableFrom(cast_type.Merge(orig_type, ®_types_)))) {
RegisterLine* update_line = RegisterLine::Create(code_item_->registers_size_, this);
if (inst->Opcode() == Instruction::IF_EQZ) {
fallthrough_line.reset(update_line);
} else {
branch_line.reset(update_line);
}
update_line->CopyFromLine(work_line_.get());
update_line->SetRegisterType<LockOp::kKeep>(this,
instance_of_inst->VRegB_22c(),
cast_type);
if (!GetInstructionFlags(instance_of_idx).IsBranchTarget() && 0 != instance_of_idx) {
// See if instance-of was preceded by a move-object operation, common due to the small
// register encoding space of instance-of, and propagate type information to the source
// of the move-object.
uint32_t move_idx = instance_of_idx - 1;
while (0 != move_idx && !GetInstructionFlags(move_idx).IsOpcode()) {
move_idx--;
}
if (FailOrAbort(this, GetInstructionFlags(move_idx).IsOpcode(),
"Unable to get previous instruction of if-eqz/if-nez for work index ",
work_insn_idx_)) {
break;
}
const Instruction* move_inst = Instruction::At(code_item_->insns_ + move_idx);
switch (move_inst->Opcode()) {
case Instruction::MOVE_OBJECT:
if (move_inst->VRegA_12x() == instance_of_inst->VRegB_22c()) {
update_line->SetRegisterType<LockOp::kKeep>(this,
move_inst->VRegB_12x(),
cast_type);
}
break;
case Instruction::MOVE_OBJECT_FROM16:
if (move_inst->VRegA_22x() == instance_of_inst->VRegB_22c()) {
update_line->SetRegisterType<LockOp::kKeep>(this,
move_inst->VRegB_22x(),
cast_type);
}
break;
case Instruction::MOVE_OBJECT_16:
if (move_inst->VRegA_32x() == instance_of_inst->VRegB_22c()) {
update_line->SetRegisterType<LockOp::kKeep>(this,
move_inst->VRegB_32x(),
cast_type);
}
break;
default:
break;
}
}
}
}
break;
}
case Instruction::IF_LTZ:
case Instruction::IF_GEZ:
case Instruction::IF_GTZ:
case Instruction::IF_LEZ: {
const RegType& reg_type = work_line_->GetRegisterType(this, inst->VRegA_21t());
if (!reg_type.IsIntegralTypes()) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "type " << reg_type
<< " unexpected as arg to if-ltz/if-gez/if-gtz/if-lez";
}
break;
}
case Instruction::AGET_BOOLEAN:
VerifyAGet(inst, reg_types_.Boolean(), true);
break;
case Instruction::AGET_BYTE:
VerifyAGet(inst, reg_types_.Byte(), true);
break;
case Instruction::AGET_CHAR:
VerifyAGet(inst, reg_types_.Char(), true);
break;
case Instruction::AGET_SHORT:
VerifyAGet(inst, reg_types_.Short(), true);
break;
case Instruction::AGET:
VerifyAGet(inst, reg_types_.Integer(), true);
break;
case Instruction::AGET_WIDE:
VerifyAGet(inst, reg_types_.LongLo(), true);
break;
case Instruction::AGET_OBJECT:
VerifyAGet(inst, reg_types_.JavaLangObject(false), false);
break;
case Instruction::APUT_BOOLEAN:
VerifyAPut(inst, reg_types_.Boolean(), true);
break;
case Instruction::APUT_BYTE:
VerifyAPut(inst, reg_types_.Byte(), true);
break;
case Instruction::APUT_CHAR:
VerifyAPut(inst, reg_types_.Char(), true);
break;
case Instruction::APUT_SHORT:
VerifyAPut(inst, reg_types_.Short(), true);
break;
case Instruction::APUT:
VerifyAPut(inst, reg_types_.Integer(), true);
break;
case Instruction::APUT_WIDE:
VerifyAPut(inst, reg_types_.LongLo(), true);
break;
case Instruction::APUT_OBJECT:
VerifyAPut(inst, reg_types_.JavaLangObject(false), false);
break;
case Instruction::IGET_BOOLEAN:
VerifyISFieldAccess<FieldAccessType::kAccGet>(inst, reg_types_.Boolean(), true, false);
break;
case Instruction::IGET_BYTE:
VerifyISFieldAccess<FieldAccessType::kAccGet>(inst, reg_types_.Byte(), true, false);
break;
case Instruction::IGET_CHAR:
VerifyISFieldAccess<FieldAccessType::kAccGet>(inst, reg_types_.Char(), true, false);
break;
case Instruction::IGET_SHORT:
VerifyISFieldAccess<FieldAccessType::kAccGet>(inst, reg_types_.Short(), true, false);
break;
case Instruction::IGET:
VerifyISFieldAccess<FieldAccessType::kAccGet>(inst, reg_types_.Integer(), true, false);
break;
case Instruction::IGET_WIDE:
VerifyISFieldAccess<FieldAccessType::kAccGet>(inst, reg_types_.LongLo(), true, false);
break;
case Instruction::IGET_OBJECT:
VerifyISFieldAccess<FieldAccessType::kAccGet>(inst, reg_types_.JavaLangObject(false), false,
false);
break;
case Instruction::IPUT_BOOLEAN:
VerifyISFieldAccess<FieldAccessType::kAccPut>(inst, reg_types_.Boolean(), true, false);
break;
case Instruction::IPUT_BYTE:
VerifyISFieldAccess<FieldAccessType::kAccPut>(inst, reg_types_.Byte(), true, false);
break;
case Instruction::IPUT_CHAR:
VerifyISFieldAccess<FieldAccessType::kAccPut>(inst, reg_types_.Char(), true, false);
break;
case Instruction::IPUT_SHORT:
VerifyISFieldAccess<FieldAccessType::kAccPut>(inst, reg_types_.Short(), true, false);
break;
case Instruction::IPUT:
VerifyISFieldAccess<FieldAccessType::kAccPut>(inst, reg_types_.Integer(), true, false);
break;
case Instruction::IPUT_WIDE:
VerifyISFieldAccess<FieldAccessType::kAccPut>(inst, reg_types_.LongLo(), true, false);
break;
case Instruction::IPUT_OBJECT:
VerifyISFieldAccess<FieldAccessType::kAccPut>(inst, reg_types_.JavaLangObject(false), false,
false);
break;
case Instruction::SGET_BOOLEAN:
VerifyISFieldAccess<FieldAccessType::kAccGet>(inst, reg_types_.Boolean(), true, true);
break;
case Instruction::SGET_BYTE:
VerifyISFieldAccess<FieldAccessType::kAccGet>(inst, reg_types_.Byte(), true, true);
break;
case Instruction::SGET_CHAR:
VerifyISFieldAccess<FieldAccessType::kAccGet>(inst, reg_types_.Char(), true, true);
break;
case Instruction::SGET_SHORT:
VerifyISFieldAccess<FieldAccessType::kAccGet>(inst, reg_types_.Short(), true, true);
break;
case Instruction::SGET:
VerifyISFieldAccess<FieldAccessType::kAccGet>(inst, reg_types_.Integer(), true, true);
break;
case Instruction::SGET_WIDE:
VerifyISFieldAccess<FieldAccessType::kAccGet>(inst, reg_types_.LongLo(), true, true);
break;
case Instruction::SGET_OBJECT:
VerifyISFieldAccess<FieldAccessType::kAccGet>(inst, reg_types_.JavaLangObject(false), false,
true);
break;
case Instruction::SPUT_BOOLEAN:
VerifyISFieldAccess<FieldAccessType::kAccPut>(inst, reg_types_.Boolean(), true, true);
break;
case Instruction::SPUT_BYTE:
VerifyISFieldAccess<FieldAccessType::kAccPut>(inst, reg_types_.Byte(), true, true);
break;
case Instruction::SPUT_CHAR:
VerifyISFieldAccess<FieldAccessType::kAccPut>(inst, reg_types_.Char(), true, true);
break;
case Instruction::SPUT_SHORT:
VerifyISFieldAccess<FieldAccessType::kAccPut>(inst, reg_types_.Short(), true, true);
break;
case Instruction::SPUT:
VerifyISFieldAccess<FieldAccessType::kAccPut>(inst, reg_types_.Integer(), true, true);
break;
case Instruction::SPUT_WIDE:
VerifyISFieldAccess<FieldAccessType::kAccPut>(inst, reg_types_.LongLo(), true, true);
break;
case Instruction::SPUT_OBJECT:
VerifyISFieldAccess<FieldAccessType::kAccPut>(inst, reg_types_.JavaLangObject(false), false,
true);
break;
case Instruction::INVOKE_VIRTUAL:
case Instruction::INVOKE_VIRTUAL_RANGE:
case Instruction::INVOKE_SUPER:
case Instruction::INVOKE_SUPER_RANGE: {
bool is_range = (inst->Opcode() == Instruction::INVOKE_VIRTUAL_RANGE ||
inst->Opcode() == Instruction::INVOKE_SUPER_RANGE);
bool is_super = (inst->Opcode() == Instruction::INVOKE_SUPER ||
inst->Opcode() == Instruction::INVOKE_SUPER_RANGE);
MethodType type = is_super ? METHOD_SUPER : METHOD_VIRTUAL;
ArtMethod* called_method = VerifyInvocationArgs(inst, type, is_range);
const RegType* return_type = nullptr;
if (called_method != nullptr) {
size_t pointer_size = Runtime::Current()->GetClassLinker()->GetImagePointerSize();
mirror::Class* return_type_class = called_method->GetReturnType(can_load_classes_,
pointer_size);
if (return_type_class != nullptr) {
return_type = &FromClass(called_method->GetReturnTypeDescriptor(),
return_type_class,
return_type_class->CannotBeAssignedFromOtherTypes());
} else {
DCHECK(!can_load_classes_ || self_->IsExceptionPending());
self_->ClearException();
}
}
if (return_type == nullptr) {
uint32_t method_idx = (is_range) ? inst->VRegB_3rc() : inst->VRegB_35c();
const DexFile::MethodId& method_id = dex_file_->GetMethodId(method_idx);
uint32_t return_type_idx = dex_file_->GetProtoId(method_id.proto_idx_).return_type_idx_;
const char* descriptor = dex_file_->StringByTypeIdx(return_type_idx);
return_type = ®_types_.FromDescriptor(GetClassLoader(), descriptor, false);
}
if (!return_type->IsLowHalf()) {
work_line_->SetResultRegisterType(this, *return_type);
} else {
work_line_->SetResultRegisterTypeWide(*return_type, return_type->HighHalf(®_types_));
}
just_set_result = true;
break;
}
case Instruction::INVOKE_DIRECT:
case Instruction::INVOKE_DIRECT_RANGE: {
bool is_range = (inst->Opcode() == Instruction::INVOKE_DIRECT_RANGE);
ArtMethod* called_method = VerifyInvocationArgs(inst, METHOD_DIRECT, is_range);
const char* return_type_descriptor;
bool is_constructor;
const RegType* return_type = nullptr;
if (called_method == nullptr) {
uint32_t method_idx = (is_range) ? inst->VRegB_3rc() : inst->VRegB_35c();
const DexFile::MethodId& method_id = dex_file_->GetMethodId(method_idx);
is_constructor = strcmp("<init>", dex_file_->StringDataByIdx(method_id.name_idx_)) == 0;
uint32_t return_type_idx = dex_file_->GetProtoId(method_id.proto_idx_).return_type_idx_;
return_type_descriptor = dex_file_->StringByTypeIdx(return_type_idx);
} else {
is_constructor = called_method->IsConstructor();
return_type_descriptor = called_method->GetReturnTypeDescriptor();
size_t pointer_size = Runtime::Current()->GetClassLinker()->GetImagePointerSize();
mirror::Class* return_type_class = called_method->GetReturnType(can_load_classes_,
pointer_size);
if (return_type_class != nullptr) {
return_type = &FromClass(return_type_descriptor,
return_type_class,
return_type_class->CannotBeAssignedFromOtherTypes());
} else {
DCHECK(!can_load_classes_ || self_->IsExceptionPending());
self_->ClearException();
}
}
if (is_constructor) {
/*
* Some additional checks when calling a constructor. We know from the invocation arg check
* that the "this" argument is an instance of called_method->klass. Now we further restrict
* that to require that called_method->klass is the same as this->klass or this->super,
* allowing the latter only if the "this" argument is the same as the "this" argument to
* this method (which implies that we're in a constructor ourselves).
*/
const RegType& this_type = work_line_->GetInvocationThis(this, inst, is_range);
if (this_type.IsConflict()) // failure.
break;
/* no null refs allowed (?) */
if (this_type.IsZero()) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "unable to initialize null ref";
break;
}
/* must be in same class or in superclass */
// const RegType& this_super_klass = this_type.GetSuperClass(®_types_);
// TODO: re-enable constructor type verification
// if (this_super_klass.IsConflict()) {
// Unknown super class, fail so we re-check at runtime.
// Fail(VERIFY_ERROR_BAD_CLASS_SOFT) << "super class unknown for '" << this_type << "'";
// break;
// }
/* arg must be an uninitialized reference */
if (!this_type.IsUninitializedTypes()) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "Expected initialization on uninitialized reference "
<< this_type;
break;
}
/*
* Replace the uninitialized reference with an initialized one. We need to do this for all
* registers that have the same object instance in them, not just the "this" register.
*/
work_line_->MarkRefsAsInitialized(this, this_type);
}
if (return_type == nullptr) {
return_type = ®_types_.FromDescriptor(GetClassLoader(), return_type_descriptor, false);
}
if (!return_type->IsLowHalf()) {
work_line_->SetResultRegisterType(this, *return_type);
} else {
work_line_->SetResultRegisterTypeWide(*return_type, return_type->HighHalf(®_types_));
}
just_set_result = true;
break;
}
case Instruction::INVOKE_STATIC:
case Instruction::INVOKE_STATIC_RANGE: {
bool is_range = (inst->Opcode() == Instruction::INVOKE_STATIC_RANGE);
ArtMethod* called_method = VerifyInvocationArgs(inst, METHOD_STATIC, is_range);
const char* descriptor;
if (called_method == nullptr) {
uint32_t method_idx = (is_range) ? inst->VRegB_3rc() : inst->VRegB_35c();
const DexFile::MethodId& method_id = dex_file_->GetMethodId(method_idx);
uint32_t return_type_idx = dex_file_->GetProtoId(method_id.proto_idx_).return_type_idx_;
descriptor = dex_file_->StringByTypeIdx(return_type_idx);
} else {
descriptor = called_method->GetReturnTypeDescriptor();
}
const RegType& return_type = reg_types_.FromDescriptor(GetClassLoader(), descriptor, false);
if (!return_type.IsLowHalf()) {
work_line_->SetResultRegisterType(this, return_type);
} else {
work_line_->SetResultRegisterTypeWide(return_type, return_type.HighHalf(®_types_));
}
just_set_result = true;
}
break;
case Instruction::INVOKE_INTERFACE:
case Instruction::INVOKE_INTERFACE_RANGE: {
bool is_range = (inst->Opcode() == Instruction::INVOKE_INTERFACE_RANGE);
ArtMethod* abs_method = VerifyInvocationArgs(inst, METHOD_INTERFACE, is_range);
if (abs_method != nullptr) {
mirror::Class* called_interface = abs_method->GetDeclaringClass();
if (!called_interface->IsInterface() && !called_interface->IsObjectClass()) {
Fail(VERIFY_ERROR_CLASS_CHANGE) << "expected interface class in invoke-interface '"
<< PrettyMethod(abs_method) << "'";
break;
}
}
/* Get the type of the "this" arg, which should either be a sub-interface of called
* interface or Object (see comments in RegType::JoinClass).
*/
const RegType& this_type = work_line_->GetInvocationThis(this, inst, is_range);
if (this_type.IsZero()) {
/* null pointer always passes (and always fails at runtime) */
} else {
if (this_type.IsUninitializedTypes()) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "interface call on uninitialized object "
<< this_type;
break;
}
// In the past we have tried to assert that "called_interface" is assignable
// from "this_type.GetClass()", however, as we do an imprecise Join
// (RegType::JoinClass) we don't have full information on what interfaces are
// implemented by "this_type". For example, two classes may implement the same
// interfaces and have a common parent that doesn't implement the interface. The
// join will set "this_type" to the parent class and a test that this implements
// the interface will incorrectly fail.
}
/*
* We don't have an object instance, so we can't find the concrete method. However, all of
* the type information is in the abstract method, so we're good.
*/
const char* descriptor;
if (abs_method == nullptr) {
uint32_t method_idx = (is_range) ? inst->VRegB_3rc() : inst->VRegB_35c();
const DexFile::MethodId& method_id = dex_file_->GetMethodId(method_idx);
uint32_t return_type_idx = dex_file_->GetProtoId(method_id.proto_idx_).return_type_idx_;
descriptor = dex_file_->StringByTypeIdx(return_type_idx);
} else {
descriptor = abs_method->GetReturnTypeDescriptor();
}
const RegType& return_type = reg_types_.FromDescriptor(GetClassLoader(), descriptor, false);
if (!return_type.IsLowHalf()) {
work_line_->SetResultRegisterType(this, return_type);
} else {
work_line_->SetResultRegisterTypeWide(return_type, return_type.HighHalf(®_types_));
}
just_set_result = true;
break;
}
case Instruction::NEG_INT:
case Instruction::NOT_INT:
work_line_->CheckUnaryOp(this, inst, reg_types_.Integer(), reg_types_.Integer());
break;
case Instruction::NEG_LONG:
case Instruction::NOT_LONG:
work_line_->CheckUnaryOpWide(this, inst, reg_types_.LongLo(), reg_types_.LongHi(),
reg_types_.LongLo(), reg_types_.LongHi());
break;
case Instruction::NEG_FLOAT:
work_line_->CheckUnaryOp(this, inst, reg_types_.Float(), reg_types_.Float());
break;
case Instruction::NEG_DOUBLE:
work_line_->CheckUnaryOpWide(this, inst, reg_types_.DoubleLo(), reg_types_.DoubleHi(),
reg_types_.DoubleLo(), reg_types_.DoubleHi());
break;
case Instruction::INT_TO_LONG:
work_line_->CheckUnaryOpToWide(this, inst, reg_types_.LongLo(), reg_types_.LongHi(),
reg_types_.Integer());
break;
case Instruction::INT_TO_FLOAT:
work_line_->CheckUnaryOp(this, inst, reg_types_.Float(), reg_types_.Integer());
break;
case Instruction::INT_TO_DOUBLE:
work_line_->CheckUnaryOpToWide(this, inst, reg_types_.DoubleLo(), reg_types_.DoubleHi(),
reg_types_.Integer());
break;
case Instruction::LONG_TO_INT:
work_line_->CheckUnaryOpFromWide(this, inst, reg_types_.Integer(),
reg_types_.LongLo(), reg_types_.LongHi());
break;
case Instruction::LONG_TO_FLOAT:
work_line_->CheckUnaryOpFromWide(this, inst, reg_types_.Float(),
reg_types_.LongLo(), reg_types_.LongHi());
break;
case Instruction::LONG_TO_DOUBLE:
work_line_->CheckUnaryOpWide(this, inst, reg_types_.DoubleLo(), reg_types_.DoubleHi(),
reg_types_.LongLo(), reg_types_.LongHi());
break;
case Instruction::FLOAT_TO_INT:
work_line_->CheckUnaryOp(this, inst, reg_types_.Integer(), reg_types_.Float());
break;
case Instruction::FLOAT_TO_LONG:
work_line_->CheckUnaryOpToWide(this, inst, reg_types_.LongLo(), reg_types_.LongHi(),
reg_types_.Float());
break;
case Instruction::FLOAT_TO_DOUBLE:
work_line_->CheckUnaryOpToWide(this, inst, reg_types_.DoubleLo(), reg_types_.DoubleHi(),
reg_types_.Float());
break;
case Instruction::DOUBLE_TO_INT:
work_line_->CheckUnaryOpFromWide(this, inst, reg_types_.Integer(),
reg_types_.DoubleLo(), reg_types_.DoubleHi());
break;
case Instruction::DOUBLE_TO_LONG:
work_line_->CheckUnaryOpWide(this, inst, reg_types_.LongLo(), reg_types_.LongHi(),
reg_types_.DoubleLo(), reg_types_.DoubleHi());
break;
case Instruction::DOUBLE_TO_FLOAT:
work_line_->CheckUnaryOpFromWide(this, inst, reg_types_.Float(),
reg_types_.DoubleLo(), reg_types_.DoubleHi());
break;
case Instruction::INT_TO_BYTE:
work_line_->CheckUnaryOp(this, inst, reg_types_.Byte(), reg_types_.Integer());
break;
case Instruction::INT_TO_CHAR:
work_line_->CheckUnaryOp(this, inst, reg_types_.Char(), reg_types_.Integer());
break;
case Instruction::INT_TO_SHORT:
work_line_->CheckUnaryOp(this, inst, reg_types_.Short(), reg_types_.Integer());
break;
case Instruction::ADD_INT:
case Instruction::SUB_INT:
case Instruction::MUL_INT:
case Instruction::REM_INT:
case Instruction::DIV_INT:
case Instruction::SHL_INT:
case Instruction::SHR_INT:
case Instruction::USHR_INT:
work_line_->CheckBinaryOp(this, inst, reg_types_.Integer(), reg_types_.Integer(),
reg_types_.Integer(), false);
break;
case Instruction::AND_INT:
case Instruction::OR_INT:
case Instruction::XOR_INT:
work_line_->CheckBinaryOp(this, inst, reg_types_.Integer(), reg_types_.Integer(),
reg_types_.Integer(), true);
break;
case Instruction::ADD_LONG:
case Instruction::SUB_LONG:
case Instruction::MUL_LONG:
case Instruction::DIV_LONG:
case Instruction::REM_LONG:
case Instruction::AND_LONG:
case Instruction::OR_LONG:
case Instruction::XOR_LONG:
work_line_->CheckBinaryOpWide(this, inst, reg_types_.LongLo(), reg_types_.LongHi(),
reg_types_.LongLo(), reg_types_.LongHi(),
reg_types_.LongLo(), reg_types_.LongHi());
break;
case Instruction::SHL_LONG:
case Instruction::SHR_LONG:
case Instruction::USHR_LONG:
/* shift distance is Int, making these different from other binary operations */
work_line_->CheckBinaryOpWideShift(this, inst, reg_types_.LongLo(), reg_types_.LongHi(),
reg_types_.Integer());
break;
case Instruction::ADD_FLOAT:
case Instruction::SUB_FLOAT:
case Instruction::MUL_FLOAT:
case Instruction::DIV_FLOAT:
case Instruction::REM_FLOAT:
work_line_->CheckBinaryOp(this, inst, reg_types_.Float(), reg_types_.Float(),
reg_types_.Float(), false);
break;
case Instruction::ADD_DOUBLE:
case Instruction::SUB_DOUBLE:
case Instruction::MUL_DOUBLE:
case Instruction::DIV_DOUBLE:
case Instruction::REM_DOUBLE:
work_line_->CheckBinaryOpWide(this, inst, reg_types_.DoubleLo(), reg_types_.DoubleHi(),
reg_types_.DoubleLo(), reg_types_.DoubleHi(),
reg_types_.DoubleLo(), reg_types_.DoubleHi());
break;
case Instruction::ADD_INT_2ADDR:
case Instruction::SUB_INT_2ADDR:
case Instruction::MUL_INT_2ADDR:
case Instruction::REM_INT_2ADDR:
case Instruction::SHL_INT_2ADDR:
case Instruction::SHR_INT_2ADDR:
case Instruction::USHR_INT_2ADDR:
work_line_->CheckBinaryOp2addr(this, inst, reg_types_.Integer(), reg_types_.Integer(),
reg_types_.Integer(), false);
break;
case Instruction::AND_INT_2ADDR:
case Instruction::OR_INT_2ADDR:
case Instruction::XOR_INT_2ADDR:
work_line_->CheckBinaryOp2addr(this, inst, reg_types_.Integer(), reg_types_.Integer(),
reg_types_.Integer(), true);
break;
case Instruction::DIV_INT_2ADDR:
work_line_->CheckBinaryOp2addr(this, inst, reg_types_.Integer(), reg_types_.Integer(),
reg_types_.Integer(), false);
break;
case Instruction::ADD_LONG_2ADDR:
case Instruction::SUB_LONG_2ADDR:
case Instruction::MUL_LONG_2ADDR:
case Instruction::DIV_LONG_2ADDR:
case Instruction::REM_LONG_2ADDR:
case Instruction::AND_LONG_2ADDR:
case Instruction::OR_LONG_2ADDR:
case Instruction::XOR_LONG_2ADDR:
work_line_->CheckBinaryOp2addrWide(this, inst, reg_types_.LongLo(), reg_types_.LongHi(),
reg_types_.LongLo(), reg_types_.LongHi(),
reg_types_.LongLo(), reg_types_.LongHi());
break;
case Instruction::SHL_LONG_2ADDR:
case Instruction::SHR_LONG_2ADDR:
case Instruction::USHR_LONG_2ADDR:
work_line_->CheckBinaryOp2addrWideShift(this, inst, reg_types_.LongLo(), reg_types_.LongHi(),
reg_types_.Integer());
break;
case Instruction::ADD_FLOAT_2ADDR:
case Instruction::SUB_FLOAT_2ADDR:
case Instruction::MUL_FLOAT_2ADDR:
case Instruction::DIV_FLOAT_2ADDR:
case Instruction::REM_FLOAT_2ADDR:
work_line_->CheckBinaryOp2addr(this, inst, reg_types_.Float(), reg_types_.Float(),
reg_types_.Float(), false);
break;
case Instruction::ADD_DOUBLE_2ADDR:
case Instruction::SUB_DOUBLE_2ADDR:
case Instruction::MUL_DOUBLE_2ADDR:
case Instruction::DIV_DOUBLE_2ADDR:
case Instruction::REM_DOUBLE_2ADDR:
work_line_->CheckBinaryOp2addrWide(this, inst, reg_types_.DoubleLo(), reg_types_.DoubleHi(),
reg_types_.DoubleLo(), reg_types_.DoubleHi(),
reg_types_.DoubleLo(), reg_types_.DoubleHi());
break;
case Instruction::ADD_INT_LIT16:
case Instruction::RSUB_INT_LIT16:
case Instruction::MUL_INT_LIT16:
case Instruction::DIV_INT_LIT16:
case Instruction::REM_INT_LIT16:
work_line_->CheckLiteralOp(this, inst, reg_types_.Integer(), reg_types_.Integer(), false,
true);
break;
case Instruction::AND_INT_LIT16:
case Instruction::OR_INT_LIT16:
case Instruction::XOR_INT_LIT16:
work_line_->CheckLiteralOp(this, inst, reg_types_.Integer(), reg_types_.Integer(), true,
true);
break;
case Instruction::ADD_INT_LIT8:
case Instruction::RSUB_INT_LIT8:
case Instruction::MUL_INT_LIT8:
case Instruction::DIV_INT_LIT8:
case Instruction::REM_INT_LIT8:
case Instruction::SHL_INT_LIT8:
case Instruction::SHR_INT_LIT8:
case Instruction::USHR_INT_LIT8:
work_line_->CheckLiteralOp(this, inst, reg_types_.Integer(), reg_types_.Integer(), false,
false);
break;
case Instruction::AND_INT_LIT8:
case Instruction::OR_INT_LIT8:
case Instruction::XOR_INT_LIT8:
work_line_->CheckLiteralOp(this, inst, reg_types_.Integer(), reg_types_.Integer(), true,
false);
break;
// Special instructions.
case Instruction::RETURN_VOID_NO_BARRIER:
if (IsConstructor() && !IsStatic()) {
auto& declaring_class = GetDeclaringClass();
if (declaring_class.IsUnresolvedReference()) {
// We must iterate over the fields, even if we cannot use mirror classes to do so. Do it
// manually over the underlying dex file.
uint32_t first_index = GetFirstFinalInstanceFieldIndex(*dex_file_,
dex_file_->GetMethodId(dex_method_idx_).class_idx_);
if (first_index != DexFile::kDexNoIndex) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "return-void-no-barrier not expected for field "
<< first_index;
}
break;
}
auto* klass = declaring_class.GetClass();
for (uint32_t i = 0, num_fields = klass->NumInstanceFields(); i < num_fields; ++i) {
if (klass->GetInstanceField(i)->IsFinal()) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "return-void-no-barrier not expected for "
<< PrettyField(klass->GetInstanceField(i));
break;
}
}
}
// Handle this like a RETURN_VOID now. Code is duplicated to separate standard from
// quickened opcodes (otherwise this could be a fall-through).
if (!IsConstructor()) {
if (!GetMethodReturnType().IsConflict()) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "return-void not expected";
}
}
break;
// Note: the following instructions encode offsets derived from class linking.
// As such they use Class*/Field*/AbstractMethod* as these offsets only have
// meaning if the class linking and resolution were successful.
case Instruction::IGET_QUICK:
VerifyQuickFieldAccess<FieldAccessType::kAccGet>(inst, reg_types_.Integer(), true);
break;
case Instruction::IGET_WIDE_QUICK:
VerifyQuickFieldAccess<FieldAccessType::kAccGet>(inst, reg_types_.LongLo(), true);
break;
case Instruction::IGET_OBJECT_QUICK:
VerifyQuickFieldAccess<FieldAccessType::kAccGet>(inst, reg_types_.JavaLangObject(false), false);
break;
case Instruction::IGET_BOOLEAN_QUICK:
VerifyQuickFieldAccess<FieldAccessType::kAccGet>(inst, reg_types_.Boolean(), true);
break;
case Instruction::IGET_BYTE_QUICK:
VerifyQuickFieldAccess<FieldAccessType::kAccGet>(inst, reg_types_.Byte(), true);
break;
case Instruction::IGET_CHAR_QUICK:
VerifyQuickFieldAccess<FieldAccessType::kAccGet>(inst, reg_types_.Char(), true);
break;
case Instruction::IGET_SHORT_QUICK:
VerifyQuickFieldAccess<FieldAccessType::kAccGet>(inst, reg_types_.Short(), true);
break;
case Instruction::IPUT_QUICK:
VerifyQuickFieldAccess<FieldAccessType::kAccPut>(inst, reg_types_.Integer(), true);
break;
case Instruction::IPUT_BOOLEAN_QUICK:
VerifyQuickFieldAccess<FieldAccessType::kAccPut>(inst, reg_types_.Boolean(), true);
break;
case Instruction::IPUT_BYTE_QUICK:
VerifyQuickFieldAccess<FieldAccessType::kAccPut>(inst, reg_types_.Byte(), true);
break;
case Instruction::IPUT_CHAR_QUICK:
VerifyQuickFieldAccess<FieldAccessType::kAccPut>(inst, reg_types_.Char(), true);
break;
case Instruction::IPUT_SHORT_QUICK:
VerifyQuickFieldAccess<FieldAccessType::kAccPut>(inst, reg_types_.Short(), true);
break;
case Instruction::IPUT_WIDE_QUICK:
VerifyQuickFieldAccess<FieldAccessType::kAccPut>(inst, reg_types_.LongLo(), true);
break;
case Instruction::IPUT_OBJECT_QUICK:
VerifyQuickFieldAccess<FieldAccessType::kAccPut>(inst, reg_types_.JavaLangObject(false), false);
break;
case Instruction::INVOKE_VIRTUAL_QUICK:
case Instruction::INVOKE_VIRTUAL_RANGE_QUICK: {
bool is_range = (inst->Opcode() == Instruction::INVOKE_VIRTUAL_RANGE_QUICK);
ArtMethod* called_method = VerifyInvokeVirtualQuickArgs(inst, is_range);
if (called_method != nullptr) {
const char* descriptor = called_method->GetReturnTypeDescriptor();
const RegType& return_type = reg_types_.FromDescriptor(GetClassLoader(), descriptor, false);
if (!return_type.IsLowHalf()) {
work_line_->SetResultRegisterType(this, return_type);
} else {
work_line_->SetResultRegisterTypeWide(return_type, return_type.HighHalf(®_types_));
}
just_set_result = true;
}
break;
}
case Instruction::INVOKE_LAMBDA: {
// Don't bother verifying, instead the interpreter will take the slow path with access checks.
// If the code would've normally hard-failed, then the interpreter will throw the
// appropriate verification errors at runtime.
Fail(VERIFY_ERROR_FORCE_INTERPRETER); // TODO(iam): implement invoke-lambda verification
break;
}
case Instruction::CAPTURE_VARIABLE: {
// Don't bother verifying, instead the interpreter will take the slow path with access checks.
// If the code would've normally hard-failed, then the interpreter will throw the
// appropriate verification errors at runtime.
Fail(VERIFY_ERROR_FORCE_INTERPRETER); // TODO(iam): implement capture-variable verification
break;
}
case Instruction::CREATE_LAMBDA: {
// Don't bother verifying, instead the interpreter will take the slow path with access checks.
// If the code would've normally hard-failed, then the interpreter will throw the
// appropriate verification errors at runtime.
Fail(VERIFY_ERROR_FORCE_INTERPRETER); // TODO(iam): implement create-lambda verification
break;
}
case Instruction::LIBERATE_VARIABLE: {
// Don't bother verifying, instead the interpreter will take the slow path with access checks.
// If the code would've normally hard-failed, then the interpreter will throw the
// appropriate verification errors at runtime.
Fail(VERIFY_ERROR_FORCE_INTERPRETER); // TODO(iam): implement liberate-variable verification
break;
}
case Instruction::UNUSED_F4: {
DCHECK(false); // TODO(iam): Implement opcodes for lambdas
// Conservatively fail verification on release builds.
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "Unexpected opcode " << inst->DumpString(dex_file_);
break;
}
case Instruction::BOX_LAMBDA: {
// Don't bother verifying, instead the interpreter will take the slow path with access checks.
// If the code would've normally hard-failed, then the interpreter will throw the
// appropriate verification errors at runtime.
Fail(VERIFY_ERROR_FORCE_INTERPRETER); // TODO(iam): implement box-lambda verification
// Partial verification. Sets the resulting type to always be an object, which
// is good enough for some other verification to occur without hard-failing.
const uint32_t vreg_target_object = inst->VRegA_22x(); // box-lambda vA, vB
const RegType& reg_type = reg_types_.JavaLangObject(need_precise_constants_);
work_line_->SetRegisterType<LockOp::kClear>(this, vreg_target_object, reg_type);
break;
}
case Instruction::UNBOX_LAMBDA: {
// Don't bother verifying, instead the interpreter will take the slow path with access checks.
// If the code would've normally hard-failed, then the interpreter will throw the
// appropriate verification errors at runtime.
Fail(VERIFY_ERROR_FORCE_INTERPRETER); // TODO(iam): implement unbox-lambda verification
break;
}
/* These should never appear during verification. */
case Instruction::UNUSED_3E ... Instruction::UNUSED_43:
case Instruction::UNUSED_FA ... Instruction::UNUSED_FF:
case Instruction::UNUSED_79:
case Instruction::UNUSED_7A:
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "Unexpected opcode " << inst->DumpString(dex_file_);
break;
/*
* DO NOT add a "default" clause here. Without it the compiler will
* complain if an instruction is missing (which is desirable).
*/
} // end - switch (dec_insn.opcode)
if (have_pending_hard_failure_) {
if (Runtime::Current()->IsAotCompiler()) {
/* When AOT compiling, check that the last failure is a hard failure */
if (failures_[failures_.size() - 1] != VERIFY_ERROR_BAD_CLASS_HARD) {
LOG(ERROR) << "Pending failures:";
for (auto& error : failures_) {
LOG(ERROR) << error;
}
for (auto& error_msg : failure_messages_) {
LOG(ERROR) << error_msg->str();
}
LOG(FATAL) << "Pending hard failure, but last failure not hard.";
}
}
/* immediate failure, reject class */
info_messages_ << "Rejecting opcode " << inst->DumpString(dex_file_);
return false;
} else if (have_pending_runtime_throw_failure_) {
/* checking interpreter will throw, mark following code as unreachable */
opcode_flags = Instruction::kThrow;
// Note: the flag must be reset as it is only global to decouple Fail and is semantically per
// instruction. However, RETURN checking may throw LOCKING errors, so we clear at the
// very end.
}
/*
* If we didn't just set the result register, clear it out. This ensures that you can only use
* "move-result" immediately after the result is set. (We could check this statically, but it's
* not expensive and it makes our debugging output cleaner.)
*/
if (!just_set_result) {
work_line_->SetResultTypeToUnknown(this);
}
/*
* Handle "branch". Tag the branch target.
*
* NOTE: instructions like Instruction::EQZ provide information about the
* state of the register when the branch is taken or not taken. For example,
* somebody could get a reference field, check it for zero, and if the
* branch is taken immediately store that register in a boolean field
* since the value is known to be zero. We do not currently account for
* that, and will reject the code.
*
* TODO: avoid re-fetching the branch target
*/
if ((opcode_flags & Instruction::kBranch) != 0) {
bool isConditional, selfOkay;
if (!GetBranchOffset(work_insn_idx_, &branch_target, &isConditional, &selfOkay)) {
/* should never happen after static verification */
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "bad branch";
return false;
}
DCHECK_EQ(isConditional, (opcode_flags & Instruction::kContinue) != 0);
if (!CheckNotMoveExceptionOrMoveResult(code_item_->insns_, work_insn_idx_ + branch_target)) {
return false;
}
/* update branch target, set "changed" if appropriate */
if (nullptr != branch_line) {
if (!UpdateRegisters(work_insn_idx_ + branch_target, branch_line.get(), false)) {
return false;
}
} else {
if (!UpdateRegisters(work_insn_idx_ + branch_target, work_line_.get(), false)) {
return false;
}
}
}
/*
* Handle "switch". Tag all possible branch targets.
*
* We've already verified that the table is structurally sound, so we
* just need to walk through and tag the targets.
*/
if ((opcode_flags & Instruction::kSwitch) != 0) {
int offset_to_switch = insns[1] | (static_cast<int32_t>(insns[2]) << 16);
const uint16_t* switch_insns = insns + offset_to_switch;
int switch_count = switch_insns[1];
int offset_to_targets, targ;
if ((*insns & 0xff) == Instruction::PACKED_SWITCH) {
/* 0 = sig, 1 = count, 2/3 = first key */
offset_to_targets = 4;
} else {
/* 0 = sig, 1 = count, 2..count * 2 = keys */
DCHECK((*insns & 0xff) == Instruction::SPARSE_SWITCH);
offset_to_targets = 2 + 2 * switch_count;
}
/* verify each switch target */
for (targ = 0; targ < switch_count; targ++) {
int offset;
uint32_t abs_offset;
/* offsets are 32-bit, and only partly endian-swapped */
offset = switch_insns[offset_to_targets + targ * 2] |
(static_cast<int32_t>(switch_insns[offset_to_targets + targ * 2 + 1]) << 16);
abs_offset = work_insn_idx_ + offset;
DCHECK_LT(abs_offset, code_item_->insns_size_in_code_units_);
if (!CheckNotMoveExceptionOrMoveResult(code_item_->insns_, abs_offset)) {
return false;
}
if (!UpdateRegisters(abs_offset, work_line_.get(), false)) {
return false;
}
}
}
/*
* Handle instructions that can throw and that are sitting in a "try" block. (If they're not in a
* "try" block when they throw, control transfers out of the method.)
*/
if ((opcode_flags & Instruction::kThrow) != 0 && GetInstructionFlags(work_insn_idx_).IsInTry()) {
bool has_catch_all_handler = false;
CatchHandlerIterator iterator(*code_item_, work_insn_idx_);
// Need the linker to try and resolve the handled class to check if it's Throwable.
ClassLinker* linker = Runtime::Current()->GetClassLinker();
for (; iterator.HasNext(); iterator.Next()) {
uint16_t handler_type_idx = iterator.GetHandlerTypeIndex();
if (handler_type_idx == DexFile::kDexNoIndex16) {
has_catch_all_handler = true;
} else {
// It is also a catch-all if it is java.lang.Throwable.
mirror::Class* klass = linker->ResolveType(*dex_file_, handler_type_idx, dex_cache_,
class_loader_);
if (klass != nullptr) {
if (klass == mirror::Throwable::GetJavaLangThrowable()) {
has_catch_all_handler = true;
}
} else {
// Clear exception.
DCHECK(self_->IsExceptionPending());
self_->ClearException();
}
}
/*
* Merge registers into the "catch" block. We want to use the "savedRegs" rather than
* "work_regs", because at runtime the exception will be thrown before the instruction
* modifies any registers.
*/
if (!UpdateRegisters(iterator.GetHandlerAddress(), saved_line_.get(), false)) {
return false;
}
}
/*
* If the monitor stack depth is nonzero, there must be a "catch all" handler for this
* instruction. This does apply to monitor-exit because of async exception handling.
*/
if (work_line_->MonitorStackDepth() > 0 && !has_catch_all_handler) {
/*
* The state in work_line reflects the post-execution state. If the current instruction is a
* monitor-enter and the monitor stack was empty, we don't need a catch-all (if it throws,
* it will do so before grabbing the lock).
*/
if (inst->Opcode() != Instruction::MONITOR_ENTER || work_line_->MonitorStackDepth() != 1) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD)
<< "expected to be within a catch-all for an instruction where a monitor is held";
return false;
}
}
}
/* Handle "continue". Tag the next consecutive instruction.
* Note: Keep the code handling "continue" case below the "branch" and "switch" cases,
* because it changes work_line_ when performing peephole optimization
* and this change should not be used in those cases.
*/
if ((opcode_flags & Instruction::kContinue) != 0) {
DCHECK_EQ(Instruction::At(code_item_->insns_ + work_insn_idx_), inst);
uint32_t next_insn_idx = work_insn_idx_ + inst->SizeInCodeUnits();
if (next_insn_idx >= code_item_->insns_size_in_code_units_) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "Execution can walk off end of code area";
return false;
}
// The only way to get to a move-exception instruction is to get thrown there. Make sure the
// next instruction isn't one.
if (!CheckNotMoveException(code_item_->insns_, next_insn_idx)) {
return false;
}
if (nullptr != fallthrough_line) {
// Make workline consistent with fallthrough computed from peephole optimization.
work_line_->CopyFromLine(fallthrough_line.get());
}
if (GetInstructionFlags(next_insn_idx).IsReturn()) {
// For returns we only care about the operand to the return, all other registers are dead.
const Instruction* ret_inst = Instruction::At(code_item_->insns_ + next_insn_idx);
AdjustReturnLine(this, ret_inst, work_line_.get());
}
RegisterLine* next_line = reg_table_.GetLine(next_insn_idx);
if (next_line != nullptr) {
// Merge registers into what we have for the next instruction, and set the "changed" flag if
// needed. If the merge changes the state of the registers then the work line will be
// updated.
if (!UpdateRegisters(next_insn_idx, work_line_.get(), true)) {
return false;
}
} else {
/*
* We're not recording register data for the next instruction, so we don't know what the
* prior state was. We have to assume that something has changed and re-evaluate it.
*/
GetInstructionFlags(next_insn_idx).SetChanged();
}
}
/* If we're returning from the method, make sure monitor stack is empty. */
if ((opcode_flags & Instruction::kReturn) != 0) {
work_line_->VerifyMonitorStackEmpty(this);
}
/*
* Update start_guess. Advance to the next instruction of that's
* possible, otherwise use the branch target if one was found. If
* neither of those exists we're in a return or throw; leave start_guess
* alone and let the caller sort it out.
*/
if ((opcode_flags & Instruction::kContinue) != 0) {
DCHECK_EQ(Instruction::At(code_item_->insns_ + work_insn_idx_), inst);
*start_guess = work_insn_idx_ + inst->SizeInCodeUnits();
} else if ((opcode_flags & Instruction::kBranch) != 0) {
/* we're still okay if branch_target is zero */
*start_guess = work_insn_idx_ + branch_target;
}
DCHECK_LT(*start_guess, code_item_->insns_size_in_code_units_);
DCHECK(GetInstructionFlags(*start_guess).IsOpcode());
if (have_pending_runtime_throw_failure_) {
have_any_pending_runtime_throw_failure_ = true;
// Reset the pending_runtime_throw flag now.
have_pending_runtime_throw_failure_ = false;
}
return true;
} // NOLINT(readability/fn_size)