// Instruction types that are not marked as throwing (because they normally would not), but for // historical reasons may do so. These instructions cannot be marked kThrow as that would introduce // a general flow that is unwanted. // // Note: Not implemented as Instruction::Flags value as that set is full and we'd need to increase // the struct size (making it a non-power-of-two) for a single element. // // Note: This should eventually be removed.
constexpr bool IsCompatThrow(Instruction::Code opcode) { return opcode == Instruction::Code::RETURN_OBJECT || opcode == Instruction::Code::MOVE_EXCEPTION;
}
class MethodVerifierImpl : public ::art::verifier::MethodVerifier { public: bool IsInstanceConstructor() const { return IsConstructor() && !IsStatic();
}
void FinalAbstractClassError(ObjPtr<mirror::Class> klass) REQUIRES_SHARED(Locks::mutator_lock_) { // Note: We reuse NO_CLASS as the instruction we're checking shall throw an exception at // runtime if executed. A final abstract class shall fail verification, so no instances can // be created and therefore instance field or method access can be reached only for a null // reference and throw NPE. All other instructions where we check for final abstract class // shall throw `VerifyError`. (But we can also hit OOME/SOE while creating the exception.)
std::string temp; constchar* descriptor = klass->GetDescriptor(&temp);
Fail(VerifyError::VERIFY_ERROR_NO_CLASS)
<< "Final abstract class used in a context that requires a verified class: " << descriptor;
}
// Is the method being verified a constructor? See the comment on the field. bool IsConstructor() const { return is_constructor_;
}
// Is the method verified static? bool IsStatic() const { return (method_access_flags_ & kAccStatic) != 0;
}
// Adds the given string to the beginning of the last failure message. void PrependToLastFailMessage(std::string prepend) {
MessageOStream* last_fail_message = &LastFailureMessageStream();
prepend += last_fail_message->view();
last_fail_message->str(std::move(prepend));
}
// Return the last failure message stream for appending.
MessageOStream& LastFailureMessageStream() {
DCHECK(!failures_.empty()); return failures_.back().message;
}
/* Ensure that the register index is valid for this code item. */
ALWAYS_INLINE bool CheckRegisterIndex(uint32_t idx) {
uint32_t registers_size = code_item_accessor_.RegistersSize(); if (UNLIKELY(idx >= registers_size)) {
FailBadRegisterIndex(idx, registers_size); returnfalse;
} returntrue;
}
/* Ensure that the wide register index is valid for this code item. */
ALWAYS_INLINE bool CheckWideRegisterIndex(uint32_t idx) {
uint32_t registers_size = code_item_accessor_.RegistersSize(); if (UNLIKELY(idx + 1 >= registers_size)) {
FailBadWideRegisterIndex(idx, registers_size); returnfalse;
} returntrue;
}
// Perform static checks on an instruction referencing a CallSite. All we do here is ensure that // the call site index is in the valid range.
ALWAYS_INLINE bool CheckCallSiteIndex(uint32_t idx) {
uint32_t limit = dex_file_->NumCallSiteIds(); if (UNLIKELY(idx >= limit)) {
FailBadCallSiteIndex(idx, limit); returnfalse;
} returntrue;
}
// Perform static checks on a field Get or set instruction. We ensure that the field index // is in the valid range and we check that the field descriptor matches the instruction.
ALWAYS_INLINE bool CheckFieldIndex(const Instruction* inst,
uint16_t inst_data,
uint32_t field_idx) { if (UNLIKELY(field_idx >= dex_file_->NumFieldIds())) {
FailBadFieldIndex(field_idx); returnfalse;
}
// Prepare a table with permitted descriptors, evaluated at compile time. static constexpr uint32_t kVerifyFieldIndexFlags =
Instruction::kVerifyRegBField | Instruction::kVerifyRegCField; static constexpr uint32_t kMinFieldAccessOpcode = []() constexpr { for (uint32_t opcode = 0u; opcode != 256u; ++opcode) {
uint32_t verify_flags = Instruction::VerifyFlagsOf(enum_cast<Instruction::Code>(opcode)); if ((verify_flags & kVerifyFieldIndexFlags) != 0u) { return opcode;
}
}
LOG(FATAL) << "Compile time error if we reach this."; return0u;
}(); static constexpr uint32_t kMaxFieldAccessOpcode = []() constexpr { for (uint32_t opcode = 256u; opcode != 0u; ) {
--opcode;
uint32_t verify_flags = Instruction::VerifyFlagsOf(enum_cast<Instruction::Code>(opcode)); if ((verify_flags & kVerifyFieldIndexFlags) != 0u) { return opcode;
}
}
LOG(FATAL) << "Compile time error if we reach this."; return0u;
}(); static constexpr uint32_t kArraySize = kMaxFieldAccessOpcode + 1u - kMinFieldAccessOpcode; using PermittedDescriptorArray = std::array<std::pair<char, char>, kArraySize>; static constexpr PermittedDescriptorArray kPermittedDescriptors = []() constexpr {
PermittedDescriptorArray result; for (uint32_t index = 0u; index != kArraySize; ++index) {
Instruction::Code opcode = enum_cast<Instruction::Code>(index + kMinFieldAccessOpcode);
DexMemAccessType access_type; if (IsInstructionIGet(opcode) || IsInstructionIPut(opcode)) {
access_type = IGetOrIPutMemAccessType(opcode);
} else { // `iget*`, `iput*`, `sget*` and `sput*` instructions form a contiguous range.
CHECK(IsInstructionSGet(opcode) || IsInstructionSPut(opcode));
access_type = SGetOrSPutMemAccessType(opcode);
} switch (access_type) { case DexMemAccessType::kDexMemAccessWord:
result[index] = { 'I', 'F' }; break; case DexMemAccessType::kDexMemAccessWide:
result[index] = { 'J', 'D' }; break; case DexMemAccessType::kDexMemAccessObject:
result[index] = { 'L', '[' }; break; case DexMemAccessType::kDexMemAccessBoolean:
result[index] = { 'Z', 'Z' }; // Only one character is permitted. break; case DexMemAccessType::kDexMemAccessByte:
result[index] = { 'B', 'B' }; // Only one character is permitted. break; case DexMemAccessType::kDexMemAccessChar:
result[index] = { 'C', 'C' }; // Only one character is permitted. break; case DexMemAccessType::kDexMemAccessShort:
result[index] = { 'S', 'S' }; // Only one character is permitted. break; default:
LOG(FATAL) << "Compile time error if we reach this."; break;
}
} return result;
}();
// Check the first character of the field type descriptor.
Instruction::Code opcode = inst->Opcode(inst_data);
DCHECK_GE(opcode, kMinFieldAccessOpcode);
DCHECK_LE(opcode, kMaxFieldAccessOpcode);
std::pair<char, char> permitted = kPermittedDescriptors[opcode - kMinFieldAccessOpcode]; constchar* descriptor = dex_file_->GetFieldTypeDescriptor(field_idx); if (UNLIKELY(descriptor[0] != permitted.first && descriptor[0] != permitted.second)) {
FailBadFieldDescriptor(field_idx, permitted.first, permitted.second, descriptor[0], opcode); returnfalse;
} returntrue;
}
// Perform static checks on a method invocation instruction. All we do here is ensure that the // method index is in the valid range.
ALWAYS_INLINE bool CheckMethodIndex(uint32_t method_idx) { if (UNLIKELY(method_idx >= dex_file_->NumMethodIds())) {
FailBadMethodIndex(method_idx); returnfalse;
} returntrue;
}
// Perform static checks on an instruction referencing a constant method handle. All we do here // is ensure that the method index is in the valid range.
ALWAYS_INLINE bool CheckMethodHandleIndex(uint32_t idx) { if (UNLIKELY(idx >= dex_file_->NumMethodHandles())) {
FailBadMethodHandleIndex(idx); returnfalse;
} returntrue;
}
// Perform static checks on a prototype indexing instruction. All we do here is ensure that the // prototype index is in the valid range.
ALWAYS_INLINE bool CheckPrototypeIndex(uint32_t idx) { if (UNLIKELY(idx >= dex_file_->NumProtoIds())) {
FailBadPrototypeIndex(idx); returnfalse;
} returntrue;
}
/* Ensure that the string index is in the valid range. */
ALWAYS_INLINE bool CheckStringIndex(uint32_t idx) { if (UNLIKELY(idx >= dex_file_->NumStringIds())) {
FailBadStringIndex(idx); returnfalse;
} returntrue;
}
// Perform static checks on an instruction that takes a class constant. Ensure that the class // index is in the valid range.
ALWAYS_INLINE bool CheckTypeIndex(dex::TypeIndex idx) { if (UNLIKELY(idx.index_ >= dex_file_->GetHeader().type_ids_size_)) {
FailBadTypeIndex(idx); returnfalse;
} returntrue;
}
// Perform static checks on a `new-instance` instruction. Specifically, make sure the class // reference isn't for an array class. bool CheckNewInstance(dex::TypeIndex idx);
// Perform static checks on a `*new-array*` instruction. Specifically, make sure it // references an array class with the number of dimensions not exceeding 255. // For `filled-new-array*`, check for a valid component type; `I` is accepted, `J` and `D` // are rejected in line with the specification and other primitive component types are marked // for interpreting (throws `InternalError` in interpreter and the compiler cannot handle them). template <bool kFilled> bool CheckNewArray(dex::TypeIndex idx);
// Determine if the relative `offset` targets a valid dex pc. // The `offset` should be inside the range `[-dex_pc, end_dex_pc - dex_pc)`.
ALWAYS_INLINE staticbool IsOffsetInRange(uint32_t dex_pc, uint32_t end_dex_pc, int32_t offset) {
DCHECK_LT(dex_pc, end_dex_pc); if (offset >= 0) { returnstatic_cast<uint32_t>(offset) < end_dex_pc - dex_pc;
} else { // Use well-defined unsigned arithmetic for the lower bound check. return dex_pc >= -static_cast<uint32_t>(offset);
}
}
// Verify an array data table. bool CheckArrayData(uint32_t dex_pc, uint32_t end_dex_pc, const Instruction* inst);
// Verify that the target of a branch instruction is valid. We don't expect code to jump directly // into an exception handler, but it's valid to do so as long as the target isn't a // "move-exception" instruction. We verify that in a later stage. // The dex format forbids instructions other than `goto/32` from branching to themselves. // Updates "insn_flags_", setting the "branch target" flag. template <Instruction::Format kFormat>
ALWAYS_INLINE bool CheckAndMarkBranchTarget(uint32_t dex_pc,
uint32_t end_dex_pc, const Instruction* inst,
uint16_t inst_data);
// Check the register indices used in a "vararg" instruction, such as invoke-virtual or // filled-new-array. // - inst is the instruction from which we retrieve the arguments // - vA holds the argument count (0-5) // There are some tests we don't do here, e.g. we don't try to verify that invoking a method that // takes a double is done with consecutive registers. This requires parsing the target method // signature, which we will be doing later on during the code flow analysis. bool CheckVarArgRegs(const Instruction* inst, uint32_t vA) {
uint16_t registers_size = code_item_accessor_.RegistersSize(); // All args are 4-bit and therefore under 16. We do not need to check args for // `registers_size >= 16u` but let's check them anyway in debug builds. if (registers_size < 16u || kIsDebugBuild) {
uint32_t args[Instruction::kMaxVarArgRegs];
inst->GetVarArgs(args); for (uint32_t idx = 0; idx < vA; idx++) {
DCHECK_LT(args[idx], 16u); if (UNLIKELY(args[idx] >= registers_size)) {
DCHECK_LT(registers_size, 16u);
Fail(VERIFY_ERROR_BAD_CLASS_HARD)
<< "invalid reg index (" << args[idx]
<< ") in non-range invoke (>= " << registers_size << ")"; returnfalse;
}
}
} returntrue;
}
// Check the register indices used in a "vararg/range" instruction, such as invoke-virtual/range // or filled-new-array/range. // - vA holds word count, vC holds index of first reg.
ALWAYS_INLINE bool CheckVarArgRangeRegs(uint32_t vA, uint32_t vC) {
uint32_t registers_size = code_item_accessor_.RegistersSize(); // vA/vC are unsigned 8-bit/16-bit quantities for /range instructions, so there's no risk of // integer overflow when adding them here. if (UNLIKELY(vA + vC > registers_size)) {
FailBadVarArgsRangeRegs(vA, vC, registers_size); returnfalse;
} returntrue;
}
// Checks the method matches the expectations required to be signature polymorphic. bool CheckSignaturePolymorphicMethod(ArtMethod* method) REQUIRES_SHARED(Locks::mutator_lock_);
// Checks the invoked receiver matches the expectations for signature polymorphic methods. bool CheckSignaturePolymorphicReceiver(const Instruction* inst) REQUIRES_SHARED(Locks::mutator_lock_);
// Extract the relative offset from a branch instruction. // Returns "false" on failure (e.g. this isn't a branch instruction). bool GetBranchOffset(uint32_t cur_offset, int32_t* pOffset, bool* pConditional, bool* selfOkay);
// Set the register types for the first instruction in the method based on the method signature. // This has the side-effect of validating the signature. bool SetTypesFromSignature() REQUIRES_SHARED(Locks::mutator_lock_);
// Perform verification of a `filled-new-array/-range` instruction. bool VerifyFilledNewArray(const Instruction* inst, bool is_range)
REQUIRES_SHARED(Locks::mutator_lock_);
// Helper to perform verification on puts of primitive type. bool VerifyPrimitivePut(RegType::Kind target_kind, uint32_t vregA)
REQUIRES_SHARED(Locks::mutator_lock_);
// Perform verification of a aget/aput instruction. // For aget, the destination register's type will be set to be that of component type // of the array unless the array type is unknown, in which case a bottom type inferred // from the type of instruction is used. template <AccessType kAccType, AccessWidth kAccWidth, bool kIsPrimitive> bool VerifyArrayAccess(const Instruction* inst, uint16_t inst_data, Instruction::Code opcode)
REQUIRES_SHARED(Locks::mutator_lock_);
// Lookup instance field and fail for resolution violations
ArtField* GetInstanceField(uint32_t vregB, uint32_t field_idx, bool is_put)
REQUIRES_SHARED(Locks::mutator_lock_);
// Lookup static field and fail for resolution violations
ArtField* GetStaticField(uint32_t field_idx, bool is_put) REQUIRES_SHARED(Locks::mutator_lock_);
// Common checks for `GetInstanceField()` and `GetStaticField()`.
ArtField* GetISFieldCommon(ArtField* field, bool is_put) REQUIRES_SHARED(Locks::mutator_lock_);
// Resolves a class based on an index and, if C is kYes, performs access checks to ensure // the referrer can access the resolved class. template <CheckAccess C> const RegType& ResolveClass(dex::TypeIndex class_idx)
REQUIRES_SHARED(Locks::mutator_lock_);
// Similar checks to the above, but on the proto. Will be used when the method cannot be // resolved. void VerifyInvocationArgsUnresolvedMethod(const Instruction* inst, MethodType method_type, bool is_range)
REQUIRES_SHARED(Locks::mutator_lock_);
// Return the register type for the method. const RegType& GetMethodReturnType() REQUIRES_SHARED(Locks::mutator_lock_);
// Get a type representing the declaring class of the method. const RegType& GetDeclaringClass() REQUIRES_SHARED(Locks::mutator_lock_) { if (declaring_class_ == nullptr) { const dex::MethodId& method_id = dex_file_->GetMethodId(dex_method_idx_);
declaring_class_ = ®_types_.FromTypeIndex(method_id.class_idx_);
} return *declaring_class_;
}
bool CanAccess(const RegType& other) REQUIRES_SHARED(Locks::mutator_lock_) {
DCHECK(other.IsJavaLangObject() || other.IsReference() || other.IsUnresolvedReference()); const RegType& declaring_class = GetDeclaringClass(); if (declaring_class.Equals(other)) { returntrue; // Trivial accessibility.
} elseif (other.IsUnresolvedReference()) { returnfalse; // More complicated test not possible on unresolved types, be conservative.
} elseif (declaring_class.IsUnresolvedReference()) { // Be conservative, only allow if `other` is public. return other.IsJavaLangObject() || (other.IsReference() && other.GetClass()->IsPublic());
} else { return GetRegTypeClass(declaring_class)->CanAccess(GetRegTypeClass(other));
}
}
bool CanAccessMember(ObjPtr<mirror::Class> klass, uint32_t access_flags)
REQUIRES_SHARED(Locks::mutator_lock_) { const RegType& declaring_class = GetDeclaringClass(); if (declaring_class.IsUnresolvedReference()) { returnfalse; // More complicated test not possible on unresolved types, be conservative.
} else { return GetRegTypeClass(declaring_class)->CanAccessMember(klass, access_flags);
}
}
NO_INLINE void FailBranchOffsetZero(uint32_t dex_pc) {
work_insn_idx_ = dex_pc; // Let `Fail()` record the dex PC of the failing instruction.
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "branch offset of zero not allowed.";
}
NO_INLINE void FailTargetOffsetOutOfRange(uint32_t dex_pc, uint32_t end_dex_pc, int32_t offset) {
work_insn_idx_ = dex_pc; // Let `Fail()` record the dex PC of the failing instruction.
Fail(VERIFY_ERROR_BAD_CLASS_HARD)
<< "invalid target offset " << offset
<< " (end " << reinterpret_cast<void*>(end_dex_pc) << ")";
}
NO_INLINE void FailTargetMidInstruction(uint32_t dex_pc, uint32_t target_dex_pc) {
work_insn_idx_ = dex_pc; // Let `Fail()` record the dex PC of the failing instruction.
Fail(VERIFY_ERROR_BAD_CLASS_HARD)
<< "target dex pc " << reinterpret_cast<void*>(target_dex_pc)
<< " is not at instruction start.";
}
NO_INLINE void FailBranchTargetIsMoveResultOrMoveException(uint32_t dex_pc,
uint32_t target_dex_pc,
Instruction::Code target_opcode) {
DCHECK(IsMoveResultOrMoveException(target_opcode));
work_insn_idx_ = dex_pc; // Let `Fail()` record the dex PC of the failing instruction.
Fail(VERIFY_ERROR_BAD_CLASS_HARD)
<< "invalid use of " << target_opcode << " as branch target at "
<< reinterpret_cast<void*>(target_dex_pc);
}
NO_INLINE void FailUnalignedTableDexPc(uint32_t dex_pc, uint32_t table_dex_pc) {
work_insn_idx_ = dex_pc; // Let `Fail()` record the dex PC of the failing instruction.
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "unaligned table at " << table_dex_pc;
}
NO_INLINE void FailBadArrayDataSignature(uint32_t dex_pc, uint32_t array_data_dex_pc) {
work_insn_idx_ = dex_pc; // Let `Fail()` record the dex PC of the failing instruction.
Fail(VERIFY_ERROR_BAD_CLASS_HARD)
<< "invalid magic for array-data at " << reinterpret_cast<void*>(array_data_dex_pc);
}
NO_INLINE void FailBadSwitchPayloadSignature(uint32_t dex_pc,
uint32_t switch_payload_dex_pc,
uint16_t signature,
uint16_t expected_signature) {
work_insn_idx_ = dex_pc; // Let `Fail()` record the dex PC of the failing instruction.
Fail(VERIFY_ERROR_BAD_CLASS_HARD)
<< "wrong signature for switch payload at "
<< reinterpret_cast<void*>(switch_payload_dex_pc)
<< " (0x" << std::hex << signature << ", wanted 0x" << expected_signature << ")";
}
NO_INLINE void FailPackedSwitchKeyOverflow(uint32_t dex_pc,
uint32_t switch_payload_dex_pc,
int32_t first_key,
uint32_t switch_count) {
work_insn_idx_ = dex_pc; // Let `Fail()` record the dex PC of the failing instruction.
Fail(VERIFY_ERROR_BAD_CLASS_HARD)
<< "invalid packed switch payload at "
<< reinterpret_cast<void*>(switch_payload_dex_pc)
<< ", key overflow: first_key=" << first_key << ", switch_count=" << switch_count;
}
NO_INLINE void FailSparseSwitchPayloadKeyOrder(uint32_t dex_pc,
uint32_t switch_payload_dex_pc,
int32_t previous_key,
int32_t current_key) {
work_insn_idx_ = dex_pc; // Let `Fail()` record the dex PC of the failing instruction.
Fail(VERIFY_ERROR_BAD_CLASS_HARD)
<< "invalid sparse switch payload at "
<< reinterpret_cast<void*>(switch_payload_dex_pc)
<< ", unordered keys: previous=" << previous_key << ", current=" << current_key;
}
ALWAYS_INLINE inlinebool VerifyRegisterType(uint32_t vsrc, const RegType& check_type)
REQUIRES_SHARED(Locks::mutator_lock_) { // Verify the src register type against the check type refining the type of the register const RegType& src_type = work_line_->GetRegisterType(this, vsrc); if (UNLIKELY(!IsAssignableFrom(check_type, src_type))) { enum VerifyError fail_type; if (!check_type.IsNonZeroReferenceTypes() || !src_type.IsNonZeroReferenceTypes()) { // Hard fail if one of the types is primitive, since they are concretely known.
fail_type = VERIFY_ERROR_BAD_CLASS_HARD;
} elseif (check_type.IsUninitializedTypes() || src_type.IsUninitializedTypes()) { // Hard fail for uninitialized types, which don't match anything but themselves.
fail_type = VERIFY_ERROR_BAD_CLASS_HARD;
} elseif (check_type.IsArrayTypes() && !src_type.IsArrayTypes()) { // Hard fail: check is array, src is non-array. Note that here we don't have to check // `!src_type.IsUnresolvedTypes()` since the assignability check is not symmetric.
fail_type = VERIFY_ERROR_BAD_CLASS_HARD;
} elseif (!check_type.IsArrayTypes() &&
!check_type.IsUnresolvedTypes() &&
src_type.IsArrayTypes()) { // Hard fail: check is resolved non-array, src is array.
fail_type = VERIFY_ERROR_BAD_CLASS_HARD;
} elseif (check_type.IsUnresolvedTypes() || src_type.IsUnresolvedTypes()) {
fail_type = VERIFY_ERROR_UNRESOLVED_TYPE_CHECK;
} else {
fail_type = VERIFY_ERROR_BAD_CLASS_HARD;
}
FailForRegisterType(vsrc, check_type, src_type, fail_type); return fail_type != VERIFY_ERROR_BAD_CLASS_HARD;
} if (check_type.IsLowHalf()) { const RegType& src_type_h = work_line_->GetRegisterType(this, vsrc + 1); if (UNLIKELY(!src_type.CheckWidePair(src_type_h))) {
FailForRegisterTypeWide(vsrc, src_type, src_type_h); returnfalse;
}
} // The register at vsrc has a defined type, we know the lower-upper-bound, but this is less // precise than the subtype in vsrc so leave it for reference types. For primitive types if // they are a defined type then they are as precise as we can get, however, for constant types // we may wish to refine them. Unfortunately constant propagation has rendered this useless. returntrue;
}
ALWAYS_INLINE inlinebool VerifyRegisterType(uint32_t vsrc, RegType::Kind check_kind)
REQUIRES_SHARED(Locks::mutator_lock_) {
DCHECK(check_kind == RegType::Kind::kInteger || check_kind == RegType::Kind::kFloat); // Verify the src register type against the check type refining the type of the register
uint16_t src_type_id = work_line_->GetRegisterTypeId(vsrc); if (UNLIKELY(src_type_id >= RegTypeCache::NumberOfRegKindCacheIds()) ||
UNLIKELY(RegType::AssignabilityFrom(check_kind, RegTypeCache::RegKindForId(src_type_id)) !=
RegType::Assignability::kAssignable)) { // Integer or float assignability is never a `kNarrowingConversion` or `kReference`.
DCHECK_EQ(
RegType::AssignabilityFrom(check_kind, reg_types_.GetFromId(src_type_id).GetKind()),
RegType::Assignability::kNotAssignable);
FailForRegisterType(vsrc, check_kind, src_type_id); returnfalse;
} returntrue;
}
bool VerifyRegisterTypeWide(uint32_t vsrc, RegType::Kind check_kind)
REQUIRES_SHARED(Locks::mutator_lock_) {
DCHECK(check_kind == RegType::Kind::kLongLo || check_kind == RegType::Kind::kDoubleLo); // Verify the src register type against the check type refining the type of the register
uint16_t src_type_id = work_line_->GetRegisterTypeId(vsrc); if (UNLIKELY(src_type_id >= RegTypeCache::NumberOfRegKindCacheIds()) ||
UNLIKELY(RegType::AssignabilityFrom(check_kind, RegTypeCache::RegKindForId(src_type_id)) !=
RegType::Assignability::kAssignable)) { // Wide assignability is never a `kNarrowingConversion` or `kReference`.
DCHECK_EQ(
RegType::AssignabilityFrom(check_kind, reg_types_.GetFromId(src_type_id).GetKind()),
RegType::Assignability::kNotAssignable);
FailForRegisterType(vsrc, check_kind, src_type_id); returnfalse;
}
uint16_t src_type_id_h = work_line_->GetRegisterTypeId(vsrc + 1);
uint16_t expected_src_type_id_h =
RegTypeCache::IdForRegKind(RegType::ToHighHalf(RegTypeCache::RegKindForId(src_type_id)));
DCHECK_EQ(src_type_id_h == expected_src_type_id_h,
reg_types_.GetFromId(src_type_id).CheckWidePair(reg_types_.GetFromId(src_type_id_h))); if (UNLIKELY(src_type_id_h != expected_src_type_id_h)) {
FailForRegisterTypeWide(vsrc, src_type_id, src_type_id_h); returnfalse;
} // The register at vsrc has a defined type, we know the lower-upper-bound, but this is less // precise than the subtype in vsrc so leave it for reference types. For primitive types if // they are a defined type then they are as precise as we can get, however, for constant types // we may wish to refine them. Unfortunately constant propagation has rendered this useless. returntrue;
}
// Returns the method index of an invoke instruction. static uint16_t GetMethodIdxOfInvoke(const Instruction* inst) { // Note: This is compiled to a single load in release mode.
Instruction::Code opcode = inst->Opcode(); if (opcode == Instruction::INVOKE_VIRTUAL ||
opcode == Instruction::INVOKE_SUPER ||
opcode == Instruction::INVOKE_DIRECT ||
opcode == Instruction::INVOKE_STATIC ||
opcode == Instruction::INVOKE_INTERFACE) { return inst->VRegB_35c();
} elseif (opcode == Instruction::INVOKE_VIRTUAL_RANGE ||
opcode == Instruction::INVOKE_SUPER_RANGE ||
opcode == Instruction::INVOKE_DIRECT_RANGE ||
opcode == Instruction::INVOKE_STATIC_RANGE ||
opcode == Instruction::INVOKE_INTERFACE_RANGE) { return inst->VRegB_3rc();
} elseif (opcode == Instruction::INVOKE_POLYMORPHIC) { return inst->VRegB_45cc();
} else {
DCHECK_EQ(opcode, Instruction::INVOKE_POLYMORPHIC_RANGE); return inst->VRegB_4rcc();
}
}
// Returns the field index of a field access instruction.
ALWAYS_INLINE static uint16_t GetFieldIdxOfFieldAccess(const Instruction* inst) { // Note: This is compiled to a single load in release mode.
Instruction::Code opcode = inst->Opcode(); if (IsInstructionSGet(opcode) || IsInstructionSPut(opcode)) { return inst->VRegB_21c();
} else {
DCHECK(IsInstructionIGet(opcode) || IsInstructionIPut(opcode)); return inst->VRegC_22c();
}
}
// For app-compatibility, code after a runtime throw is treated as dead code // for apps targeting <= S. void PotentiallyMarkRuntimeThrow() override;
// Dump the failures encountered by the verifier.
std::ostream& DumpFailures(std::ostream& os) { for (const VerifyErrorAndMessage& veam : failures_) {
os << veam.message.view() << "\n";
} return os;
}
// Dump the state of the verifier, namely each instruction, what flags are set on it, register // information void Dump(std::ostream& os) REQUIRES_SHARED(Locks::mutator_lock_) {
VariableIndentationOutputStream vios(&os);
Dump(&vios);
} void Dump(VariableIndentationOutputStream* vios) REQUIRES_SHARED(Locks::mutator_lock_);
const uint32_t method_access_flags_; // Method's access flags. const RegType* return_type_; // Lazily computed return type of the method. // The dex_cache for the declaring class of the method.
Handle<mirror::DexCache> dex_cache_ GUARDED_BY(Locks::mutator_lock_); // The class loader for the declaring class of the method.
Handle<mirror::ClassLoader> class_loader_ GUARDED_BY(Locks::mutator_lock_); const RegType* declaring_class_; // Lazily computed reg type of the method's declaring class.
// The dex PC of a FindLocksAtDexPc request, -1 otherwise.
uint32_t interesting_dex_pc_; // The container into which FindLocksAtDexPc should write the registers containing held locks, // null if we're not doing FindLocksAtDexPc.
std::vector<DexLockInfo>* monitor_enter_dex_pcs_;
// Indicates whether we verify to dump the info. In that case we accept quickened instructions // even though we might detect to be a compiler. Should only be set when running // VerifyMethodAndDump. constbool verify_to_dump_;
// Whether or not we call AllowThreadSuspension periodically, we want a way to disable this for // thread dumping checkpoints since we may get thread suspension at an inopportune time due to // FindLocksAtDexPC, resulting in deadlocks. constbool allow_thread_suspension_;
// Whether the method seems to be a constructor. Note that this field exists as we can't trust // the flags in the dex file. Some older code does not mark methods named "<init>" and "<clinit>" // correctly. // // Note: this flag is only valid once Verify() has started. bool is_constructor_;
// API level, for dependent checks. Note: we do not use '0' for unset here, to simplify checks. // Instead, unset level should correspond to max(). const uint32_t api_level_;
// Set of switch payload addresses encountered so far in the current method.
std::unordered_set<const uint16_t*> switch_payload_addresses_;
DISALLOW_COPY_AND_ASSIGN(MethodVerifierImpl);
};
template <bool kVerifierDebug> class MethodVerifier final : public MethodVerifierImpl { public: void FindLocksAtDexPc() REQUIRES_SHARED(Locks::mutator_lock_);
private: using MethodVerifierImpl::MethodVerifierImpl;
/* Perform detailed code-flow analysis on a single method. */ bool VerifyCodeFlow() REQUIRES_SHARED(Locks::mutator_lock_);
// Run verification on the method. Returns true if verification completes and false if the input // has an irrecoverable corruption. bool Verify() override REQUIRES_SHARED(Locks::mutator_lock_);
friendclass ::art::verifier::MethodVerifier;
DISALLOW_COPY_AND_ASSIGN(MethodVerifier);
};
staticbool IsLargeMethod(const CodeItemDataAccessor& accessor) { if (!accessor.HasCodeItem()) { returnfalse;
}
template <bool kVerifierDebug> void MethodVerifier<kVerifierDebug>::FindLocksAtDexPc() {
CHECK(monitor_enter_dex_pcs_ != nullptr);
CHECK(code_item_accessor_.HasCodeItem()); // This only makes sense for methods with code.
// Quick check whether there are any monitor_enter instructions before verifying. for (const DexInstructionPcPair& inst : code_item_accessor_) { if (inst->Opcode() == Instruction::MONITOR_ENTER) { // Strictly speaking, we ought to be able to get away with doing a subset of the full method // verification. In practice, the phase we want relies on data structures set up by all the // earlier passes, so we just run the full method verification and bail out early when we've // got what we wanted.
Verify(); return;
}
}
}
template <bool kVerifierDebug> bool MethodVerifier<kVerifierDebug>::Verify() { // Some older code doesn't correctly mark constructors as such, so we need look // at the name if the constructor flag is not present. if ((method_access_flags_ & kAccConstructor) != 0) { // `DexFileVerifier` rejects methods with the constructor flag without a constructor name.
DCHECK(dex_file_->GetMethodNameView(dex_method_idx_) == "<init>" ||
dex_file_->GetMethodNameView(dex_method_idx_) == "<clinit>");
is_constructor_ = true;
} elseif (dex_file_->GetMethodName(dex_method_idx_)[0] == '<') { // `DexFileVerifier` rejects method names starting with '<' other than constructors.
DCHECK(dex_file_->GetMethodNameView(dex_method_idx_) == "<init>" ||
dex_file_->GetMethodNameView(dex_method_idx_) == "<clinit>");
LOG(WARNING) << "Method " << dex_file_->PrettyMethod(dex_method_idx_)
<< " not marked as constructor.";
is_constructor_ = true;
} // If it's a constructor, check whether IsStatic() matches the name for newer dex files. // This should be rejected by the `DexFileVerifier` but it's accepted for older dex files. if (kIsDebugBuild && IsConstructor() && dex_file_->SupportsDefaultMethods()) {
CHECK_EQ(IsStatic(), dex_file_->GetMethodNameView(dex_method_idx_) == "<clinit>");
}
// Methods may only have one of public/protected/private. // This should have been rejected by the dex file verifier. Only do in debug build.
constexpr uint32_t kAccPublicProtectedPrivate = kAccPublic | kAccProtected | kAccPrivate;
DCHECK_IMPLIES((method_access_flags_ & kAccPublicProtectedPrivate) != 0u,
IsPowerOfTwo(method_access_flags_ & kAccPublicProtectedPrivate));
// If there aren't any instructions, make sure that's expected, then exit successfully. if (!code_item_accessor_.HasCodeItem()) { // Only native or abstract methods may not have code. if ((method_access_flags_ & (kAccNative | kAccAbstract)) == 0) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "zero-length code in concrete non-native method"; returnfalse;
}
// Test FastNative and CriticalNative annotations. We do this in the // verifier for convenience. if ((method_access_flags_ & kAccNative) != 0) { // Fetch the flags from the annotations: the class linker hasn't processed // them yet.
uint32_t native_access_flags = annotations::GetNativeMethodAnnotationAccessFlags(
*dex_file_, class_def_, dex_method_idx_); if ((native_access_flags & kAccFastNative) != 0) { if ((method_access_flags_ & kAccSynchronized) != 0) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "fast native methods cannot be synchronized"; returnfalse;
}
} if ((native_access_flags & kAccCriticalNative) != 0) { if ((method_access_flags_ & kAccSynchronized) != 0) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "critical native methods cannot be synchronized"; returnfalse;
} if ((method_access_flags_ & kAccStatic) == 0) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "critical native methods must be static"; returnfalse;
} constchar* shorty = dex_file_->GetMethodShorty(dex_method_idx_); for (size_t i = 0, len = strlen(shorty); i < len; ++i) { if (Primitive::GetType(shorty[i]) == Primitive::kPrimNot) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "critical native methods must not have references as arguments or return type"; returnfalse;
}
}
}
}
// This should have been rejected by the dex file verifier. Only do in debug build. // Note: the above will also be rejected in the dex file verifier, starting in dex version 37. if (kIsDebugBuild) { if ((method_access_flags_ & kAccAbstract) != 0) { // Abstract methods are not allowed to have the following flags. static constexpr uint32_t kForbidden =
kAccPrivate |
kAccStatic |
kAccFinal |
kAccNative |
kAccStrict |
kAccSynchronized; if ((method_access_flags_ & kForbidden) != 0) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD)
<< "method can't be abstract and private/static/final/native/strict/synchronized"; returnfalse;
}
} if ((class_def_.GetJavaAccessFlags() & kAccInterface) != 0) { // Interface methods must be public and abstract (if default methods are disabled).
uint32_t kRequired = kAccPublic; if ((method_access_flags_ & kRequired) != kRequired) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "interface methods must be public"; returnfalse;
} // In addition to the above, interface methods must not be protected. static constexpr uint32_t kForbidden = kAccProtected; if ((method_access_flags_ & kForbidden) != 0) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "interface methods can't be protected"; returnfalse;
}
} // We also don't allow constructors to be abstract or native. if (IsConstructor()) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "constructors can't be abstract or native"; returnfalse;
}
} returntrue;
}
// This should have been rejected by the dex file verifier. Only do in debug build. if (kIsDebugBuild) { // When there's code, the method must not be native or abstract. if ((method_access_flags_ & (kAccNative | kAccAbstract)) != 0) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "non-zero-length code in abstract or native method"; returnfalse;
}
if ((class_def_.GetJavaAccessFlags() & kAccInterface) != 0) { // Interfaces may always have static initializers for their fields. If we are running with // default methods enabled we also allow other public, static, non-final methods to have code. // Otherwise that is the only type of method allowed. if (!(IsConstructor() && IsStatic())) { if (IsInstanceConstructor()) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "interfaces may not have non-static constructor"; returnfalse;
} elseif (method_access_flags_ & kAccFinal) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "interfaces may not have final methods"; returnfalse;
} else {
uint32_t access_flag_options = kAccPublic; if (dex_file_->SupportsDefaultMethods()) {
access_flag_options |= kAccPrivate;
} if (!(method_access_flags_ & access_flag_options)) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD)
<< "interfaces may not have protected or package-private members"; returnfalse;
}
}
}
}
// Instance constructors must not be synchronized. if (IsInstanceConstructor()) { static constexpr uint32_t kForbidden = kAccSynchronized; if ((method_access_flags_ & kForbidden) != 0) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "constructors can't be synchronized"; returnfalse;
}
}
}
// Consistency-check of the register counts. // ins + locals = registers, so make sure that ins <= registers. if (code_item_accessor_.InsSize() > code_item_accessor_.RegistersSize()) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "bad register counts (ins="
<< code_item_accessor_.InsSize()
<< " regs=" << code_item_accessor_.RegistersSize(); returnfalse;
}
if (code_item_accessor_.InsnsSizeInCodeUnits() == 0u) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "code item has no opcode"; returnfalse;
} // Allocate and initialize an array to hold instruction data.
insn_flags_.reset(allocator_.AllocArray<InstructionFlags>(
code_item_accessor_.InsnsSizeInCodeUnits()));
DCHECK(insn_flags_ != nullptr); // `ArenaAllocator` guarantees zero-initialization.
static_assert(std::is_same_v<decltype(allocator_), ArenaAllocator>);
DCHECK(std::all_of(
insn_flags_.get(),
insn_flags_.get() + code_item_accessor_.InsnsSizeInCodeUnits(),
[](const InstructionFlags& flags) { return flags.Equals(InstructionFlags()); })); // Run through the instructions and see if the width checks out. bool result = ComputeWidthsAndCountOps(); // Flag instructions guarded by a "try" block and check exception handlers.
result = result && ScanTryCatchBlocks(); // Perform static instruction verification.
result = result && VerifyInstructions(); // Perform code-flow analysis and return.
result = result && VerifyCodeFlow();
return result;
}
bool MethodVerifierImpl::ComputeWidthsAndCountOps() { // We can't assume the instruction is well formed, handle the case where calculating the size // goes past the end of the code item. const uint32_t insns_size = code_item_accessor_.InsnsSizeInCodeUnits(); const Instruction* inst = &code_item_accessor_.InstructionAt(0u);
uint32_t dex_pc = 0u; while (dex_pc != insns_size) { const uint32_t remaining_code_units = insns_size - dex_pc; const uint16_t inst_data = inst->Fetch16(0); const Instruction::Code opcode = inst->Opcode(inst_data);
uint32_t instruction_size = 0u; bool ok; if (opcode == Instruction::NOP) { auto check_switch = [&](uint32_t base_size, uint32_t entry_size) ALWAYS_INLINE { if (UNLIKELY(base_size > remaining_code_units)) { returnfalse;
} // This 32-bit calculation cannot overflow because `num_entries` starts as 16-bit.
uint32_t num_entries = inst->Fetch16(1);
instruction_size = base_size + num_entries * entry_size; if (UNLIKELY(instruction_size > remaining_code_units)) { returnfalse;
} returntrue;
}; switch (inst_data) { case Instruction::kPackedSwitchSignature:
ok = check_switch(4u, 2u); break; case Instruction::kSparseSwitchSignature:
ok = check_switch(2u, 4u); break; case Instruction::kArrayDataSignature: if (UNLIKELY(remaining_code_units < 4u)) {
ok = false;
} else {
uint16_t element_size = inst->Fetch16(1);
uint32_t length = inst->Fetch16(2) | (((uint32_t)inst->Fetch16(3)) << 16); // Use 64-bit calculation to avoid arithmetic overflow.
uint64_t bytes = static_cast<uint64_t>(element_size) * static_cast<uint64_t>(length);
uint64_t code_units = UINT64_C(4) + (bytes + /* round up */ UINT64_C(1)) / UINT64_C(2); if (UNLIKELY(code_units > remaining_code_units)) {
ok = false;
} else {
instruction_size = dchecked_integral_cast<uint32_t>(code_units);
ok = true;
}
} break; default:
instruction_size = 1u;
ok = true; break;
}
} else {
instruction_size = Instruction::SizeInCodeUnits(Instruction::FormatOf(opcode));
DCHECK_EQ(instruction_size, inst->SizeInCodeUnits());
ok = LIKELY(instruction_size <= remaining_code_units);
} if (!ok) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "code did not end where expected ("
<< dex_pc << " vs. " << insns_size << ")"; returnfalse;
}
GetModifiableInstructionFlags(dex_pc).SetIsOpcode();
DCHECK_NE(instruction_size, 0u);
DCHECK_EQ(instruction_size, inst->SizeInCodeUnits());
DCHECK_LE(instruction_size, remaining_code_units);
dex_pc += instruction_size;
inst = inst->RelativeAt(instruction_size);
}
DCHECK(GetInstructionFlags(0).IsOpcode()); returntrue;
}
bool MethodVerifierImpl::ScanTryCatchBlocks() { const uint32_t tries_size = code_item_accessor_.TriesSize(); if (tries_size == 0) { returntrue;
} const uint32_t insns_size = code_item_accessor_.InsnsSizeInCodeUnits(); for (const dex::TryItem& try_item : code_item_accessor_.TryItems()) { const uint32_t start = try_item.start_addr_; const uint32_t end = start + try_item.insn_count_; if ((start >= end) || (start >= insns_size) || (end > insns_size)) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "bad exception entry: startAddr=" << start
<< " endAddr=" << end << " (size=" << insns_size << ")"; returnfalse;
} if (!GetInstructionFlags(start).IsOpcode()) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD)
<< "'try' block starts inside an instruction (" << start << ")"; returnfalse;
} // `end` should be either: A) the end of the method, or B) right before an instruction. if (end != insns_size && !GetInstructionFlags(end).IsOpcode()) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD)
<< "'try' block ends inside an instruction (" << end << ")"; returnfalse;
}
DexInstructionIterator end_it(code_item_accessor_.Insns(), end); for (DexInstructionIterator it(code_item_accessor_.Insns(), start); it < end_it; ++it) {
GetModifiableInstructionFlags(it.DexPc()).SetInTry();
}
} // Iterate over each of the handlers to verify target addresses. const uint8_t* handlers_ptr = code_item_accessor_.GetCatchHandlerData(); const uint32_t handlers_size = DecodeUnsignedLeb128(&handlers_ptr);
ClassLinker* linker = GetClassLinker(); for (uint32_t idx = 0; idx < handlers_size; idx++) {
CatchHandlerIterator iterator(handlers_ptr); for (; iterator.HasNext(); iterator.Next()) {
uint32_t dex_pc = iterator.GetHandlerAddress(); // `DexFileVerifier` checks that the `dex_pc` is in range.
DCHECK_LT(dex_pc, code_item_accessor_.InsnsSizeInCodeUnits()); if (!GetInstructionFlags(dex_pc).IsOpcode()) {
work_insn_idx_ = dex_pc; // Let `Fail()` record the dex PC of the failing instruction.
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "exception handler starts at bad address"; returnfalse;
} if (UNLIKELY(IsMoveResult(code_item_accessor_.InstructionAt(dex_pc).Opcode()))) {
work_insn_idx_ = dex_pc; // Let `Fail()` record the dex PC of the failing instruction.
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "exception handler begins with move-result*"; returnfalse;
}
GetModifiableInstructionFlags(dex_pc).SetBranchTarget(); // Ensure exception types are resolved so that they don't need resolution to be delivered, // unresolved exception types will be ignored by exception delivery if (iterator.GetHandlerTypeIndex().IsValid()) {
ObjPtr<mirror::Class> exception_type =
linker->ResolveType(iterator.GetHandlerTypeIndex(), dex_cache_, class_loader_); if (exception_type == nullptr) {
DCHECK(self_->IsExceptionPending());
self_->ClearException();
}
}
}
handlers_ptr = iterator.EndDataPointer();
} returntrue;
}
bool MethodVerifierImpl::VerifyInstructions() {
DCHECK(switch_payload_addresses_.empty()); // Flag the start of the method as a branch target.
GetModifiableInstructionFlags(0).SetBranchTarget(); const Instruction* inst = Instruction::At(code_item_accessor_.Insns());
uint32_t dex_pc = 0u; const uint32_t end_dex_pc = code_item_accessor_.InsnsSizeInCodeUnits(); while (dex_pc != end_dex_pc) { auto find_dispatch_opcode = [](Instruction::Code opcode) constexpr { // NOP needs its own dipatch because it needs special code for instruction size. if (opcode == Instruction::NOP) { return opcode;
}
DCHECK_GT(Instruction::SizeInCodeUnits(Instruction::FormatOf(opcode)), 0u); for (uint32_t raw_other = 0; raw_other != opcode; ++raw_other) {
Instruction::Code other = enum_cast<Instruction::Code>(raw_other); if (other == Instruction::NOP) { continue;
} // We dispatch to `VerifyInstruction()` based on the format and verify flags but // we also treat return instructions separately to update instruction flags. if (Instruction::FormatOf(opcode) == Instruction::FormatOf(other) &&
Instruction::VerifyFlagsOf(opcode) == Instruction::VerifyFlagsOf(other) &&
Instruction::IsReturn(opcode) == Instruction::IsReturn(other)) { return other;
}
} return opcode;
};
template <Instruction::Code kDispatchOpcode> inlinebool MethodVerifierImpl::VerifyInstruction(uint32_t dex_pc,
uint32_t end_dex_pc, const Instruction* inst,
uint16_t inst_data) { // The `kDispatchOpcode` may differ from the actual opcode but it shall have the // same verification flags and format. We explicitly `DCHECK` these below and // the format is also `DCHECK`ed in VReg getters that take it as an argument.
constexpr Instruction::Format kFormat = Instruction::FormatOf(kDispatchOpcode);
DCHECK_EQ(kFormat, Instruction::FormatOf(inst->Opcode()));
bool result = true;
constexpr uint32_t kVerifyA = Instruction::GetVerifyTypeArgumentAOf(kDispatchOpcode);
DCHECK_EQ(kVerifyA, inst->GetVerifyTypeArgumentA()); switch (kVerifyA) { case Instruction::kVerifyRegA:
result = result && CheckRegisterIndex(inst->VRegA(kFormat, inst_data)); break; case Instruction::kVerifyRegAWide:
result = result && CheckWideRegisterIndex(inst->VRegA(kFormat, inst_data)); break; case Instruction::kVerifyNothing: break;
}
constexpr uint32_t kVerifyB = Instruction::GetVerifyTypeArgumentBOf(kDispatchOpcode);
DCHECK_EQ(kVerifyB, inst->GetVerifyTypeArgumentB()); switch (kVerifyB) { case Instruction::kVerifyRegB:
result = result && CheckRegisterIndex(inst->VRegB(kFormat, inst_data)); break; case Instruction::kVerifyRegBField:
result = result && CheckFieldIndex(inst, inst_data, inst->VRegB(kFormat, inst_data)); break; case Instruction::kVerifyRegBMethod:
result = result && CheckMethodIndex(inst->VRegB(kFormat, inst_data)); break; case Instruction::kVerifyRegBNewInstance:
result = result && CheckNewInstance(dex::TypeIndex(inst->VRegB(kFormat, inst_data))); break; case Instruction::kVerifyRegBString:
result = result && CheckStringIndex(inst->VRegB(kFormat, inst_data)); break; case Instruction::kVerifyRegBType:
result = result && CheckTypeIndex(dex::TypeIndex(inst->VRegB(kFormat, inst_data))); break; case Instruction::kVerifyRegBWide:
result = result && CheckWideRegisterIndex(inst->VRegB(kFormat, inst_data)); break; case Instruction::kVerifyRegBCallSite:
result = result && CheckCallSiteIndex(inst->VRegB(kFormat, inst_data)); break; case Instruction::kVerifyRegBMethodHandle:
result = result && CheckMethodHandleIndex(inst->VRegB(kFormat, inst_data)); break; case Instruction::kVerifyRegBPrototype:
result = result && CheckPrototypeIndex(inst->VRegB(kFormat, inst_data)); break; case Instruction::kVerifyRegBFilledNewArray:
result = result &&
CheckNewArray</*kFilled=*/ true>(dex::TypeIndex(inst->VRegB(kFormat, inst_data))); break; case Instruction::kVerifyNothing: break;
}
constexpr uint32_t kVerifyC = Instruction::GetVerifyTypeArgumentCOf(kDispatchOpcode);
DCHECK_EQ(kVerifyC, inst->GetVerifyTypeArgumentC()); switch (kVerifyC) { case Instruction::kVerifyRegC:
result = result && CheckRegisterIndex(inst->VRegC(kFormat)); break; case Instruction::kVerifyRegCField:
result = result && CheckFieldIndex(inst, inst_data, inst->VRegC(kFormat)); break; case Instruction::kVerifyRegCNewArray:
result = result && CheckNewArray</*kFilled=*/ false>(dex::TypeIndex(inst->VRegC(kFormat))); break; case Instruction::kVerifyRegCType:
result = result && CheckTypeIndex(dex::TypeIndex(inst->VRegC(kFormat))); break; case Instruction::kVerifyRegCWide:
result = result && CheckWideRegisterIndex(inst->VRegC(kFormat)); break; case Instruction::kVerifyNothing: break;
}
constexpr uint32_t kVerifyH = Instruction::GetVerifyTypeArgumentHOf(kDispatchOpcode);
DCHECK_EQ(kVerifyH, inst->GetVerifyTypeArgumentH()); switch (kVerifyH) { case Instruction::kVerifyRegHPrototype:
result = result && CheckPrototypeIndex(inst->VRegH(kFormat)); break; case Instruction::kVerifyNothing: break;
}
constexpr uint32_t kVerifyExtra = Instruction::GetVerifyExtraFlagsOf(kDispatchOpcode);
DCHECK_EQ(kVerifyExtra, inst->GetVerifyExtraFlags()); switch (kVerifyExtra) { case Instruction::kVerifyArrayData:
result = result && CheckArrayData(dex_pc, end_dex_pc, inst); break; case Instruction::kVerifyBranchTarget:
result = result && CheckAndMarkBranchTarget<kFormat>(dex_pc, end_dex_pc, inst, inst_data); break; case Instruction::kVerifySwitchTargets:
result = result && CheckAndMarkSwitchTargets(dex_pc, end_dex_pc, inst, inst_data); break; case Instruction::kVerifyVarArgNonZero: // Fall-through. case Instruction::kVerifyVarArg: { // Instructions that can actually return a negative value shouldn't have this flag.
uint32_t v_a = dchecked_integral_cast<uint32_t>(inst->VRegA(kFormat, inst_data)); if ((kVerifyExtra == Instruction::kVerifyVarArgNonZero && v_a == 0) ||
v_a > Instruction::kMaxVarArgRegs) {
FailInvalidArgCount(inst, v_a); returnfalse;
}
result = result && CheckVarArgRegs(inst, v_a); break;
} case Instruction::kVerifyVarArgRangeNonZero: // Fall-through. case Instruction::kVerifyVarArgRange: {
uint32_t v_a = inst->VRegA(kFormat, inst_data); if (inst->GetVerifyExtraFlags() == Instruction::kVerifyVarArgRangeNonZero && v_a == 0) {
FailInvalidArgCount(inst, v_a); returnfalse;
}
result = result && CheckVarArgRangeRegs(v_a, inst->VRegC(kFormat)); break;
} case Instruction::kVerifyError:
FailUnexpectedOpcode(inst);
result = false; break; case Instruction::kVerifyNothing: break;
} return result;
}
inlinebool MethodVerifierImpl::CheckNewInstance(dex::TypeIndex idx) { if (!CheckTypeIndex(idx)) { returnfalse;
} // We don't need the actual class, just a pointer to the class name. const std::string_view descriptor = dex_file_->GetTypeDescriptorView(idx); if (UNLIKELY(descriptor[0] != 'L')) {
FailBadNewInstanceDescriptor(descriptor); returnfalse;
} elseif (UNLIKELY(descriptor == "Ljava/lang/Class;")) { // An unlikely new instance on Class is not allowed.
Fail(VERIFY_ERROR_INSTANTIATION);
} returntrue;
}
template <bool kFilled> inlinebool MethodVerifierImpl::CheckNewArray(dex::TypeIndex idx) { if (!CheckTypeIndex(idx)) { returnfalse;
} constchar* descriptor = dex_file_->GetTypeDescriptor(idx); constchar* cp = descriptor; while (*cp == '[') {
++cp;
}
size_t bracket_count = static_cast<size_t>(cp - descriptor); if (UNLIKELY(bracket_count == 0u)) { /* The given class must be an array type. */
FailBadNewArrayNotArray(descriptor); returnfalse;
} elseif (UNLIKELY(bracket_count > 255u)) { /* It is illegal to create an array of more than 255 dimensions. */
FailBadNewArrayTooManyDimensions(descriptor); returnfalse;
} if (kFilled && bracket_count == 1u && UNLIKELY(*cp != 'I' && *cp != 'L')) { if (UNLIKELY(*cp == 'J') || UNLIKELY(*cp == 'D')) { // Forbidden, see https://source.android.com/docs/core/runtime/dalvik-bytecode .
FailBadFilledNewArray(descriptor); returnfalse;
} else { // Fall back to interpreter to throw `InternalError`. Compiler does not handle this case.
Fail(VERIFY_ERROR_FILLED_NEW_ARRAY);
}
} returntrue;
}
bool MethodVerifierImpl::CheckArrayData(uint32_t dex_pc,
uint32_t end_dex_pc, const Instruction* inst) {
int32_t array_data_offset = inst->VRegB_31t(); /* Make sure the start of the array data table is in range. */ if (!IsOffsetInRange(dex_pc, end_dex_pc, array_data_offset)) {
FailTargetOffsetOutOfRange(dex_pc, end_dex_pc, array_data_offset); returnfalse;
} // Make sure the array-data is marked as an opcode. // This ensures that it was reached when traversing the code in `ComputeWidthsAndCountOps()`.
uint32_t array_data_dex_pc = dex_pc + array_data_offset; if (UNLIKELY(!GetInstructionFlags(array_data_dex_pc).IsOpcode())) {
FailTargetMidInstruction(dex_pc, array_data_dex_pc); returnfalse;
} // Make sure the table is at an even dex pc, that is, 32-bit aligned. if (UNLIKELY(!IsAligned<2>(array_data_dex_pc))) {
FailUnalignedTableDexPc(dex_pc, array_data_dex_pc); returnfalse;
} const Instruction* array_data = inst->RelativeAt(array_data_offset);
DCHECK_EQ(array_data, &code_item_accessor_.InstructionAt(array_data_dex_pc));
DCHECK_ALIGNED(array_data, 4u); // Make sure the array data has the correct signature. if (UNLIKELY(array_data->Fetch16(0) != Instruction::kArrayDataSignature)) {
FailBadArrayDataSignature(dex_pc, array_data_dex_pc); returnfalse;
} // The length of the array data has been verified by `ComputeWidthsAndCountOps()`.
DCHECK_LT(array_data_dex_pc, end_dex_pc);
DCHECK_LE(array_data->SizeInCodeUnits(), end_dex_pc - array_data_dex_pc); returntrue;
}
bool MethodVerifierImpl::GetBranchOffset(uint32_t cur_offset,
int32_t* pOffset, bool* pConditional, bool* selfOkay) { const uint16_t* insns = code_item_accessor_.Insns() + cur_offset;
*pConditional = false;
*selfOkay = false; switch (*insns & 0xff) { case Instruction::GOTO:
*pOffset = ((int16_t) *insns) >> 8; break; case Instruction::GOTO_32:
*pOffset = insns[1] | (((uint32_t) insns[2]) << 16);
*selfOkay = true; break; case Instruction::GOTO_16:
*pOffset = (int16_t) insns[1]; break; case Instruction::IF_EQ: case Instruction::IF_NE: case Instruction::IF_LT: case Instruction::IF_GE: case Instruction::IF_GT: case Instruction::IF_LE: case Instruction::IF_EQZ: case Instruction::IF_NEZ: case Instruction::IF_LTZ: case Instruction::IF_GEZ: case Instruction::IF_GTZ: case Instruction::IF_LEZ:
*pOffset = (int16_t) insns[1];
*pConditional = true; break; default: returnfalse;
} returntrue;
}
bool MethodVerifierImpl::CheckAndMarkSwitchTargets(uint32_t dex_pc,
uint32_t end_dex_pc, const Instruction* inst,
uint16_t inst_data) {
int32_t switch_payload_offset = inst->VRegB_31t(); /* Make sure the start of the switch data is in range. */ if (!IsOffsetInRange(dex_pc, end_dex_pc, switch_payload_offset)) {
FailTargetOffsetOutOfRange(dex_pc, end_dex_pc, switch_payload_offset); returnfalse;
} // Make sure the switch data is marked as an opcode. // This ensures that it was reached when traversing the code in `ComputeWidthsAndCountOps()`.
uint32_t switch_payload_dex_pc = dex_pc + switch_payload_offset; if (UNLIKELY(!GetInstructionFlags(switch_payload_dex_pc).IsOpcode())) {
FailTargetMidInstruction(dex_pc, switch_payload_dex_pc); returnfalse;
} // Make sure the switch data is at an even dex pc, that is, 32-bit aligned. if (UNLIKELY(!IsAligned<2>(switch_payload_dex_pc))) {
FailUnalignedTableDexPc(dex_pc, switch_payload_dex_pc); returnfalse;
}
/* offset to switch table is a relative branch-style offset */ const Instruction* payload = inst->RelativeAt(switch_payload_offset);
DCHECK_EQ(payload, &code_item_accessor_.InstructionAt(switch_payload_dex_pc));
DCHECK_ALIGNED(payload, 4u); const uint16_t* switch_insns = reinterpret_cast<const uint16_t*>(payload);
// Might be asked to dump before the table is initialized. if (reg_table_.IsInitialized()) {
RegisterLine* reg_line = reg_table_.GetLine(dex_pc); if (reg_line != nullptr) {
vios->Stream() << reg_line->Dump(this) << "\n";
}
}
// Should have been verified earlier.
DCHECK_GE(code_item_accessor_.RegistersSize(), code_item_accessor_.InsSize());
uint32_t arg_start = code_item_accessor_.RegistersSize() - code_item_accessor_.InsSize();
size_t expected_args = code_item_accessor_.InsSize(); /* long/double count as two */
// Include the "this" pointer.
size_t cur_arg = 0; if (!IsStatic()) { if (expected_args == 0) { // Expect at least a receiver.
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "expected 0 args, but method is not static"; returnfalse;
}
// If this is a constructor for a class other than java.lang.Object, mark the first ("this") // argument as uninitialized. This restricts field access until the superclass constructor is // called. const RegType& declaring_class = GetDeclaringClass(); if (IsConstructor()) { if (declaring_class.IsJavaLangObject()) { // "this" is implicitly initialized.
reg_line->SetThisInitialized();
reg_line->SetRegisterType<LockOp::kClear>(arg_start + cur_arg, declaring_class);
} else {
reg_line->SetRegisterType<LockOp::kClear>(
arg_start + cur_arg,
reg_types_.UninitializedThisArgument(declaring_class));
}
} else {
reg_line->SetRegisterType<LockOp::kClear>(arg_start + cur_arg, declaring_class);
}
cur_arg++;
}
for (; iterator.HasNext(); iterator.Next()) { constchar* descriptor = iterator.GetDescriptor(); if (descriptor == nullptr) {
LOG(FATAL) << "Null descriptor";
} if (cur_arg >= expected_args) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "expected " << expected_args
<< " args, found more (" << descriptor << ")"; returnfalse;
} switch (descriptor[0]) { case'L': case'[': // We assume that reference arguments are initialized. The only way it could be otherwise // (assuming the caller was verified) is if the current method is <init>, but in that case // it's effectively considered initialized the instant we reach here (in the sense that we // can return without doing anything or call virtual methods).
{ // Note: don't check access. No error would be thrown for declaring or passing an // inaccessible class. Only actual accesses to fields or methods will. const RegType& reg_type = ResolveClass<CheckAccess::kNo>(iterator.GetTypeIdx()); if (!reg_type.IsNonZeroReferenceTypes()) {
DCHECK(HasFailures()); returnfalse;
}
reg_line->SetRegisterType<LockOp::kClear>(arg_start + cur_arg, reg_type);
} break; case'Z':
reg_line->SetRegisterType(arg_start + cur_arg, RegType::Kind::kBoolean); break; case'C':
reg_line->SetRegisterType(arg_start + cur_arg, RegType::Kind::kChar); break; case'B':
reg_line->SetRegisterType(arg_start + cur_arg, RegType::Kind::kByte); break; case'I':
reg_line->SetRegisterType(arg_start + cur_arg, RegType::Kind::kInteger); break; case'S':
reg_line->SetRegisterType(arg_start + cur_arg, RegType::Kind::kShort); break; case'F':
reg_line->SetRegisterType(arg_start + cur_arg, RegType::Kind::kFloat); break; case'J': case'D': { if (cur_arg + 1 >= expected_args) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "expected " << expected_args
<< " args, found more (" << descriptor << ")"; returnfalse;
}
COLD_ATTR void HandleMonitorDexPcsWorkLine(
std::vector<::art::verifier::MethodVerifier::DexLockInfo>* monitor_enter_dex_pcs,
RegisterLine* work_line) {
monitor_enter_dex_pcs->clear(); // The new work line is more accurate than the previous one.
std::map<uint32_t, ::art::verifier::MethodVerifier::DexLockInfo> depth_to_lock_info; auto collector = [&](uint32_t dex_reg, uint32_t depth) { auto insert_pair = depth_to_lock_info.emplace(
depth, ::art::verifier::MethodVerifier::DexLockInfo(depth)); auto it = insert_pair.first; auto set_insert_pair = it->second.dex_registers.insert(dex_reg);
DCHECK(set_insert_pair.second);
};
work_line->IterateRegToLockDepths(collector); for (auto& pair : depth_to_lock_info) {
monitor_enter_dex_pcs->push_back(pair.second); // Map depth to dex PC.
monitor_enter_dex_pcs->back().dex_pc = work_line->GetMonitorEnterDexPc(pair.second.dex_pc);
}
}
/* Begin by marking the first instruction as "changed". */
GetModifiableInstructionFlags(0).SetChanged();
uint32_t start_guess = 0;
/* Continue until no instructions are marked "changed". */ while (true) { if (allow_thread_suspension_) {
self_->AllowThreadSuspension();
} // Find the first marked one. Use "start_guess" as a way to find one quickly.
uint32_t insn_idx = start_guess; for (; insn_idx < insns_size; insn_idx++) { if (GetInstructionFlags(insn_idx).IsChanged()) break;
} if (insn_idx == insns_size) { if (start_guess != 0) { /* try again, starting from the top */
start_guess = 0; continue;
} else { /* all flags are clear */ break;
}
} // We carry the working set of registers from instruction to instruction. If this address can // be the target of a branch (or throw) instruction, or if we're skipping around chasing // "changed" flags, we need to load the set of registers from the table. // Because we always prefer to continue on to the next instruction, we should never have a // situation where we have a stray "changed" flag set on an instruction that isn't a branch // target.
work_insn_idx_ = insn_idx; if (GetInstructionFlags(insn_idx).IsBranchTarget()) {
work_line_->CopyFromLine(reg_table_.GetLine(insn_idx));
} elseif (kIsDebugBuild) { /* *Consistencycheck:retrievethestoredregisterline(assuming *afulltable)andmakesureitactuallymatches.
*/
RegisterLine* register_line = reg_table_.GetLine(insn_idx); if (register_line != nullptr) { if (work_line_->CompareLine(register_line) != 0) {
Dump(LOG_STREAM(FATAL_WITHOUT_ABORT));
LOG(FATAL_WITHOUT_ABORT) << InfoMessages().view();
LOG(FATAL) << "work_line diverged in " << dex_file_->PrettyMethod(dex_method_idx_)
<< "@" << reinterpret_cast<void*>(work_insn_idx_) << "\n"
<< " work_line=" << work_line_->Dump(this) << "\n"
<< " expected=" << register_line->Dump(this);
}
}
}
// 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 (kMonitorDexPCs && UNLIKELY(work_insn_idx_ == interesting_dex_pc_)) {
HandleMonitorDexPcsWorkLine(monitor_enter_dex_pcs_, work_line_.get());
}
if (UNLIKELY(!CodeFlowVerifyInstruction(&start_guess))) {
DCHECK(flags_.have_pending_hard_failure_); if (IsAotMode()) { /* When AOT compiling, check that the last failure is a hard failure */
DCHECK(!failures_.empty()); if (failures_.back().error != VERIFY_ERROR_BAD_CLASS_HARD) {
LOG(ERROR) << "Pending failures:"; for (const VerifyErrorAndMessage& veam : failures_) {
LOG(ERROR) << veam.error << " " << veam.message.view();
}
LOG(FATAL) << "Pending hard failure, but last failure not hard.";
}
} if (kVerifierDebug) {
InfoMessages() << "Rejecting opcode "
<< code_item_accessor_.InstructionAt(work_insn_idx_).DumpString(dex_file_);
}
std::string prepend(dex_file_->PrettyMethod(dex_method_idx_));
prepend += " failed to verify: ";
PrependToLastFailMessage(prepend); returnfalse;
} /* Clear "changed" and mark as visited. */
DCHECK(!flags_.have_pending_hard_failure_);
GetModifiableInstructionFlags(insn_idx).SetVisited();
GetModifiableInstructionFlags(insn_idx).ClearChanged();
}
if (kVerifierDebug) { /* *Scanfordeadcode.There'snothing"evil"aboutdeadcode *(besidesthewastedspace),butitindicatesaflawsomewhere *downtheline,possiblyintheverifier. * *Ifwe'vesubstituted"alwaysthrow"instructionsintothestream, *wearealmostcertainlygoingtohavesomedeadcode.
*/ int dead_start = -1;
int32_t branch_target = 0; bool just_set_result = false; if (kVerifierDebug) { // Generate processing back trace to debug verifier
LogVerifyInfo() << "Processing " << inst->DumpString(dex_file_) << std::endl
<< work_line_->Dump(this);
}
/* *Makeacopyofthepreviousregisterstate.Iftheinstruction *canthrowanexception,wewillcopy/mergethisintothe"catch" *addressratherthanwork_line,becausewedon'twanttheresult *fromthe"successful"codepath(e.g.acheck-castthat"improves" *atype)tobevisibletotheexceptionhandler.
*/ if (((opcode_flags & Instruction::kThrow) != 0 || IsCompatThrow(inst->Opcode())) &&
CurrentInsnFlags()->IsInTry()) {
saved_line_->CopyFromLine(work_line_.get());
} elseif (kIsDebugBuild) {
saved_line_->FillWithGarbage();
} // Per-instruction flag, should not be set here.
DCHECK(!flags_.have_pending_runtime_throw_failure_);
// 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;
usingenum RegType::Kind;
uint16_t inst_data = inst->Fetch16(0);
Instruction::Code opcode = inst->Opcode(inst_data); switch (opcode) { case Instruction::NOP: /* *A"pure"NOPhasnoeffectonanything.Datatablesstartwith *asignaturethatlookslikeaNOP;ifweseeoneofthesein *thecourseofexecutingcodethenwehaveaproblem.
*/ if (inst->VRegA_10x(inst_data) != 0) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "encountered data table in instruction stream"; returnfalse;
} break;
case Instruction::MOVE: if (!VerifyCopyCat1(inst->VRegA_12x(inst_data), inst->VRegB_12x(inst_data))) { returnfalse;
} break; case Instruction::MOVE_FROM16: if (!VerifyCopyCat1(inst->VRegA_22x(inst_data), inst->VRegB_22x())) { returnfalse;
} break; case Instruction::MOVE_16: if (!VerifyCopyCat1(inst->VRegA_32x(), inst->VRegB_32x())) { returnfalse;
} break; case Instruction::MOVE_WIDE: if (!VerifyCopyCat2(inst->VRegA_12x(inst_data), inst->VRegB_12x(inst_data))) { returnfalse;
} break; case Instruction::MOVE_WIDE_FROM16: if (!VerifyCopyCat2(inst->VRegA_22x(inst_data), inst->VRegB_22x())) { returnfalse;
} break; case Instruction::MOVE_WIDE_16: if (!VerifyCopyCat2(inst->VRegA_32x(), inst->VRegB_32x())) { returnfalse;
} break; case Instruction::MOVE_OBJECT: if (!VerifyCopyReference(inst->VRegA_12x(inst_data), inst->VRegB_12x(inst_data))) { returnfalse;
} break; case Instruction::MOVE_OBJECT_FROM16: if (!VerifyCopyReference(inst->VRegA_22x(inst_data), inst->VRegB_22x())) { returnfalse;
} break; case Instruction::MOVE_OBJECT_16: if (!VerifyCopyReference(inst->VRegA_32x(), inst->VRegB_32x())) { returnfalse;
} break;
/* *Themove-resultinstructionscopydataoutofa"pseudo-register" *withtheresultsfromthelastmethodinvocation.Inpracticewe *mightwanttoholdtheresultinanactualCPUregister,sothe *Dalvikspecrequiresthattheseonlyappearimmediatelyafteran *invokeorfilled-new-array. * *Thesecallsinvalidatethe"result"register.(Thisisnow *redundantwiththeresetdonebelow,butitcanmakethedebuginfo *easiertoreadinsomecases.)
*/ case Instruction::MOVE_RESULT:
work_line_->CopyResultRegister1(this, inst->VRegA_11x(inst_data), false); break; case Instruction::MOVE_RESULT_WIDE:
work_line_->CopyResultRegister2(this, inst->VRegA_11x(inst_data)); break; case Instruction::MOVE_RESULT_OBJECT:
work_line_->CopyResultRegister1(this, inst->VRegA_11x(inst_data), true); break;
case Instruction::MOVE_EXCEPTION: { auto result = HandleMoveException(inst); if (!result.success) { returnfalse;
}
DCHECK_NE(opcode_flags & Instruction::kContinue, 0); if (UNLIKELY(result.skip_verification_of_exception_handler)) { // Avoid verification of the following exception handler instructions.
opcode_flags &= ~Instruction::kContinue;
} break;
}
case Instruction::RETURN_VOID: if (IsInstanceConstructor() && UNLIKELY(!work_line_->CheckConstructorReturn(this))) { returnfalse;
} if (!GetMethodReturnType().IsConflict()) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "return-void not expected"; returnfalse;
} break; case Instruction::RETURN: { if (IsInstanceConstructor() && UNLIKELY(!work_line_->CheckConstructorReturn(this))) { returnfalse;
} /* 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; returnfalse;
} 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(inst_data); 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 = VerifyRegisterType(vregA, use_src ? src_type : return_type); if (!success) {
LastFailureMessageStream() << " return-1nr on invalid register v" << vregA;
}
} break;
} case Instruction::RETURN_WIDE: { if (IsInstanceConstructor() && UNLIKELY(!work_line_->CheckConstructorReturn(this))) { returnfalse;
} /* check the method signature */ const RegType& return_type = GetMethodReturnType(); if (!return_type.IsCategory2Types()) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "return-wide not expected"; returnfalse;
} else { /* check the register contents */ const uint32_t vregA = inst->VRegA_11x(inst_data); bool success = VerifyRegisterTypeWide(vregA, return_type.GetKind()); if (!success) {
LastFailureMessageStream() << " return-wide on invalid register v" << vregA;
}
} break;
} case Instruction::RETURN_OBJECT: { if (IsInstanceConstructor() && UNLIKELY(!work_line_->CheckConstructorReturn(this))) { returnfalse;
} const RegType& return_type = GetMethodReturnType(); if (!return_type.IsReferenceTypes()) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "return-object not expected"; returnfalse;
} else { /* return_type is the *expected* return type, not register value */
DCHECK(!return_type.IsZeroOrNull());
DCHECK(!return_type.IsUninitializedReference()); const uint32_t vregA = inst->VRegA_11x(inst_data); 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"; returnfalse;
} elseif (reg_type.IsConflict()) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "returning register with conflict"; returnfalse;
} elseif (reg_type.IsUninitializedTypes()) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "returning uninitialized object '"
<< reg_type << "'"; returnfalse;
} elseif (!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; returnfalse;
} elseif (!IsAssignableFrom(return_type, reg_type)) { if (reg_type.IsUnresolvedTypes() || return_type.IsUnresolvedTypes()) {
Fail(VERIFY_ERROR_UNRESOLVED_TYPE_CHECK)
<< " can't resolve returned type '" << return_type << "' or '" << reg_type << "'";
} else {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "returning '" << reg_type
<< "', but expected from declaration '" << return_type << "'"; returnfalse;
}
}
} break;
} /* could be boolean, int, float, or a null reference */ case Instruction::CONST_4: {
int32_t val = static_cast<int32_t>(inst->VRegB_11n(inst_data) << 28) >> 28;
work_line_->SetRegisterType(inst->VRegA_11n(inst_data), DetermineCat1Constant(val)); break;
} case Instruction::CONST_16: {
int16_t val = static_cast<int16_t>(inst->VRegB_21s());
work_line_->SetRegisterType(inst->VRegA_21s(inst_data), DetermineCat1Constant(val)); break;
} case Instruction::CONST: {
int32_t val = inst->VRegB_31i();
work_line_->SetRegisterType(inst->VRegA_31i(inst_data), DetermineCat1Constant(val)); break;
} case Instruction::CONST_HIGH16: {
int32_t val = static_cast<int32_t>(inst->VRegB_21h() << 16);
work_line_->SetRegisterType(inst->VRegA_21h(inst_data), DetermineCat1Constant(val)); break;
} /* could be long or double; resolved upon use */ case Instruction::CONST_WIDE_16:
work_line_->SetRegisterTypeWide(inst->VRegA_21s(inst_data), kConstantLo, kConstantHi); break; case Instruction::CONST_WIDE_32:
work_line_->SetRegisterTypeWide(inst->VRegA_31i(inst_data), kConstantLo, kConstantHi); break; case Instruction::CONST_WIDE:
work_line_->SetRegisterTypeWide(inst->VRegA_51l(inst_data), kConstantLo, kConstantHi); break; case Instruction::CONST_WIDE_HIGH16:
work_line_->SetRegisterTypeWide(inst->VRegA_21h(inst_data), kConstantLo, kConstantHi); break; case Instruction::CONST_STRING:
work_line_->SetRegisterType<LockOp::kClear>(
inst->VRegA_21c(inst_data), reg_types_.JavaLangString()); break; case Instruction::CONST_STRING_JUMBO:
work_line_->SetRegisterType<LockOp::kClear>(
inst->VRegA_31c(inst_data), 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 = ResolveClass<CheckAccess::kYes>(dex::TypeIndex(inst->VRegB_21c())); // Register holds class, ie its type is class, on error it will hold Conflict.
work_line_->SetRegisterType<LockOp::kClear>(
inst->VRegA_21c(inst_data),
res_type.IsConflict() ? res_type : reg_types_.JavaLangClass()); break;
} case Instruction::CONST_METHOD_HANDLE:
work_line_->SetRegisterType<LockOp::kClear>(
inst->VRegA_21c(inst_data), reg_types_.JavaLangInvokeMethodHandle()); break; case Instruction::CONST_METHOD_TYPE:
work_line_->SetRegisterType<LockOp::kClear>(
inst->VRegA_21c(inst_data), reg_types_.JavaLangInvokeMethodType()); break; case Instruction::MONITOR_ENTER: {
uint32_t vreg = inst->VRegA_11x(inst_data); const RegType& reg_type = work_line_->GetRegisterType(this, vreg); if (!reg_type.IsReferenceTypes()) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "monitor-enter on non-object (" << reg_type << ")"; returnfalse;
}
work_line_->PushMonitor(this, vreg, reg_type, 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 = code_item_accessor_.InstructionAt(prev_idx); switch (prev_inst.Opcode()) { case Instruction::MOVE_OBJECT: case Instruction::MOVE_OBJECT_16: case Instruction::MOVE_OBJECT_FROM16: if (static_cast<uint32_t>(prev_inst.VRegB()) == vreg) { // Redo the copy. This won't change the register types, but update the lock status // for the aliased register.
work_line_->CopyReference(prev_inst.VRegA(), vreg, reg_type);
} break;
// Catch a case of register aliasing when two registers are linked to the same // java.lang.Class object via two consequent const-class instructions immediately // preceding monitor-enter called on one of those registers. case Instruction::CONST_CLASS: { // Get the second previous instruction. if (prev_idx == 0 || GetInstructionFlags(prev_idx).IsBranchTarget()) { break;
}
prev_idx--; while (0 != prev_idx && !GetInstructionFlags(prev_idx).IsOpcode()) {
prev_idx--;
} const Instruction& prev2_inst = code_item_accessor_.InstructionAt(prev_idx);
// Match the pattern "const-class; const-class; monitor-enter;" if (prev2_inst.Opcode() != Instruction::CONST_CLASS) { break;
}
// Ensure both const-classes are called for the same type_idx. if (prev_inst.VRegB_21c() != prev2_inst.VRegB_21c()) { break;
}
// Update the lock status for the aliased register.
uint32_t prev_inst_vregA = prev_inst.VRegA_21c(prev_inst.Fetch16(0));
uint32_t prev2_inst_vregA = prev2_inst.VRegA_21c(prev2_inst.Fetch16(0)); if (prev_inst_vregA == vreg) {
work_line_->CopyReference(prev2_inst_vregA, vreg, reg_type);
} elseif (prev2_inst_vregA == vreg) {
work_line_->CopyReference(prev_inst_vregA, vreg, reg_type);
} break;
}
default: // Other instruction types ignored. break;
}
} break;
} case Instruction::MONITOR_EXIT: { /* *monitor-exitinstructionsareodd.Theycanthrowexceptions, *butwhentheydotheyactasiftheysucceededandthePCis *pointingtothefollowinginstruction.(Thisbehaviorgoesback *totheneedtohandleasynchronousexceptions,anow-deprecated *featurethatDalvikdoesn'tsupport.) * *Inpracticewedon'tneedtoworryaboutthis.Theonly *exceptionsthatcanbethrownfrommonitor-exitarefora *nullreferenceand-exitwithoutamatching-enter.Ifthe *structuredlockingchecksareworking,theformerwouldhave *failedonthe-enterinstruction,andthelatterisimpossible. * *Thisisfortunate,becauseissue3221411preventsusfrom *chasingthe"canthrow"pathwhenmonitorverificationis *enabled.Ifwecanfullyverifythelockingwecanignore *somecatchblocks(whichwillshowupas"dead"codewhen *weskipthemhere);ifwecan't,thenthecodepathcouldbe *"live"sowestillneedtocheckit.
*/
opcode_flags &= ~Instruction::kThrow;
uint32_t vreg = inst->VRegA_11x(inst_data); const RegType& reg_type = work_line_->GetRegisterType(this, vreg); if (!reg_type.IsReferenceTypes()) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "monitor-exit on non-object (" << reg_type << ")"; returnfalse;
}
work_line_->PopMonitor(this, vreg, reg_type); break;
} case Instruction::CHECK_CAST: case Instruction::INSTANCE_OF: { /* *Ifthisinstructionsucceeds,wewill"downcast"registervAtothetypeinvB.(This *couldbea"upcast"--notexpected,sowedon'ttrytoaddressit.) * *Ifitfails,anexceptionisthrown,whichwedealwithlaterbyignoringtheupdateto *dec_insn.vAwhenbranchingtoahandler.
*/ constbool is_checkcast = (inst->Opcode() == Instruction::CHECK_CAST); const dex::TypeIndex type_idx((is_checkcast) ? inst->VRegB_21c() : inst->VRegC_22c()); const RegType& res_type = ResolveClass<CheckAccess::kYes>(type_idx); if (!res_type.IsNonZeroReferenceTypes()) { // `void` (reported as conflict), or primitive type.
FailForVoidOrPrimitiveType(opcode, type_idx); returnfalse;
} // TODO: check Compiler::CanAccessTypeWithoutChecks returns false when res_type is unresolved
uint32_t orig_type_reg =
(is_checkcast) ? inst->VRegA_21c(inst_data) : inst->VRegB_22c(inst_data); const RegType& orig_type = work_line_->GetRegisterType(this, orig_type_reg); if (!orig_type.IsReferenceTypes()) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << opcode << " on non-reference in v" << orig_type_reg; returnfalse;
} elseif (orig_type.IsUninitializedTypes()) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << opcode << " on uninitialized reference in v"
<< orig_type_reg; returnfalse;
} else { if (is_checkcast) {
work_line_->SetRegisterType<LockOp::kKeep>(inst->VRegA_21c(inst_data), res_type);
} else {
work_line_->SetRegisterType(inst->VRegA_22c(inst_data), kBoolean);
}
} break;
} case Instruction::ARRAY_LENGTH: { const RegType& res_type = work_line_->GetRegisterType(this, inst->VRegB_12x(inst_data)); if (!res_type.IsReferenceTypes() || (!res_type.IsArrayTypes() && !res_type.IsZeroOrNull())) { // ie not an array or null
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "array-length on non-array " << res_type; returnfalse;
}
work_line_->SetRegisterType(inst->VRegA_12x(inst_data), kInteger); break;
} case Instruction::NEW_INSTANCE: { const RegType& res_type = ResolveClass<CheckAccess::kYes>(dex::TypeIndex(inst->VRegB_21c())); // Dex file verifier ensures that all valid type indexes reference valid descriptors and the // `CheckNewInstance()` ensures that the descriptor starts with an `L` before we get to the // code flow verification. So, we should not see a conflict (void) or a primitive type here.
DCHECK(res_type.IsJavaLangObject() ||
res_type.IsReference() ||
res_type.IsUnresolvedReference()) << res_type; // 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); // Add the new uninitialized reference to the register state and record the allocation dex pc.
uint32_t vA = inst->VRegA_21c(inst_data);
work_line_->DCheckUniqueNewInstanceDexPc(this, work_insn_idx_);
work_line_->SetRegisterTypeForNewInstance(vA, uninit_type, work_insn_idx_); break;
} case Instruction::NEW_ARRAY: { // Make sure the "size" register has a valid type. if (!VerifyRegisterType(inst->VRegB_22c(), RegType::Kind::kInteger)) { returnfalse;
} // Dex file verifier ensures that all valid type indexes reference valid descriptors and the // `CheckNewArray()` ensures that the descriptor starts with an `[` before we get to the // code flow verification. So, we should see only array types here. const RegType& res_type = ResolveClass<CheckAccess::kYes>(dex::TypeIndex(inst->VRegC_22c()));
DCHECK(res_type.IsArrayTypes()); // Set the register type to the array class.
work_line_->SetRegisterType<LockOp::kClear>(inst->VRegA_22c(), res_type); break;
} case Instruction::FILLED_NEW_ARRAY: if (!VerifyFilledNewArray(inst, /*is_range=*/ false)) { returnfalse;
}
just_set_result = true; // Filled new array sets result register break; case Instruction::FILLED_NEW_ARRAY_RANGE: if (!VerifyFilledNewArray(inst, /*is_range=*/ true)) { returnfalse;
}
just_set_result = true; // Filled new array range sets result register break; case Instruction::CMPL_FLOAT: case Instruction::CMPG_FLOAT: if (!CheckBinaryOp(inst, inst_data, kInteger, kFloat, kFloat, /*check_boolean_op=*/ false)) { returnfalse;
} break; case Instruction::CMPL_DOUBLE: case Instruction::CMPG_DOUBLE: if (!CheckBinaryOpWideCmp(inst, inst_data, kInteger, kDoubleLo, kDoubleLo)) { returnfalse;
} break; case Instruction::CMP_LONG: if (!CheckBinaryOpWideCmp(inst, inst_data, kInteger, kLongLo, kLongLo)) { returnfalse;
} break; case Instruction::THROW: { const RegType& res_type = work_line_->GetRegisterType(this, inst->VRegA_11x(inst_data)); if (!IsAssignableFrom(reg_types_.JavaLangThrowable(), res_type)) { if (res_type.IsUninitializedTypes()) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "thrown exception not initialized"; returnfalse;
} elseif (!res_type.IsReferenceTypes()) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "thrown value of non-reference type " << res_type; returnfalse;
} else { bool unresolved = res_type.IsUnresolvedTypes();
Fail(unresolved ? VERIFY_ERROR_UNRESOLVED_TYPE_CHECK : VERIFY_ERROR_BAD_CLASS_HARD)
<< "thrown class " << res_type << " not instanceof Throwable"; if (!unresolved) { returnfalse;
}
}
} 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 */
VerifyRegisterType(inst->VRegA_31t(inst_data), kInteger); break;
case Instruction::FILL_ARRAY_DATA: { /* Similar to the verification done for APUT */ const RegType& array_type = work_line_->GetRegisterType(this, inst->VRegA_31t(inst_data)); /* array_type can be null if the reg type is Zero */ if (!array_type.IsZeroOrNull()) { if (!array_type.IsArrayTypes()) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "invalid fill-array-data with array type "
<< array_type; returnfalse;
} elseif (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; returnfalse;
} else { const RegType& component_type = reg_types_.GetComponentType(array_type);
DCHECK(!component_type.IsConflict()); if (component_type.IsNonZeroReferenceTypes()) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "invalid fill-array-data with component type "
<< component_type; returnfalse;
} else { // Now verify if the element width in the table matches the element width declared in // the array. The signature has been verified by `CheckArrayData()`. const uint16_t* array_data =
insns + (insns[1] | (static_cast<int32_t>(insns[2]) << 16));
DCHECK_EQ(array_data[0], Instruction::kArrayDataSignature);
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 << ")"; returnfalse;
}
}
}
} break;
} case Instruction::IF_EQ: case Instruction::IF_NE: { // Compatibility table for comparison. Note that: // - IsIntegralTypes includes IsZero, and IsReferenceTypes includes both IsZero/IsNull // - null is comparable with the same types as non-null reference, in particular, // it's not comparable with non-zero integral types // // Abbreviations: Z: IsZero, N: IsNull, R: IsReferenceTypes, I: IsIntegralTypes, X: other. // // | Z N R-{Z,N} I-{Z} X // --------|---------------------------------- // Z | . . . . x // N | . . . x x // R-{Z,N} | . . . x x // I-{Z} | . x x . x // X | x x x x x // const RegType& reg_type1 = work_line_->GetRegisterType(this, inst->VRegA_22t(inst_data)); const RegType& reg_type2 = work_line_->GetRegisterType(this, inst->VRegB_22t(inst_data)); bool mismatch = false; if (reg_type1.IsZero()) { // zero then integral or reference expected
mismatch = !reg_type2.IsReferenceTypes() && !reg_type2.IsIntegralTypes();
} elseif (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"; returnfalse;
} 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(inst_data)); const RegType& reg_type2 = work_line_->GetRegisterType(this, inst->VRegB_22t(inst_data)); if (!reg_type1.IsIntegralTypes() || !reg_type2.IsIntegralTypes()) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "args to 'if' (" << reg_type1 << ","
<< reg_type2 << ") must be integral"; returnfalse;
} break;
} case Instruction::IF_EQZ: case Instruction::IF_NEZ: { const RegType& reg_type = work_line_->GetRegisterType(this, inst->VRegA_21t(inst_data)); if (!reg_type.IsReferenceTypes() && !reg_type.IsIntegralTypes()) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "type " << reg_type
<< " unexpected as arg to if-eqz/if-nez"; returnfalse;
}
// Find previous instruction - its existence is a precondition to peephole optimization. if (UNLIKELY(0 == work_insn_idx_)) { break;
}
uint32_t instance_of_idx = work_insn_idx_ - 1; while (0 != instance_of_idx && !GetInstructionFlags(instance_of_idx).IsOpcode()) {
instance_of_idx--;
} // Dex index 0 must be an opcode.
DCHECK(GetInstructionFlags(instance_of_idx).IsOpcode());
/* Check for peep-hole pattern of: *...; *instance-ofvX,vY,T; *ifXXXvX,label; *...; *label: *...; *andsharpenthetypeofvYtobetypeT. *Note,thispatterncan'tbeif: *-ifthereareotherbranchestothisbranch, *-whenvX==vY.
*/ if (!CurrentInsnFlags()->IsBranchTarget() &&
(Instruction::INSTANCE_OF == instance_of_inst.Opcode()) &&
(inst->VRegA_21t(inst_data) == 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. // // Note: do not do an access check. This may mark this with a runtime throw that actually // happens at the instanceof, not the branch (and branches aren't flagged to throw). const RegType& orig_type = work_line_->GetRegisterType(this, instance_of_inst.VRegB_22c()); const RegType& cast_type = ResolveClass<CheckAccess::kNo>(
dex::TypeIndex(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.IsZeroOrNull() &&
IsStrictlyAssignableFrom(orig_type, cast_type.Merge(orig_type, ®_types_, this))) {
RegisterLine* update_line = RegisterLine::Create(code_item_accessor_.RegistersSize(),
allocator_); 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>(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. // Note: this is only valid if the move source was not clobbered.
uint32_t move_idx = instance_of_idx - 1; while (0 != move_idx && !GetInstructionFlags(move_idx).IsOpcode()) {
move_idx--;
}
DCHECK(GetInstructionFlags(move_idx).IsOpcode()); auto maybe_update_fn = [&instance_of_inst, update_line, &cast_type](
uint16_t move_src,
uint16_t move_trg)
REQUIRES_SHARED(Locks::mutator_lock_) { if (move_trg == instance_of_inst.VRegB_22c() &&
move_src != instance_of_inst.VRegA_22c()) {
update_line->SetRegisterType<LockOp::kKeep>(move_src, cast_type);
}
}; const Instruction& move_inst = code_item_accessor_.InstructionAt(move_idx); switch (move_inst.Opcode()) { case Instruction::MOVE_OBJECT:
maybe_update_fn(move_inst.VRegB_12x(), move_inst.VRegA_12x()); break; case Instruction::MOVE_OBJECT_FROM16:
maybe_update_fn(move_inst.VRegB_22x(), move_inst.VRegA_22x()); break; case Instruction::MOVE_OBJECT_16:
maybe_update_fn(move_inst.VRegB_32x(), move_inst.VRegA_32x()); 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(inst_data)); 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"; returnfalse;
} break;
} case Instruction::AGET_BOOLEAN: case Instruction::AGET_BYTE: case Instruction::AGET_CHAR: case Instruction::AGET_SHORT: if (!VerifyArrayAccess<AccessType::kGet,
AccessWidth::kNarrow, /*kIsPrimitive=*/ true>(inst, inst_data, opcode)) { returnfalse;
} break; case Instruction::AGET: if (!VerifyArrayAccess<AccessType::kGet,
AccessWidth::kVreg, /*kIsPrimitive=*/ true>(inst, inst_data, opcode)) { returnfalse;
} break; case Instruction::AGET_WIDE: if (!VerifyArrayAccess<AccessType::kGet,
AccessWidth::kWide, /*kIsPrimitive=*/ true>(inst, inst_data, opcode)) { returnfalse;
} break; case Instruction::AGET_OBJECT: if (!VerifyArrayAccess<AccessType::kGet,
AccessWidth::kVreg, /*kIsPrimitive=*/ false>(inst, inst_data, opcode)) { returnfalse;
} break;
case Instruction::APUT_BOOLEAN: case Instruction::APUT_BYTE: case Instruction::APUT_CHAR: case Instruction::APUT_SHORT: if (!VerifyArrayAccess<AccessType::kPut,
AccessWidth::kNarrow, /*kIsPrimitive=*/ true>(inst, inst_data, opcode)) { returnfalse;
} break; case Instruction::APUT: if (!VerifyArrayAccess<AccessType::kPut,
AccessWidth::kVreg, /*kIsPrimitive=*/ true>(inst, inst_data, opcode)) { returnfalse;
} break; case Instruction::APUT_WIDE: if (!VerifyArrayAccess<AccessType::kPut,
AccessWidth::kWide, /*kIsPrimitive=*/ true>(inst, inst_data, opcode)) { returnfalse;
} break; case Instruction::APUT_OBJECT: if (!VerifyArrayAccess<AccessType::kPut,
AccessWidth::kVreg, /*kIsPrimitive=*/ false>(inst, inst_data, opcode)) { returnfalse;
} break;
case Instruction::IGET_BOOLEAN: case Instruction::IGET_BYTE: case Instruction::IGET_CHAR: case Instruction::IGET_SHORT: if (!VerifyISFieldAccess<AccessType::kGet,
AccessWidth::kNarrow, /*kIsStatic=*/ false, /*kIsPrimitive=*/ true>(inst, inst_data, opcode)) { returnfalse;
} break; case Instruction::IGET: if (!VerifyISFieldAccess<AccessType::kGet,
AccessWidth::kVreg, /*kIsStatic=*/ false, /*kIsPrimitive=*/ true>(inst, inst_data, opcode)) { returnfalse;
} break; case Instruction::IGET_WIDE: if (!VerifyISFieldAccess<AccessType::kGet,
AccessWidth::kWide, /*kIsStatic=*/ false, /*kIsPrimitive=*/ true>(inst, inst_data, opcode)) { returnfalse;
} break; case Instruction::IGET_OBJECT: if (!VerifyISFieldAccess<AccessType::kGet,
AccessWidth::kVreg, /*kIsStatic=*/ false, /*kIsPrimitive=*/ false>(inst, inst_data, opcode)) { returnfalse;
} break;
case Instruction::IPUT_BOOLEAN: case Instruction::IPUT_BYTE: case Instruction::IPUT_CHAR: case Instruction::IPUT_SHORT: if (!VerifyISFieldAccess<AccessType::kPut,
AccessWidth::kNarrow, /*kIsStatic=*/ false, /*kIsPrimitive=*/ true>(inst, inst_data, opcode)) { returnfalse;
} break; case Instruction::IPUT: if (!VerifyISFieldAccess<AccessType::kPut,
AccessWidth::kVreg, /*kIsStatic=*/ false, /*kIsPrimitive=*/ true>(inst, inst_data, opcode)) { returnfalse;
} break; case Instruction::IPUT_WIDE: if (!VerifyISFieldAccess<AccessType::kPut,
AccessWidth::kWide, /*kIsStatic=*/ false, /*kIsPrimitive=*/ true>(inst, inst_data, opcode)) { returnfalse;
} break; case Instruction::IPUT_OBJECT: if (!VerifyISFieldAccess<AccessType::kPut,
AccessWidth::kVreg, /*kIsStatic=*/ false, /*kIsPrimitive=*/ false>(inst, inst_data, opcode)) { returnfalse;
} break;
case Instruction::SGET_BOOLEAN: case Instruction::SGET_BYTE: case Instruction::SGET_CHAR: case Instruction::SGET_SHORT: if (!VerifyISFieldAccess<AccessType::kGet,
AccessWidth::kNarrow, /*kIsStatic=*/ true, /*kIsPrimitive=*/ true>(inst, inst_data, opcode)) { returnfalse;
} break; case Instruction::SGET: if (!VerifyISFieldAccess<AccessType::kGet,
AccessWidth::kVreg, /*kIsStatic=*/ true, /*kIsPrimitive=*/ true>(inst, inst_data, opcode)) { returnfalse;
} break; case Instruction::SGET_WIDE: if (!VerifyISFieldAccess<AccessType::kGet,
AccessWidth::kWide, /*kIsStatic=*/ true, /*kIsPrimitive=*/ true>(inst, inst_data, opcode)) { returnfalse;
} break; case Instruction::SGET_OBJECT: if (!VerifyISFieldAccess<AccessType::kGet,
AccessWidth::kVreg, /*kIsStatic=*/ true, /*kIsPrimitive=*/ false>(inst, inst_data, opcode)) { returnfalse;
} break;
case Instruction::SPUT_BOOLEAN: case Instruction::SPUT_BYTE: case Instruction::SPUT_CHAR: case Instruction::SPUT_SHORT: if (!VerifyISFieldAccess<AccessType::kPut,
AccessWidth::kNarrow, /*kIsStatic=*/ true, /*kIsPrimitive=*/ true>(inst, inst_data, opcode)) { returnfalse;
} break; case Instruction::SPUT: if (!VerifyISFieldAccess<AccessType::kPut,
AccessWidth::kVreg, /*kIsStatic=*/ true, /*kIsPrimitive=*/ true>(inst, inst_data, opcode)) { returnfalse;
} break; case Instruction::SPUT_WIDE: if (!VerifyISFieldAccess<AccessType::kPut,
AccessWidth::kWide, /*kIsStatic=*/ true, /*kIsPrimitive=*/ true>(inst, inst_data, opcode)) { returnfalse;
} break; case Instruction::SPUT_OBJECT: if (!VerifyISFieldAccess<AccessType::kPut,
AccessWidth::kVreg, /*kIsStatic=*/ true, /*kIsPrimitive=*/ false>(inst, inst_data, opcode)) { returnfalse;
} break;
/* no null refs allowed (?) */ if (this_type.IsZeroOrNull()) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "unable to initialize null ref"; returnfalse;
}
/* arg must be an uninitialized reference */ if (!this_type.IsUninitializedTypes()) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "Expected initialization on uninitialized reference "
<< this_type; returnfalse;
}
// Note: According to JLS, constructors are never inherited. Therefore the target // constructor should be defined exactly by the `this_type`, or by the direct // superclass in the case of a constructor calling the superclass constructor. // However, ART had this check commented out for a very long time and this has // allowed bytecode optimizers such as R8 to inline constructors, often calling // `j.l.Object.<init>` directly without any intermediate constructor. Since this // optimization allows eliminating constructor methods, this often results in a // significant dex size reduction. Therefore it is undesirable to reinstate this // check and ART deliberately remains permissive here and diverges from the RI.
/* *Replacetheuninitializedreferencewithaninitializedone.Weneedtodothisforall *registersthathavethesameobjectinstanceinthem,notjustthe"this"register.
*/
work_line_->MarkRefsAsInitialized(this, inst->VRegC());
} const RegType& return_type = reg_types_.FromTypeIndex(return_type_idx); if (!return_type.IsLowHalf()) {
work_line_->SetResultRegisterType(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);
uint32_t method_idx = (is_range) ? inst->VRegB_3rc() : inst->VRegB_35c(); const dex::MethodId& method_id = dex_file_->GetMethodId(method_idx);
dex::TypeIndex return_type_idx = dex_file_->GetProtoId(method_id.proto_idx_).return_type_idx_;
DCHECK_IMPLIES(called_method != nullptr,
called_method->GetReturnTypeDescriptorView() ==
dex_file_->GetTypeDescriptorView(return_type_idx)); const RegType& return_type = reg_types_.FromTypeIndex(return_type_idx); if (!return_type.IsLowHalf()) {
work_line_->SetResultRegisterType(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) {
ObjPtr<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 '"
<< abs_method->PrettyMethod() << "'"; break;
}
} /* Get the type of the "this" arg, which should either be a sub-interface of called *interfaceorObject(seecommentsinRegType::JoinClass).
*/ const RegType& this_type = GetInvocationThis(inst); if (this_type.IsZeroOrNull()) { /* 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; returnfalse;
} // 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.
} /* *Wedon'thaveanobjectinstance,sowecan'tfindtheconcretemethod.However,allof *thetypeinformationisintheabstractmethod,sowe'regood.
*/
uint32_t method_idx = (is_range) ? inst->VRegB_3rc() : inst->VRegB_35c(); const dex::MethodId& method_id = dex_file_->GetMethodId(method_idx);
dex::TypeIndex return_type_idx = dex_file_->GetProtoId(method_id.proto_idx_).return_type_idx_;
DCHECK_IMPLIES(abs_method != nullptr,
abs_method->GetReturnTypeDescriptorView() ==
dex_file_->GetTypeDescriptorView(return_type_idx)); const RegType& return_type = reg_types_.FromTypeIndex(return_type_idx); if (!return_type.IsLowHalf()) {
work_line_->SetResultRegisterType(return_type);
} else {
work_line_->SetResultRegisterTypeWide(return_type, return_type.HighHalf(®_types_));
}
just_set_result = true; break;
} case Instruction::INVOKE_POLYMORPHIC: case Instruction::INVOKE_POLYMORPHIC_RANGE: { bool is_range = (inst->Opcode() == Instruction::INVOKE_POLYMORPHIC_RANGE);
ArtMethod* called_method = VerifyInvocationArgs(inst, METHOD_POLYMORPHIC, is_range); if (called_method == nullptr) { // Convert potential soft failures in VerifyInvocationArgs() to hard errors.
std::string_view message = failures_.empty() ? "invoke-polymorphic verification failure."
: failures_.back().message.view(); // Note: Adding another failure to `failures_` does not invalidate the view of // the previous message (if any) - the list node holding it is not even moved.
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << message; returnfalse;
} if (!CheckSignaturePolymorphicMethod(called_method) ||
!CheckSignaturePolymorphicReceiver(inst)) {
DCHECK(HasFailures()); break;
} const dex::ProtoIndex proto_idx((is_range) ? inst->VRegH_4rcc() : inst->VRegH_45cc()); const RegType& return_type =
reg_types_.FromTypeIndex(dex_file_->GetProtoId(proto_idx).return_type_idx_); if (!return_type.IsLowHalf()) {
work_line_->SetResultRegisterType(return_type);
} else {
work_line_->SetResultRegisterTypeWide(return_type, return_type.HighHalf(®_types_));
}
just_set_result = true; break;
} case Instruction::INVOKE_CUSTOM: case Instruction::INVOKE_CUSTOM_RANGE: { // Verify registers based on method_type in the call site. bool is_range = (inst->Opcode() == Instruction::INVOKE_CUSTOM_RANGE);
// Step 1. Check the call site that produces the method handle for invocation const uint32_t call_site_idx = is_range ? inst->VRegB_3rc() : inst->VRegB_35c(); if (!CheckCallSite(call_site_idx)) {
DCHECK(HasFailures()); break;
}
// Step 2. Check the register arguments correspond to the expected arguments for the // method handle produced by step 1. The dex file verifier has checked ranges for // the first three arguments and CheckCallSite has checked the method handle type. const dex::ProtoIndex proto_idx = dex_file_->GetProtoIndexForCallSite(call_site_idx); const dex::ProtoId& proto_id = dex_file_->GetProtoId(proto_idx);
DexFileParameterIterator param_it(*dex_file_, proto_id); // Treat method as static as it has yet to be determined.
VerifyInvocationArgsFromIterator(¶m_it, inst, METHOD_STATIC, is_range, nullptr);
// Step 3. Propagate return type information const RegType& return_type = reg_types_.FromTypeIndex(proto_id.return_type_idx_); if (!return_type.IsLowHalf()) {
work_line_->SetResultRegisterType(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: if (!CheckUnaryOp(inst, inst_data, kInteger, kInteger)) { returnfalse;
} break; case Instruction::NEG_LONG: case Instruction::NOT_LONG: if (!CheckUnaryOpWide(inst, inst_data, kLongLo, kLongLo)) { returnfalse;
} break; case Instruction::NEG_FLOAT: if (!CheckUnaryOp(inst, inst_data, kFloat, kFloat)) { returnfalse;
} break; case Instruction::NEG_DOUBLE: if (!CheckUnaryOpWide(inst, inst_data, kDoubleLo, kDoubleLo)) { returnfalse;
} break; case Instruction::INT_TO_LONG: if (!CheckUnaryOpToWide(inst, inst_data, kLongLo, kInteger)) { returnfalse;
} break; case Instruction::INT_TO_FLOAT: if (!CheckUnaryOp(inst, inst_data, kFloat, kInteger)) { returnfalse;
} break; case Instruction::INT_TO_DOUBLE: if (!CheckUnaryOpToWide(inst, inst_data, kDoubleLo, kInteger)) { returnfalse;
} break; case Instruction::LONG_TO_INT: if (!CheckUnaryOpFromWide(inst, inst_data, kInteger, kLongLo)) { returnfalse;
} break; case Instruction::LONG_TO_FLOAT: if (!CheckUnaryOpFromWide(inst, inst_data, kFloat, kLongLo)) { returnfalse;
} break; case Instruction::LONG_TO_DOUBLE: if (!CheckUnaryOpWide(inst, inst_data, kDoubleLo, kLongLo)) { returnfalse;
} break; case Instruction::FLOAT_TO_INT: if (!CheckUnaryOp(inst, inst_data, kInteger, kFloat)) { returnfalse;
} break; case Instruction::FLOAT_TO_LONG: if (!CheckUnaryOpToWide(inst, inst_data, kLongLo, kFloat)) { returnfalse;
} break; case Instruction::FLOAT_TO_DOUBLE: if (!CheckUnaryOpToWide(inst, inst_data, kDoubleLo, kFloat)) { returnfalse;
} break; case Instruction::DOUBLE_TO_INT: if (!CheckUnaryOpFromWide(inst, inst_data, kInteger, kDoubleLo)) { returnfalse;
} break; case Instruction::DOUBLE_TO_LONG: if (!CheckUnaryOpWide(inst, inst_data, kLongLo, kDoubleLo)) { returnfalse;
} break; case Instruction::DOUBLE_TO_FLOAT: if (!CheckUnaryOpFromWide(inst, inst_data, kFloat, kDoubleLo)) { returnfalse;
} break; case Instruction::INT_TO_BYTE: if (!CheckUnaryOp(inst, inst_data, kByte, kInteger)) { returnfalse;
} break; case Instruction::INT_TO_CHAR: if (!CheckUnaryOp(inst, inst_data, kChar, kInteger)) { returnfalse;
} break; case Instruction::INT_TO_SHORT: if (!CheckUnaryOp(inst, inst_data, kShort, kInteger)) { returnfalse;
} 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: if (!CheckBinaryOp(
inst, inst_data, kInteger, kInteger, kInteger, /*check_boolean_op=*/ false)) { returnfalse;
} break; case Instruction::AND_INT: case Instruction::OR_INT: case Instruction::XOR_INT: if (!CheckBinaryOp(
inst, inst_data, kInteger, kInteger, kInteger, /*check_boolean_op=*/ true)) { returnfalse;
} 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: if (!CheckBinaryOpWide(inst, inst_data, kLongLo, kLongLo, kLongLo)) { returnfalse;
} break; case Instruction::SHL_LONG: case Instruction::SHR_LONG: case Instruction::USHR_LONG: /* shift distance is Int, making these different from other binary operations */ if (!CheckBinaryOpWideShift(inst, inst_data, kLongLo, kInteger)) { returnfalse;
} break; case Instruction::ADD_FLOAT: case Instruction::SUB_FLOAT: case Instruction::MUL_FLOAT: case Instruction::DIV_FLOAT: case Instruction::REM_FLOAT: if (!CheckBinaryOp(inst, inst_data, kFloat, kFloat, kFloat, /*check_boolean_op=*/ false)) { returnfalse;
} break; case Instruction::ADD_DOUBLE: case Instruction::SUB_DOUBLE: case Instruction::MUL_DOUBLE: case Instruction::DIV_DOUBLE: case Instruction::REM_DOUBLE: if (!CheckBinaryOpWide(inst, inst_data, kDoubleLo, kDoubleLo, kDoubleLo)) { returnfalse;
} 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: if (!CheckBinaryOp2addr(
inst, inst_data, kInteger, kInteger, kInteger, /*check_boolean_op=*/ false)) { returnfalse;
} break; case Instruction::AND_INT_2ADDR: case Instruction::OR_INT_2ADDR: case Instruction::XOR_INT_2ADDR: if (!CheckBinaryOp2addr(
inst, inst_data, kInteger, kInteger, kInteger, /*check_boolean_op=*/ true)) { returnfalse;
} break; case Instruction::DIV_INT_2ADDR: if (!CheckBinaryOp2addr(
inst, inst_data, kInteger, kInteger, kInteger, /*check_boolean_op=*/ false)) { returnfalse;
} 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: if (!CheckBinaryOp2addrWide(inst, inst_data, kLongLo, kLongLo, kLongLo)) { returnfalse;
} break; case Instruction::SHL_LONG_2ADDR: case Instruction::SHR_LONG_2ADDR: case Instruction::USHR_LONG_2ADDR: if (!CheckBinaryOp2addrWideShift(inst, inst_data, kLongLo, kInteger)) { returnfalse;
} 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: if (!CheckBinaryOp2addr(
inst, inst_data, kFloat, kFloat, kFloat, /*check_boolean_op=*/ false)) { returnfalse;
} 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: if (!CheckBinaryOp2addrWide(inst, inst_data, kDoubleLo, kDoubleLo, kDoubleLo)) { returnfalse;
} 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: if (!CheckLiteralOp</*kIsLit16=*/ true>(
inst, inst_data, kInteger, kInteger, /*check_boolean_op=*/ false)) { returnfalse;
} break; case Instruction::AND_INT_LIT16: case Instruction::OR_INT_LIT16: case Instruction::XOR_INT_LIT16: if (!CheckLiteralOp</*kIsLit16=*/ true>(
inst, inst_data, kInteger, kInteger, /*check_boolean_op=*/ true)) { returnfalse;
} 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: if (!CheckLiteralOp</*kIsLit16=*/ false>(
inst, inst_data, kInteger, kInteger, /*check_boolean_op=*/ false)) { returnfalse;
} break; case Instruction::AND_INT_LIT8: case Instruction::OR_INT_LIT8: case Instruction::XOR_INT_LIT8: if (!CheckLiteralOp</*kIsLit16=*/ false>(
inst, inst_data, kInteger, kInteger, /*check_boolean_op=*/ true)) { returnfalse;
} break;
/* These should never appear during verification. */ case Instruction::UNUSED_3E ... Instruction::UNUSED_43: case Instruction::UNUSED_E3 ... Instruction::UNUSED_F9: case Instruction::UNUSED_73: case Instruction::UNUSED_79: case Instruction::UNUSED_7A:
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "Unexpected opcode " << inst->DumpString(dex_file_); returnfalse;
/* *DONOTadda"default"clausehere.Withoutitthecompilerwill *complainifaninstructionismissing(whichisdesirable).
*/
} // end - switch (dec_insn.opcode)
if (flags_.have_pending_hard_failure_) { /* immediate failure, reject class */ returnfalse;
} elseif (flags_.have_pending_runtime_throw_failure_) {
LogVerifyInfo() << "Elevating opcode flags from " << opcode_flags << " to Throw"; /* 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.
} /* *Ifwedidn'tjustsettheresultregister,clearitout.Thisensuresthatyoucanonlyuse *"move-result"immediatelyaftertheresultisset.(Wecouldcheckthisstatically,butit's *notexpensiveanditmakesourdebuggingoutputcleaner.)
*/ if (!just_set_result) {
work_line_->SetResultTypeToUnknown();
}
/* *Handle"branch".Tagthebranchtarget. * *NOTE:instructionslikeInstruction::EQZprovideinformationaboutthe *stateoftheregisterwhenthebranchistakenornottaken.Forexample, *somebodycouldgetareferencefield,checkitforzero,andifthe *branchistakenimmediatelystorethatregisterinabooleanfield *sincethevalueisknowntobezero.Wedonotcurrentlyaccountfor *that,andwillrejectthecode. * *TODO:avoidre-fetchingthebranchtarget
*/ 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"; returnfalse;
}
DCHECK_EQ(isConditional, (opcode_flags & Instruction::kContinue) != 0);
DCHECK(!IsMoveResultOrMoveException(inst->RelativeAt(branch_target)->Opcode())); /* update branch target, set "changed" if appropriate */ if (nullptr != branch_line) {
UpdateRegisters(work_insn_idx_ + branch_target, branch_line.get(), false);
} else {
UpdateRegisters(work_insn_idx_ + branch_target, work_line_.get(), false);
}
}
/* *Handle"switch".Tagallpossiblebranchtargets. * *We'vealreadyverifiedthatthetableisstructurallysound,sowe *justneedtowalkthroughandtagthetargets.
*/ 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;
// Need the linker to try and resolve the handled class to check if it's Throwable.
ClassLinker* linker = GetClassLinker();
for (; iterator.HasNext(); iterator.Next()) {
dex::TypeIndex handler_type_idx = iterator.GetHandlerTypeIndex(); if (!handler_type_idx.IsValid()) {
has_catch_all_handler = true;
} else { // It is also a catch-all if it is java.lang.Throwable.
ObjPtr<mirror::Class> klass =
linker->ResolveType(handler_type_idx, dex_cache_, class_loader_); if (klass != nullptr) { if (klass == GetClassRoot<mirror::Throwable>()) {
has_catch_all_handler = true;
}
} else { // Clear exception.
DCHECK(self_->IsExceptionPending());
self_->ClearException();
}
} /* *Mergeregistersintothe"catch"block.Wewanttousethe"savedRegs"ratherthan *"work_regs",becauseatruntimetheexceptionwillbethrownbeforetheinstruction *modifiesanyregisters.
*/ if (kVerifierDebug) {
LogVerifyInfo() << "Updating exception handler 0x"
<< std::hex << iterator.GetHandlerAddress();
}
UpdateRegisters(iterator.GetHandlerAddress(), saved_line_.get(), false);
}
/* *Ifthemonitorstackdepthisnonzero,theremustbea"catchall"handlerforthis *instruction.Thisdoesapplytomonitor-exitbecauseofasyncexceptionhandling.
*/ if (work_line_->MonitorStackDepth() > 0 && !has_catch_all_handler) { /* *Thestateinwork_linereflectsthepost-executionstate.Ifthecurrentinstructionisa *monitor-enterandthemonitorstackwasempty,wedon'tneedacatch-all(ifitthrows, *itwilldosobeforegrabbingthelock).
*/ 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"; returnfalse;
}
}
}
/* Handle "continue". Tag the next consecutive instruction. *Note:Keepthecodehandling"continue"casebelowthe"branch"and"switch"cases, *becauseitchangeswork_line_whenperformingpeepholeoptimization *andthischangeshouldnotbeusedinthosecases.
*/ if ((opcode_flags & Instruction::kContinue) != 0) {
DCHECK_EQ(&code_item_accessor_.InstructionAt(work_insn_idx_), inst);
uint32_t next_insn_idx = work_insn_idx_ + inst->SizeInCodeUnits(); if (next_insn_idx >= code_item_accessor_.InsnsSizeInCodeUnits()) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "Can flow through to end of code area"; returnfalse;
} // The only way to get to a move-exception instruction is to get thrown there. Make sure the // next instruction isn't one.
Instruction::Code next_opcode = code_item_accessor_.InstructionAt(next_insn_idx).Opcode(); if (UNLIKELY(next_opcode == Instruction::MOVE_EXCEPTION)) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "Can flow through to move-exception"; returnfalse;
} if (nullptr != fallthrough_line) { // Make workline consistent with fallthrough computed from peephole optimization.
work_line_->CopyFromLine(fallthrough_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.
UpdateRegisters(next_insn_idx, work_line_.get(), true);
} else { /* *We'renotrecordingregisterdataforthenextinstruction,sowedon'tknowwhatthe *priorstatewas.Wehavetoassumethatsomethinghaschangedandre-evaluateit.
*/
GetModifiableInstructionFlags(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);
}
/* *Updatestart_guess.Advancetothenextinstructionofthat's *possible,otherwiseusethebranchtargetifonewasfound.If *neitherofthoseexistswe'reinareturnorthrow;leavestart_guess *aloneandletthecallersortitout.
*/ if ((opcode_flags & Instruction::kContinue) != 0) {
DCHECK_EQ(&code_item_accessor_.InstructionAt(work_insn_idx_), inst);
*start_guess = work_insn_idx_ + inst->SizeInCodeUnits();
} elseif ((opcode_flags & Instruction::kBranch) != 0) { /* we're still okay if branch_target is zero */
*start_guess = work_insn_idx_ + branch_target;
}
if (flags_.have_pending_runtime_throw_failure_) {
Fail(VERIFY_ERROR_RUNTIME_THROW, /* pending_exc= */ false); // Reset the pending_runtime_throw flag now.
flags_.have_pending_runtime_throw_failure_ = false;
}
returntrue;
} // NOLINT(readability/fn_size)
template <CheckAccess C> const RegType& MethodVerifierImpl::ResolveClass(dex::TypeIndex class_idx) { // FIXME: `RegTypeCache` can currently return a few fundamental classes such as j.l.Object // or j.l.Class without resolving them using the current class loader and recording them // in the corresponding `ClassTable`. The subsequent method and field lookup by callers of // `ResolveClass<>()` can then put their methods and fields to the `DexCache` which should // not be done for classes that are not in the `ClassTable`, potentially leading to crashes. // For now, we force the class resolution here to avoid the inconsistency. // Note that there's nothing we can do if we cannot load classes. (The only code path that // does not allow loading classes is `FindLocksAtDexPc()` which should really need only to // distinguish between reference and non-reference types and track locking. All the other // work, including class lookup, is unnecessary as the class has already been verified.) if (CanLoadClasses()) {
ClassLinker* linker = GetClassLinker();
ObjPtr<mirror::Class> klass = linker->ResolveType(class_idx, dex_cache_, class_loader_); if (klass == nullptr) {
DCHECK(self_->IsExceptionPending());
self_->ClearException();
}
}
const RegType& result = reg_types_.FromTypeIndex(class_idx); if (result.IsConflict()) { constchar* descriptor = dex_file_->GetTypeDescriptor(class_idx);
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "accessing broken descriptor '" << descriptor
<< "' in " << GetDeclaringClass(); return result;
}
// If requested, check if access is allowed. Unresolved types are included in this check, as the // interpreter only tests whether access is allowed when a class is not pre-verified and runs in // the access-checks interpreter. If result is primitive, skip the access check. // // Note: we do this for unresolved classes to trigger re-verification at runtime. if (C != CheckAccess::kNo &&
result.IsNonZeroReferenceTypes() &&
((C == CheckAccess::kYes && IsSdkVersionSetAndAtLeast(api_level_, SdkVersion::kP))
|| !result.IsUnresolvedTypes())) { const RegType& referrer = GetDeclaringClass(); if ((IsSdkVersionSetAndAtLeast(api_level_, SdkVersion::kP) || !referrer.IsUnresolvedTypes()) &&
!CanAccess(result)) { if (IsAotMode()) {
Fail(VERIFY_ERROR_ACCESS_CLASS);
VLOG(verifier)
<< "(possibly) illegal class access: '" << referrer << "' -> '" << result << "'";
} else {
Fail(VERIFY_ERROR_ACCESS_CLASS)
<< "(possibly) illegal class access: '" << referrer << "' -> '" << result << "'";
}
}
} return result;
}
MethodVerifierImpl::HandleMoveExceptionResult
MethodVerifierImpl::HandleMoveException(const Instruction* inst) { // 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"; return {false, false};
} /* *Thisstatementcanonlyappearasthefirstinstructioninanexceptionhandler.Weverify *thataspartofextractingtheexceptiontypefromthecatchblocklist.
*/ const RegType* common_super = nullptr; const RegType* unresolved = nullptr; if (code_item_accessor_.TriesSize() != 0) { const uint8_t* handlers_ptr = code_item_accessor_.GetCatchHandlerData();
uint32_t handlers_size = DecodeUnsignedLeb128(&handlers_ptr); for (uint32_t i = 0; i < handlers_size; i++) {
CatchHandlerIterator iterator(handlers_ptr); for (; iterator.HasNext(); iterator.Next()) { if (iterator.GetHandlerAddress() == (uint32_t) work_insn_idx_) { if (!iterator.GetHandlerTypeIndex().IsValid()) {
common_super = ®_types_.JavaLangThrowable();
} else { // Do access checks only on resolved exception classes. const RegType& exception =
ResolveClass<CheckAccess::kOnResolvedClass>(iterator.GetHandlerTypeIndex()); if (!IsAssignableFrom(reg_types_.JavaLangThrowable(), exception)) {
DCHECK(!exception.IsUninitializedTypes()); // Comes from dex, shouldn't be uninit. if (exception.IsUnresolvedTypes()) { if (unresolved == nullptr) {
unresolved = &exception;
} else {
unresolved = &unresolved->SafeMerge(exception, ®_types_, this);
}
} else {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "unexpected non-throwable class "
<< exception; return {false, false};
}
} elseif (common_super == nullptr) {
common_super = &exception;
} elseif (common_super->Equals(exception)) { // odd case, but nothing to do
} else {
common_super = &common_super->Merge(exception, ®_types_, this); if (UNLIKELY(!IsAssignableFrom(reg_types_.JavaLangThrowable(), *common_super))) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD)
<< "java.lang.Throwable is not assignable-from common_super"; return {false, false};
}
}
}
}
}
handlers_ptr = iterator.EndDataPointer();
}
} const RegType* reg_type = nullptr; bool skip_verification_of_exception_handler = false; if (unresolved != nullptr) { // Soft-fail, but do not handle this with a synthetic throw.
Fail(VERIFY_ERROR_UNRESOLVED_TYPE_CHECK, /*pending_exc=*/ false) << "Unresolved catch handler"; if (common_super != nullptr) {
reg_type = &unresolved->Merge(*common_super, ®_types_, this);
} else {
reg_type = unresolved; if (!IsAotMode() && !IsSdkVersionSetAndAtLeast(api_level_, SdkVersion::kS_V2)) { // This is an unreachable handler at runtime. For older API levels, we avoid the // verification of the entire handler for compatibility reasons. The instruction // doesn't throw, but we mark the method as having a pending runtime throw failure // so that the JIT compiler does not try to compile it - the compiler expects all // instructions to be properly verified and may crash otherwise.
Fail(VERIFY_ERROR_RUNTIME_THROW, /* pending_exc= */ false);
skip_verification_of_exception_handler = true;
}
}
} elseif (common_super == nullptr) { /* No catch block */
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "unable to find exception handler"; return {false, false};
} else {
DCHECK(common_super->HasClass());
CheckForFinalAbstractClass(common_super->GetClass());
reg_type = common_super;
}
DCHECK(reg_type != nullptr);
work_line_->SetRegisterType<LockOp::kClear>(inst->VRegA_11x(), *reg_type); return {true, skip_verification_of_exception_handler};
}
ArtMethod* MethodVerifierImpl::ResolveMethodAndCheckAccess(
uint32_t dex_method_idx, MethodType method_type) { const dex::MethodId& method_id = dex_file_->GetMethodId(dex_method_idx); const RegType& klass_type = ResolveClass<CheckAccess::kYes>(method_id.class_idx_); if (klass_type.IsConflict()) {
LastFailureMessageStream()
<< " in attempt to access method " << dex_file_->GetMethodName(method_id); return nullptr;
} if (klass_type.IsUnresolvedTypes()) { return nullptr; // Can't resolve Class so no more to do here
}
ClassLinker* class_linker = GetClassLinker();
ObjPtr<mirror::Class> klass = GetRegTypeClass(klass_type);
bool must_fail = false; // This is traditional and helps with screwy bytecode. It will tell you that, yes, a method // exists, but that it's called incorrectly. This significantly helps debugging, as locally it's // hard to see the differences. // If we don't have res_method here we must fail. Just use this bool to make sure of that with a // DCHECK. if (res_method == nullptr) {
must_fail = true; // Try to find the method also with the other type for better error reporting below // but do not store such bogus lookup result in the DexCache or VerifierDeps.
res_method = class_linker->FindIncompatibleMethod(
klass, dex_cache_.Get(), class_loader_.Get(), dex_method_idx);
}
// Make sure calls to constructors are "direct". There are additional restrictions but we don't // enforce them here. if (res_method->IsConstructor() && method_type != METHOD_DIRECT) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "rejecting non-direct call to constructor "
<< res_method->PrettyMethod(); return nullptr;
} // Disallow any calls to class initializers. if (res_method->IsClassInitializer()) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "rejecting call to class initializer "
<< res_method->PrettyMethod(); return nullptr;
}
// Check that interface methods are static or match interface classes. // We only allow statics if we don't have default methods enabled. // // Note: this check must be after the initializer check, as those are required to fail a class, // while this check implies an IncompatibleClassChangeError. if (klass->IsInterface()) { // methods called on interfaces should be invoke-interface, invoke-super, invoke-direct (if // default methods are supported for the dex file), or invoke-static. if (method_type != METHOD_INTERFACE &&
method_type != METHOD_STATIC &&
(!dex_file_->SupportsDefaultMethods() ||
method_type != METHOD_DIRECT) &&
method_type != METHOD_SUPER) {
Fail(VERIFY_ERROR_CLASS_CHANGE)
<< "non-interface method " << dex_file_->PrettyMethod(dex_method_idx)
<< " is in an interface class " << klass->PrettyClass(); return nullptr;
} if (method_type == METHOD_SUPER &&
res_method->GetDeclaringClass()->IsObjectClass()) {
Fail(VERIFY_ERROR_NO_METHOD) << "invoke-super " << klass->PrettyDescriptor() << "."
<< dex_file_->GetMethodName(method_id) << " "
<< dex_file_->GetMethodSignature(method_id) << " resolved to "
<< "object method " << res_method->PrettyMethod() << " "
<< "but Object methods are excluded from super "
<< "method resolution on interfaces."; return nullptr;
}
} else { if (method_type == METHOD_INTERFACE) {
Fail(VERIFY_ERROR_CLASS_CHANGE)
<< "interface method " << dex_file_->PrettyMethod(dex_method_idx)
<< " is in a non-interface class " << klass->PrettyClass(); return nullptr;
}
}
// Check specifically for non-public object methods being provided for interface dispatch. This // can occur if we failed to find a method with FindInterfaceMethod but later find one with // FindClassMethod for error message use. if (method_type == METHOD_INTERFACE &&
res_method->GetDeclaringClass()->IsObjectClass() &&
!res_method->IsPublic()) {
Fail(VERIFY_ERROR_NO_METHOD) << "invoke-interface " << klass->PrettyDescriptor() << "."
<< dex_file_->GetMethodName(method_id) << " "
<< dex_file_->GetMethodSignature(method_id) << " resolved to "
<< "non-public object method " << res_method->PrettyMethod() << " "
<< "but non-public Object methods are excluded from interface "
<< "method resolution."; return nullptr;
} // Check if access is allowed. if (!CanAccessMember(res_method->GetDeclaringClass(), res_method->GetAccessFlags())) {
Fail(VERIFY_ERROR_ACCESS_METHOD) << "illegal method access (call "
<< res_method->PrettyMethod()
<< " from " << GetDeclaringClass() << ")"; return res_method;
} // Check that invoke-virtual and invoke-super are not used on private methods of the same class. if (res_method->IsPrivate() && (method_type == METHOD_VIRTUAL || method_type == METHOD_SUPER)) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "invoke-super/virtual can't be used on private method "
<< res_method->PrettyMethod(); return nullptr;
} // See if the method type implied by the invoke instruction matches the access flags for the // target method. The flags for METHOD_POLYMORPHIC are based on there being precisely two // signature polymorphic methods supported by the run-time which are native methods with variable // arguments. if ((method_type == METHOD_DIRECT && (!res_method->IsDirect() || res_method->IsStatic())) ||
(method_type == METHOD_STATIC && !res_method->IsStatic()) ||
((method_type == METHOD_SUPER ||
method_type == METHOD_VIRTUAL ||
method_type == METHOD_INTERFACE) && res_method->IsDirect()) ||
((method_type == METHOD_POLYMORPHIC) &&
(!res_method->IsNative() || !res_method->IsVarargs()))) {
Fail(VERIFY_ERROR_CLASS_CHANGE) << "invoke type (" << method_type << ") does not match method " "type of " << res_method->PrettyMethod(); return nullptr;
} // Make sure we weren't expecting to fail.
DCHECK(!must_fail) << "invoke type (" << method_type << ")"
<< klass->PrettyDescriptor() << "."
<< dex_file_->GetMethodName(method_id) << " "
<< dex_file_->GetMethodSignature(method_id) << " unexpectedly resolved to "
<< res_method->PrettyMethod() << " without error. Initially this method was "
<< "not found so we were expecting to fail for some reason."; return res_method;
}
// We use vAA as our expected arg count, rather than res_method->insSize, because we need to // match the call to the signature. Also, we might be calling through an abstract method // definition (which doesn't have register count values). const size_t expected_args = inst->VRegA(); /* caught by static verifier */
DCHECK(is_range || expected_args <= 5);
/* *Checkthe"this"argument,whichmustbeaninstanceoftheclassthatdeclaredthemethod. *Foraninterfaceclass,wedon'tdothefullinterfacemerge(seeJoinClass),sowecan'tdoa *rigorouscheckhere(whichisokaysincewehavetodoitatruntime).
*/ if (method_type != METHOD_STATIC) { const RegType& actual_arg_type = GetInvocationThis(inst); if (actual_arg_type.IsConflict()) { // GetInvocationThis failed.
CHECK(flags_.have_pending_hard_failure_); return nullptr;
} bool is_init = false; if (actual_arg_type.IsUninitializedTypes()) { if (res_method != nullptr) { if (!res_method->IsConstructor()) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "'this' arg must be initialized"; return nullptr;
}
} else { // Check whether the name of the called method is "<init>" const uint32_t method_idx = GetMethodIdxOfInvoke(inst); if (strcmp(dex_file_->GetMethodName(dex_file_->GetMethodId(method_idx)), "<init>") != 0) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "'this' arg must be initialized"; return nullptr;
}
}
is_init = true;
} const RegType& adjusted_type = is_init
? GetRegTypeCache()->FromUninitialized(actual_arg_type)
: actual_arg_type; if (method_type != METHOD_INTERFACE && !adjusted_type.IsZeroOrNull()) { // Get the referenced class first. This is fast because it's already cached by the type // index due to method resolution. It is usually the resolved method's declaring class. const uint32_t method_idx = GetMethodIdxOfInvoke(inst); const dex::TypeIndex class_idx = dex_file_->GetMethodId(method_idx).class_idx_; const RegType* res_method_class = ®_types_.FromTypeIndex(class_idx);
DCHECK_IMPLIES(res_method != nullptr,
res_method_class->IsJavaLangObject() || res_method_class->IsReference());
DCHECK_IMPLIES(res_method != nullptr && res_method_class->IsJavaLangObject(),
res_method->GetDeclaringClass()->IsObjectClass()); // Miranda methods have the declaring interface as their declaring class, not the abstract // class. It would be wrong to use this for the type check (interface type checks are // postponed to runtime). if (res_method != nullptr && res_method_class->IsReference() && !res_method->IsMiranda()) {
ObjPtr<mirror::Class> klass = res_method->GetDeclaringClass(); if (res_method_class->GetClass() != klass) { // The resolved method is in a superclass, not directly in the referenced class.
res_method_class = ®_types_.FromClass(klass);
}
} if (!IsAssignableFrom(*res_method_class, adjusted_type)) { // We return a soft unresolved type check failure as long as: // 1) `adjusted_type` is unresolved // 2) `res_method_class` is not a non-array final class. // In this case, potentially the unresolved class becomes resolved and everything is okay. constbool soft_unresolved_failure =
adjusted_type.IsUnresolvedTypes() && !res_method_class->IsNonArrayFinalClass();
Fail(soft_unresolved_failure ? VERIFY_ERROR_UNRESOLVED_TYPE_CHECK
: VERIFY_ERROR_BAD_CLASS_HARD)
<< "'this' argument '" << actual_arg_type << "' not instance of '" << *res_method_class
<< "'"; // Continue on soft failures. We need to find possible hard failures to avoid problems in // the compiler. if (flags_.have_pending_hard_failure_) { return nullptr;
}
}
}
}
const RegType& reg_type = reg_types_.FromTypeIndex(it->GetTypeIdx());
uint32_t get_reg = is_range ? inst->VRegC() + static_cast<uint32_t>(sig_registers) :
arg[sig_registers]; if (reg_type.IsIntegralTypes()) { const RegType& src_type = work_line_->GetRegisterType(this, get_reg); if (!src_type.IsIntegralTypes()) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "register v" << get_reg << " has type " << src_type
<< " but expected " << reg_type; return nullptr;
}
} else { if (!VerifyRegisterType(get_reg, reg_type)) { // Continue on soft failures. We need to find possible hard failures to avoid problems in // the compiler. if (flags_.have_pending_hard_failure_) { return nullptr;
}
} elseif (reg_type.IsLongOrDoubleTypes()) { // Check that registers are consecutive (for non-range invokes). Invokes are the only // instructions not specifying register pairs by the first component, but require them // nonetheless. Only check when there's an actual register in the parameters. If there's // none, this will fail below. if (!is_range && sig_registers + 1 < expected_args) {
uint32_t second_reg = arg[sig_registers + 1]; if (second_reg != get_reg + 1) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "Rejecting invocation, long or double parameter " "at index " << sig_registers << " is not a pair: " << get_reg << " + "
<< second_reg << "."; return nullptr;
}
}
}
}
sig_registers += reg_type.IsLongOrDoubleTypes() ? 2 : 1;
} if (expected_args != sig_registers) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "Rejecting invocation, expected " << expected_args << " argument registers, method signature has " << sig_registers; return nullptr;
} return res_method;
}
void MethodVerifierImpl::VerifyInvocationArgsUnresolvedMethod(const Instruction* inst,
MethodType method_type, bool is_range) { // As the method may not have been resolved, make this static check against what we expect. // The main reason for this code block is to fail hard when we find an illegal use, e.g., // wrong number of arguments or wrong primitive types, even if the method could not be resolved. const uint32_t method_idx = GetMethodIdxOfInvoke(inst);
DexFileParameterIterator it(*dex_file_,
dex_file_->GetProtoId(dex_file_->GetMethodId(method_idx).proto_idx_));
VerifyInvocationArgsFromIterator(&it, inst, method_type, is_range, nullptr);
}
bool MethodVerifierImpl::CheckCallSite(uint32_t call_site_idx) { if (call_site_idx >= dex_file_->NumCallSiteIds()) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "Bad call site id #" << call_site_idx
<< " >= " << dex_file_->NumCallSiteIds(); returnfalse;
}
CallSiteArrayValueIterator it(*dex_file_, dex_file_->GetCallSiteId(call_site_idx)); // Check essential arguments are provided. The dex file verifier has verified indices of the // main values (method handle, name, method_type). staticconst size_t kRequiredArguments = 3; if (it.Size() < kRequiredArguments) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "Call site #" << call_site_idx
<< " has too few arguments: "
<< it.Size() << " < " << kRequiredArguments; returnfalse;
}
// Check arguments have expected types and are within permitted ranges. for (size_t i = 0; i < kRequiredArguments; ++i) { if (it.GetValueType() != type_and_max[i].first) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "Call site id #" << call_site_idx
<< " argument " << i << " has wrong type "
<< it.GetValueType() << "!=" << type_and_max[i].first; returnfalse;
}
index[i] = static_cast<uint32_t>(it.GetJavaValue().i); if (index[i] >= type_and_max[i].second) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "Call site id #" << call_site_idx
<< " argument " << i << " bad index "
<< index[i] << " >= " << type_and_max[i].second; returnfalse;
}
// Don't increase if we are going to read past the item. if (i != kRequiredArguments - 1) {
it.Next();
}
}
// Check method handle kind is valid. const dex::MethodHandleItem& mh = dex_file_->GetMethodHandle(index[0]); if (mh.method_handle_type_ != static_cast<uint16_t>(DexFile::MethodHandleType::kInvokeStatic)) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "Call site #" << call_site_idx
<< " argument 0 method handle type is not InvokeStatic: "
<< mh.method_handle_type_; returnfalse;
} returntrue;
}
ArtMethod* MethodVerifierImpl::VerifyInvocationArgs( const Instruction* inst, MethodType method_type, bool is_range) { // Resolve the method. This could be an abstract or concrete method depending on what sort of call // we're making. const uint32_t method_idx = GetMethodIdxOfInvoke(inst);
ArtMethod* res_method = ResolveMethodAndCheckAccess(method_idx, method_type); if (res_method == nullptr) { // error or class is unresolved // Check what we can statically. if (!flags_.have_pending_hard_failure_) {
VerifyInvocationArgsUnresolvedMethod(inst, method_type, is_range);
} return nullptr;
}
// If we're using invoke-super(method), make sure that the executing method's class' superclass // has a vtable entry for the target method. Or the target is on a interface. if (method_type == METHOD_SUPER) {
dex::TypeIndex class_idx = dex_file_->GetMethodId(method_idx).class_idx_; const RegType& reference_type = reg_types_.FromTypeIndex(class_idx); if (reference_type.IsUnresolvedTypes()) { // We cannot differentiate on whether this is a class change error or just // a missing method. This will be handled at runtime.
Fail(VERIFY_ERROR_NO_METHOD) << "Unable to find referenced class from invoke-super";
VerifyInvocationArgsUnresolvedMethod(inst, method_type, is_range); return nullptr;
}
DCHECK(reference_type.IsJavaLangObject() || reference_type.IsReference()); if (reference_type.IsReference() && reference_type.GetClass()->IsInterface()) { if (!GetDeclaringClass().HasClass()) {
Fail(VERIFY_ERROR_NO_CLASS) << "Unable to resolve the full class of 'this' used in an"
<< "interface invoke-super";
VerifyInvocationArgsUnresolvedMethod(inst, method_type, is_range); return nullptr;
} elseif (!IsStrictlyAssignableFrom(reference_type, GetDeclaringClass())) {
Fail(VERIFY_ERROR_CLASS_CHANGE)
<< "invoke-super in " << mirror::Class::PrettyClass(GetDeclaringClass().GetClass())
<< " in method "
<< dex_file_->PrettyMethod(dex_method_idx_) << " to method "
<< dex_file_->PrettyMethod(method_idx) << " references "
<< "non-super-interface type " << mirror::Class::PrettyClass(reference_type.GetClass());
VerifyInvocationArgsUnresolvedMethod(inst, method_type, is_range); return nullptr;
}
} else { if (UNLIKELY(!class_def_.superclass_idx_.IsValid())) { // Verification error in `j.l.Object` leads to a hang while trying to verify // the exception class. It is better to crash directly.
LOG(FATAL) << "No superclass for invoke-super from "
<< dex_file_->PrettyMethod(dex_method_idx_)
<< " to super " << res_method->PrettyMethod() << ".";
UNREACHABLE();
} const RegType& super = reg_types_.FromTypeIndex(class_def_.superclass_idx_); if (super.IsUnresolvedTypes()) {
Fail(VERIFY_ERROR_NO_METHOD) << "unknown super class in invoke-super from "
<< dex_file_->PrettyMethod(dex_method_idx_)
<< " to super " << res_method->PrettyMethod();
VerifyInvocationArgsUnresolvedMethod(inst, method_type, is_range); return nullptr;
} if (!IsStrictlyAssignableFrom(reference_type, GetDeclaringClass()) ||
(res_method->GetMethodIndex() >= GetRegTypeClass(super)->GetVTableLength())) {
Fail(VERIFY_ERROR_NO_METHOD) << "invalid invoke-super from "
<< dex_file_->PrettyMethod(dex_method_idx_)
<< " to super " << super
<< "." << res_method->GetName()
<< res_method->GetSignature();
VerifyInvocationArgsUnresolvedMethod(inst, method_type, is_range); return nullptr;
}
}
}
dex::ProtoIndex proto_idx; if (UNLIKELY(method_type == METHOD_POLYMORPHIC)) { // Process the signature of the calling site that is invoking the method handle.
proto_idx = dex::ProtoIndex(inst->VRegH());
} else { // Process the target method's signature.
proto_idx = dex_file_->GetMethodId(method_idx).proto_idx_;
}
DexFileParameterIterator it(*dex_file_, dex_file_->GetProtoId(proto_idx));
ArtMethod* verified_method =
VerifyInvocationArgsFromIterator(&it, inst, method_type, is_range, res_method);
if (verified_method != nullptr && !verified_method->GetDeclaringClass()->IsInterface()) {
CheckForFinalAbstractClass(res_method->GetDeclaringClass());
}
bool MethodVerifierImpl::CheckSignaturePolymorphicReceiver(const Instruction* inst) { const RegType& this_type = GetInvocationThis(inst); if (this_type.IsZeroOrNull()) { /* null pointer always passes (and always fails at run time) */ returntrue;
} elseif (!this_type.IsNonZeroReferenceTypes()) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD)
<< "invoke-polymorphic receiver is not a reference: "
<< this_type; returnfalse;
} elseif (this_type.IsUninitializedReference()) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD)
<< "invoke-polymorphic receiver is uninitialized: "
<< this_type; returnfalse;
} elseif (!this_type.HasClass()) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD)
<< "invoke-polymorphic receiver has no class: "
<< this_type; returnfalse;
} else {
ObjPtr<mirror::ObjectArray<mirror::Class>> class_roots = GetClassLinker()->GetClassRoots(); if (!this_type.GetClass()->IsSubClass(GetClassRoot<mirror::MethodHandle>(class_roots)) &&
!this_type.GetClass()->IsSubClass(GetClassRoot<mirror::VarHandle>(class_roots))) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD)
<< "invoke-polymorphic receiver is not a subclass of MethodHandle or VarHandle: "
<< this_type; returnfalse;
}
} returntrue;
}
bool MethodVerifierImpl::VerifyFilledNewArray(const Instruction* inst, bool is_range) {
dex::TypeIndex type_idx; if (!is_range) {
DCHECK_EQ(inst->Opcode(), Instruction::FILLED_NEW_ARRAY);
type_idx = dex::TypeIndex(inst->VRegB_35c());
} else {
DCHECK_EQ(inst->Opcode(), Instruction::FILLED_NEW_ARRAY_RANGE);
type_idx = dex::TypeIndex(inst->VRegB_3rc());
} // Dex file verifier ensures that all valid type indexes reference valid descriptors and the // `CheckNewArray()` ensures that the descriptor starts with an `[` before we get to the // code flow verification. So, we should see only array types here. const RegType& res_type = ResolveClass<CheckAccess::kYes>(type_idx);
DCHECK(res_type.IsArrayTypes()); // TODO: check Compiler::CanAccessTypeWithoutChecks returns false when res_type is unresolved
DCHECK(!res_type.IsUnresolvedMergedReference()); // Verify each input register. It's legal, if silly, for arg_count to be zero. const RegType& expected_type = reg_types_.GetComponentType(res_type);
uint32_t arg_count = (is_range) ? inst->VRegA_3rc() : inst->VRegA_35c();
uint32_t arg[5]; if (!is_range) {
inst->GetVarArgs(arg);
} for (size_t ui = 0; ui < arg_count; ui++) {
uint32_t get_reg = is_range ? inst->VRegC_3rc() + ui : arg[ui]; if (!VerifyRegisterType(get_reg, expected_type)) { // Don't continue on hard failures.
DCHECK(flags_.have_pending_hard_failure_); returnfalse;
}
DCHECK(!flags_.have_pending_hard_failure_);
} // filled-array result goes into "result" register
work_line_->SetResultRegisterType(res_type); returntrue;
}
auto narrow_opcode_kind = [=]() {
DCHECK(kAccWidth == AccessWidth::kNarrow); // Register kinds Boolean, Byte, Char, Short are ordered as in get/put instructions. static constexpr Instruction::Code kBaseOpcode =
(kAccType == AccessType::kGet) ? Instruction::AGET_BOOLEAN : Instruction::APUT_BOOLEAN; return enum_cast<RegType::Kind>(RegType::Kind::kBoolean + (opcode - kBaseOpcode));
};
const RegType& array_type = work_line_->GetRegisterType(this, inst->VRegB_23x());
uint32_t vregA = inst->VRegA_23x(inst_data);
RegType::Kind kind = RegType::kConflict;
std::optional<uint16_t> type_id(std::nullopt); if (array_type.IsZeroOrNull()) { // Null array class; this code path will fail at runtime. For aget, infer a merge-able type // from the instruction. For aput, check that the given value matches the instruction. // Note: this is, as usual, complicated by the fact that some of these instructions are not // fully typed and fit multiple register types. if (kAccType == AccessType::kGet) { if (!kIsPrimitive) {
kind = RegType::kNull;
} elseif (kAccWidth == AccessWidth::kNarrow) {
kind = narrow_opcode_kind();
} elseif (kAccWidth == AccessWidth::kVreg) {
kind = RegType::kIntegerConstant; // Serves as either `int` or `float`.
} else {
DCHECK(kAccWidth == AccessWidth::kWide);
kind = RegType::kConstantLo; // Serves as either `long` or `double`.
}
} else {
DCHECK(kAccType == AccessType::kPut); if (!kIsPrimitive) { // `kind` shall not be used, leave it as `kConflict`.
} elseif (kAccWidth == AccessWidth::kNarrow) {
kind = narrow_opcode_kind();
} elseif (kAccWidth == AccessWidth::kVreg) {
kind = (work_line_->GetRegisterTypeId(vregA) == RegTypeCache::kFloatCacheId)
? RegType::Kind::kFloat
: RegType::Kind::kInteger;
} else {
DCHECK(kAccWidth == AccessWidth::kWide);
kind = (work_line_->GetRegisterTypeId(vregA) == RegTypeCache::kDoubleLoCacheId)
? RegType::Kind::kDoubleLo
: RegType::Kind::kLongLo;
}
}
} elseif (!array_type.IsArrayTypes()) {
FailNonArrayType(opcode, array_type); returnfalse;
} elseif (array_type.IsUnresolvedMergedReference()) { // Unresolved array types must be reference array types. if (kIsPrimitive) {
FailIncompatibleArrayType(opcode, array_type); returnfalse;
} else {
SoftFailArrayIsUnresolvedMergedReference(opcode, array_type); // Approximate with java.lang.Object[]. if (kAccType == AccessType::kGet) {
kind = RegType::Kind::kJavaLangObject;
} // else `kind` shall not be used, leave it as `kConflict`.
}
} else { /* verify the class */ const RegType& component_type = reg_types_.GetComponentType(array_type); bool instruction_compatible; if (!kIsPrimitive) {
instruction_compatible = component_type.IsReferenceTypes();
} elseif (kAccWidth == AccessWidth::kNarrow) {
instruction_compatible = (component_type.GetKind() == narrow_opcode_kind());
} elseif (kAccWidth == AccessWidth::kVreg) {
instruction_compatible = (component_type.GetKind() == RegType::Kind::kInteger) ||
(component_type.GetKind() == RegType::Kind::kFloat);
} else {
DCHECK(kAccWidth == AccessWidth::kWide);
instruction_compatible = (component_type.GetKind() == RegType::Kind::kDoubleLo) ||
(component_type.GetKind() == RegType::Kind::kLongLo);
} if (!instruction_compatible) { // This is a global failure rather than a class change failure as the instructions and // the descriptors for the type should have been consistent within the same file at // compile time.
FailIncompatibleArrayType(inst->Opcode(), array_type); returnfalse;
} if (!kIsPrimitive && kAccType == AccessType::kGet) {
type_id.emplace(component_type.GetId());
}
kind = (kAccWidth == AccessWidth::kNarrow) ? narrow_opcode_kind() : component_type.GetKind();
}
if (kAccType == AccessType::kGet) { if (kAccWidth == AccessWidth::kWide) {
work_line_->SetRegisterTypeWide(vregA, kind, RegType::ToHighHalf(kind));
} else { // For `aget-object`, only edge cases should reach this code without `type_id` set.
work_line_->SetRegisterTypeId(
vregA, type_id.has_value() ? type_id.value() : RegTypeCache::IdForRegKind(kind));
} returntrue;
} else {
DCHECK(kAccType == AccessType::kPut); if (kIsPrimitive) { return VerifyPrimitivePut(kind, vregA);
} else { // The instruction agrees with the type of array, confirm the value to be stored does too // Note: we use the instruction type (rather than the component type) for aput-object as // incompatible classes will be caught at runtime as an array store exception return VerifyRegisterType(vregA, reg_types_.JavaLangObject());
}
}
}
ArtField* MethodVerifierImpl::GetStaticField(uint32_t field_idx, bool is_put) { const dex::FieldId& field_id = dex_file_->GetFieldId(field_idx); // Check access to class const RegType& klass_type = ResolveClass<CheckAccess::kYes>(field_id.class_idx_); // Dex file verifier ensures that field ids reference valid descriptors starting with `L`.
DCHECK(klass_type.IsJavaLangObject() ||
klass_type.IsReference() ||
klass_type.IsUnresolvedReference()); if (klass_type.IsUnresolvedReference()) { // Accessibility checks depend on resolved fields.
DCHECK(klass_type.Equals(GetDeclaringClass()) ||
!failures_.empty() ||
IsSdkVersionSetAndLessThan(api_level_, SdkVersion::kP)); return nullptr; // Can't resolve Class so no more to do here, will do checking at runtime.
}
ClassLinker* class_linker = GetClassLinker();
ArtField* field = class_linker->ResolveFieldJLS(field_idx, dex_cache_, class_loader_); if (field == nullptr) {
VLOG(verifier) << "Unable to resolve static field " << field_idx << " ("
<< dex_file_->GetFieldName(field_id) << ") in "
<< dex_file_->GetFieldDeclaringClassDescriptor(field_id);
DCHECK(self_->IsExceptionPending());
self_->ClearException();
Fail(VERIFY_ERROR_NO_FIELD)
<< "field " << dex_file_->PrettyField(field_idx)
<< " not found in the resolved type " << klass_type; return nullptr;
} elseif (!field->IsStatic()) {
Fail(VERIFY_ERROR_CLASS_CHANGE) << "expected field " << field->PrettyField() << " to be static"; return nullptr;
}
return GetISFieldCommon(field, is_put);
}
ArtField* MethodVerifierImpl::GetInstanceField(uint32_t vregB, uint32_t field_idx, bool is_put) { const RegType& obj_type = work_line_->GetRegisterType(this, vregB); if (!obj_type.IsReferenceTypes()) { // Trying to read a field from something that isn't a reference.
Fail(VERIFY_ERROR_BAD_CLASS_HARD)
<< "instance field access on object that has non-reference type " << obj_type; return nullptr;
} const dex::FieldId& field_id = dex_file_->GetFieldId(field_idx); // Check access to class. const RegType& klass_type = ResolveClass<CheckAccess::kYes>(field_id.class_idx_); // Dex file verifier ensures that field ids reference valid descriptors starting with `L`.
DCHECK(klass_type.IsJavaLangObject() ||
klass_type.IsReference() ||
klass_type.IsUnresolvedReference());
ArtField* field = nullptr; if (!klass_type.IsUnresolvedReference()) {
ClassLinker* class_linker = GetClassLinker();
field = class_linker->ResolveFieldJLS(field_idx, dex_cache_, class_loader_); if (field == nullptr) {
VLOG(verifier) << "Unable to resolve instance field " << field_idx << " ("
<< dex_file_->GetFieldName(field_id) << ") in "
<< dex_file_->GetFieldDeclaringClassDescriptor(field_id);
DCHECK(self_->IsExceptionPending());
self_->ClearException();
}
}
if (obj_type.IsUninitializedTypes()) { // One is not allowed to access fields on uninitialized references, except to write to // fields in the constructor (before calling another constructor). We strictly check // that the field id references the class directly instead of some subclass. if (is_put && field_id.class_idx_ == GetClassDef().class_idx_) { if (obj_type.IsUnresolvedUninitializedThisReference()) {
DCHECK(GetDeclaringClass().IsUnresolvedReference());
DCHECK(GetDeclaringClass().Equals(reg_types_.FromUninitialized(obj_type)));
ClassAccessor accessor(*dex_file_, GetClassDef()); auto it = std::find_if(
accessor.GetInstanceFields().begin(),
accessor.GetInstanceFields().end(),
[field_idx] (const ClassAccessor::Field& f) { return f.GetIndex() == field_idx; }); if (it != accessor.GetInstanceFields().end()) { // There are no soft failures to report anymore, other than the class being unresolved. return nullptr;
}
} elseif (obj_type.IsUninitializedThisReference()) {
DCHECK(GetDeclaringClass().IsJavaLangObject() || GetDeclaringClass().IsReference());
DCHECK(GetDeclaringClass().Equals(reg_types_.FromUninitialized(obj_type))); if (field != nullptr &&
field->GetDeclaringClass() == GetDeclaringClass().GetClass() &&
!field->IsStatic()) { // The field is now fully verified against the `obj_type`. return field;
}
}
} // Allow `iget` on resolved uninitialized `this` for app compatibility. // This is rejected by the RI but there are Android apps that actually have such `iget`s. // TODO: Should we start rejecting such bytecode based on the SDK level? if (!is_put &&
obj_type.IsUninitializedThisReference() &&
field != nullptr &&
field->GetDeclaringClass() == GetDeclaringClass().GetClass()) { return field;
}
Fail(VERIFY_ERROR_BAD_CLASS_HARD)
<< "cannot access instance field " << dex_file_->PrettyField(field_idx)
<< " of a not fully initialized object within the context of "
<< dex_file_->PrettyMethod(dex_method_idx_); return nullptr;
}
if (klass_type.IsUnresolvedReference()) { // Accessibility checks depend on resolved fields.
DCHECK(klass_type.Equals(GetDeclaringClass()) ||
!failures_.empty() ||
IsSdkVersionSetAndLessThan(api_level_, SdkVersion::kP)); return nullptr; // Can't resolve Class so no more to do here, will do checking at runtime.
} elseif (field == nullptr) {
Fail(VERIFY_ERROR_NO_FIELD)
<< "field " << dex_file_->PrettyField(field_idx)
<< " not found in the resolved type " << klass_type; return nullptr;
} elseif (obj_type.IsZeroOrNull()) { // Cannot infer and check type, however, access will cause null pointer exception. // Fall through into a few last soft failure checks below.
} else {
ObjPtr<mirror::Class> klass = field->GetDeclaringClass();
DCHECK_IMPLIES(klass_type.IsJavaLangObject(), klass->IsObjectClass()); const RegType& field_klass =
LIKELY(klass_type.IsJavaLangObject() || klass_type.GetClass() == klass)
? klass_type
: reg_types_.FromClass(klass);
DCHECK(!obj_type.IsUninitializedTypes()); if (!IsAssignableFrom(field_klass, obj_type)) { // Trying to access C1.field1 using reference of type C2, which is neither C1 or a sub-class // of C1. For resolution to occur the declared class of the field must be compatible with // obj_type, we've discovered this wasn't so, so report the field didn't exist.
DCHECK(!field_klass.IsUnresolvedTypes());
Fail(obj_type.IsUnresolvedTypes()
? VERIFY_ERROR_UNRESOLVED_TYPE_CHECK
: VERIFY_ERROR_BAD_CLASS_HARD)
<< "cannot access instance field " << field->PrettyField()
<< " from object of type " << obj_type; return nullptr;
}
}
// Few last soft failure checks. if (field->IsStatic()) {
Fail(VERIFY_ERROR_CLASS_CHANGE) << "expected field " << field->PrettyField()
<< " to not be static"; return nullptr;
}
return GetISFieldCommon(field, is_put);
}
ArtField* MethodVerifierImpl::GetISFieldCommon(ArtField* field, bool is_put) {
DCHECK(field != nullptr); if (!CanAccessMember(field->GetDeclaringClass(), field->GetAccessFlags())) {
Fail(VERIFY_ERROR_ACCESS_FIELD)
<< "cannot access " << (field->IsStatic() ? "static" : "instance") << " field "
<< field->PrettyField() << " from " << GetDeclaringClass(); return nullptr;
} if (is_put && field->IsFinal() && field->GetDeclaringClass() != GetDeclaringClass().GetClass()) {
Fail(VERIFY_ERROR_ACCESS_FIELD)
<< "cannot modify final field " << field->PrettyField()
<< " from other class " << GetDeclaringClass(); return nullptr;
}
CheckForFinalAbstractClass(field->GetDeclaringClass()); return field;
}
void MethodVerifierImpl::PotentiallyMarkRuntimeThrow() { if (IsAotMode() || IsSdkVersionSetAndAtLeast(api_level_, SdkVersion::kS_V2)) { return;
} // Compatibility mode: we treat the following code unreachable and the verifier // will not analyze it. // The verifier may fail before we touch any instruction, for the signature of a method. So // add a check. if (work_insn_idx_ < dex::kDexNoIndex) { const Instruction& inst = code_item_accessor_.InstructionAt(work_insn_idx_);
Instruction::Code opcode = inst.Opcode();
DCHECK_NE(opcode, Instruction::MOVE_EXCEPTION); // How to handle runtime failures for instructions that are not flagged kThrow. if ((Instruction::FlagsOf(opcode) & Instruction::kThrow) == 0 &&
!impl::IsCompatThrow(opcode) &&
GetInstructionFlags(work_insn_idx_).IsInTry()) { if (Runtime::Current()->IsVerifierMissingKThrowFatal()) {
LOG(FATAL) << "Unexpected throw: " << std::hex << work_insn_idx_ << " " << opcode;
UNREACHABLE();
} // We need to save the work_line if the instruction wasn't throwing before. Otherwise // we'll try to merge garbage. // Note: this assumes that Fail is called before we do any work_line modifications.
saved_line_->CopyFromLine(work_line_.get());
}
}
flags_.have_pending_runtime_throw_failure_ = true;
}
impl::MethodVerifier<kVerifierDebug> verifier(self,
arena_pool,
reg_types,
verifier_deps,
code_item,
method_idx,
aot_mode,
dex_cache,
class_def,
method_access_flags, /* verify_to_dump= */ false,
api_level); if (verifier.Verify()) { // Verification completed, however failures may be pending that didn't cause the verification // to hard fail.
CHECK(!verifier.flags_.have_pending_hard_failure_);
if (verifier.failures_.size() != 0) { if (VLOG_IS_ON(verifier)) {
verifier.DumpFailures(VLOG_STREAM(verifier)
<< "Soft verification failures in "
<< reg_types->GetDexFile()->PrettyMethod(method_idx) << "\n");
} if (kVerifierDebug) {
LOG(INFO) << verifier.InfoMessages().view();
verifier.Dump(LOG_STREAM(INFO));
} if (CanCompilerHandleVerificationFailure(verifier.encountered_failure_types_)) { if (verifier.encountered_failure_types_ & VERIFY_ERROR_UNRESOLVED_TYPE_CHECK) {
result.kind = FailureKind::kTypeChecksFailure;
} else {
result.kind = FailureKind::kAccessChecksFailure;
}
} else { // If the compiler cannot handle the failure, force a soft failure to // ensure the class will be re-verified at runtime and the method marked // as not compilable.
result.kind = FailureKind::kSoftFailure;
}
}
} else { // Bad method data.
CHECK_NE(verifier.failures_.size(), 0U);
CHECK(verifier.flags_.have_pending_hard_failure_); if (VLOG_IS_ON(verifier)) {
log_level = std::max(HardFailLogMode::kLogVerbose, log_level);
} if (log_level >= HardFailLogMode::kLogVerbose) {
LogSeverity severity; switch (log_level) { case HardFailLogMode::kLogVerbose:
severity = LogSeverity::VERBOSE; break; case HardFailLogMode::kLogWarning:
severity = LogSeverity::WARNING; break; case HardFailLogMode::kLogInternalFatal:
severity = LogSeverity::FATAL_WITHOUT_ABORT; break; default:
LOG(FATAL) << "Unsupported log-level " << static_cast<uint32_t>(log_level);
UNREACHABLE();
}
verifier.DumpFailures(LOG_STREAM(severity)
<< "Verification error in "
<< reg_types->GetDexFile()->PrettyMethod(method_idx) << "\n");
} if (hard_failure_msg != nullptr) {
CHECK(!verifier.failures_.empty());
*hard_failure_msg = verifier.failures_.back().message.view();
}
result.kind = FailureKind::kHardFailure;
std::ostream& MethodVerifier::Fail(VerifyError error, bool pending_exc) { // Mark the error type as encountered.
encountered_failure_types_ |= static_cast<uint32_t>(error);
if (pending_exc) { switch (error) { case VERIFY_ERROR_NO_CLASS: case VERIFY_ERROR_UNRESOLVED_TYPE_CHECK: case VERIFY_ERROR_NO_METHOD: case VERIFY_ERROR_NO_FIELD: case VERIFY_ERROR_ACCESS_CLASS: case VERIFY_ERROR_ACCESS_FIELD: case VERIFY_ERROR_ACCESS_METHOD: case VERIFY_ERROR_INSTANTIATION: case VERIFY_ERROR_FILLED_NEW_ARRAY: case VERIFY_ERROR_CLASS_CHANGE: {
PotentiallyMarkRuntimeThrow(); break;
}
case VERIFY_ERROR_LOCKING:
PotentiallyMarkRuntimeThrow(); // This will be reported to the runtime as a soft failure. break;
// Hard verification failures at compile time will still fail at runtime, so the class is // marked as rejected to prevent it from being compiled. case VERIFY_ERROR_BAD_CLASS_HARD: {
flags_.have_pending_hard_failure_ = true; break;
}
RegType::Assignability assignable = RegType::AssignabilityFrom(lhs.GetKind(), rhs.GetKind());
DCHECK(assignable != RegType::Assignability::kInvalid)
<< "Unexpected register type in IsAssignableFrom: '" << lhs << "' := '" << rhs << "'"; if (assignable == RegType::Assignability::kAssignable) { returntrue;
} elseif (assignable == RegType::Assignability::kNotAssignable) { returnfalse;
} elseif (assignable == RegType::Assignability::kNarrowingConversion) { // FIXME: The `MethodVerifier` is mostly doing a category check and avoiding // assignability checks that would expose narrowing conversions. However, for // the `return` instruction, it explicitly allows certain narrowing conversions // and prohibits others by doing a modified assignability check. Without strict // enforcement in all cases, this can compromise compiler optimizations that // rely on knowing the range of the values. Bug: 270660613 returnfalse;
} else {
DCHECK(assignable == RegType::Assignability::kReference);
DCHECK(lhs.IsNonZeroReferenceTypes());
DCHECK(rhs.IsNonZeroReferenceTypes());
DCHECK(!lhs.IsUninitializedTypes());
DCHECK(!rhs.IsUninitializedTypes());
DCHECK(!lhs.IsJavaLangObject()); if (!strict && !lhs.IsUnresolvedTypes() && lhs.GetClass()->IsInterface()) { // If we're not strict allow assignment to any interface, see comment in ClassJoin. returntrue;
} elseif (lhs.IsJavaLangObjectArray()) { return rhs.IsObjectArrayTypes(); // All reference arrays may be assigned to Object[]
} elseif (lhs.HasClass() && rhs.IsJavaLangObject()) { returnfalse; // Note: Non-strict check for interface `lhs` is handled above.
} elseif (lhs.HasClass() && rhs.HasClass()) { // Test assignability from the Class point-of-view. bool result = lhs.GetClass()->IsAssignableFrom(rhs.GetClass()); // Record assignability dependency. The `verifier` is null during unit tests and // VerifiedMethod::GenerateSafeCastSet. if (result) {
VerifierDeps::MaybeRecordAssignability(GetVerifierDeps(),
GetDexFile(),
GetClassDef(),
lhs.GetClass(),
rhs.GetClass());
} return result;
} else { // For unresolved types, we don't know if they are assignable, and the // verifier will continue assuming they are. We need to record that. // // Note that if `rhs` is an interface type, `lhs` may be j.l.Object // and if the assignability check is not strict, then this should be // OK. However we don't encode strictness in the verifier deps, and // such a situation will force a full verification.
VerifierDeps::MaybeRecordAssignability(GetVerifierDeps(),
GetDexFile(),
GetClassDef(),
lhs,
rhs); // Unresolved types are only assignable for null and equality. // Null cannot be the left-hand side. returnfalse;
}
}
}
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.254Bemerkung:
(vorverarbeitet am 2026-06-29)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.