summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Vladimir Marko <vmarko@google.com> 2025-01-07 00:26:17 -0800
committer Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> 2025-01-07 00:26:17 -0800
commit8c7eb63071a9b83292f0a8f5024dcfce14791f5d (patch)
tree73848b4038fcc7ff1e505f20f77a4d08346ad389
parent8a994058a6f02e743df8410988c6aba598a8843a (diff)
parent7d019711618186490029b5504ebca0f647a5cb7a (diff)
verifier: Rewrite branch target verification. am: 7d01971161
Original change: https://android-review.googlesource.com/c/platform/art/+/3435699 Change-Id: I84f598578666d4096d67df90a64174efae25f6a8 Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
-rw-r--r--runtime/verifier/method_verifier.cc472
1 files changed, 281 insertions, 191 deletions
diff --git a/runtime/verifier/method_verifier.cc b/runtime/verifier/method_verifier.cc
index ae696776bf..68c0268720 100644
--- a/runtime/verifier/method_verifier.cc
+++ b/runtime/verifier/method_verifier.cc
@@ -209,12 +209,10 @@ class MethodVerifier final : public ::art::verifier::MethodVerifier {
delete last_fail_message;
}
- // Adds the given string to the end of the last failure message.
- void AppendToLastFailMessage(const std::string& append) {
- size_t failure_num = failure_messages_.size();
- DCHECK_NE(failure_num, 0U);
- std::ostringstream* last_fail_message = failure_messages_[failure_num - 1];
- (*last_fail_message) << append;
+ // Return the last failure message stream for appending.
+ std::ostream& LastFailureMessageStream() {
+ DCHECK(!failure_messages_.empty());
+ return *failure_messages_.back();
}
/*
@@ -286,8 +284,9 @@ class MethodVerifier final : public ::art::verifier::MethodVerifier {
* instruction
*/
template <Instruction::Code kDispatchOpcode>
- ALWAYS_INLINE bool VerifyInstruction(const Instruction* inst,
- uint32_t code_offset,
+ ALWAYS_INLINE bool VerifyInstruction(uint32_t dex_pc,
+ uint32_t end_dex_pc,
+ const Instruction* inst,
uint16_t inst_data);
/* Ensure that the register index is valid for this code item. */
@@ -479,19 +478,41 @@ class MethodVerifier final : public ::art::verifier::MethodVerifier {
// creating an array of arrays that causes the number of dimensions to exceed 255.
bool CheckNewArray(dex::TypeIndex idx);
- // Verify an array data table. "cur_offset" is the offset of the fill-array-data instruction.
- bool CheckArrayData(uint32_t cur_offset);
+ // 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
+ static bool IsOffsetInRange(uint32_t dex_pc, uint32_t end_dex_pc, int32_t offset) {
+ DCHECK_LT(dex_pc, end_dex_pc);
+ if (offset >= 0) {
+ return static_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 certain instructions from branching to themselves.
+ // The dex format forbids instructions other than `goto/32` from branching to themselves.
// Updates "insn_flags_", setting the "branch target" flag.
- bool CheckBranchTarget(uint32_t cur_offset);
+ template <Instruction::Format kFormat>
+ ALWAYS_INLINE
+ bool CheckAndMarkBranchTarget(uint32_t dex_pc,
+ uint32_t end_dex_pc,
+ const Instruction* inst,
+ uint16_t inst_data);
- // Verify a switch table. "cur_offset" is the offset of the switch instruction.
+ // Verify a switch table.
// Updates "insn_flags_", setting the "branch target" flag.
- bool CheckSwitchTargets(uint32_t cur_offset);
+ ALWAYS_INLINE
+ bool CheckAndMarkSwitchTargets(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.
@@ -714,43 +735,6 @@ class MethodVerifier final : public ::art::verifier::MethodVerifier {
bool CheckCallSite(uint32_t call_site_idx);
/*
- * Verify that the target instruction is not "move-exception". It's important that the only way
- * to execute a move-exception is as the first instruction of an exception handler.
- * Returns "true" if all is well, "false" if the target instruction is move-exception.
- */
- bool CheckNotMoveException(const uint16_t* insns, int insn_idx) {
- if ((insns[insn_idx] & 0xff) == Instruction::MOVE_EXCEPTION) {
- Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "invalid use of move-exception";
- return false;
- }
- return true;
- }
-
- /*
- * Verify that the target instruction is not "move-result". It is important that we cannot
- * branch to move-result instructions, but we have to make this a distinct check instead of
- * adding it to CheckNotMoveException, because it is legal to continue into "move-result"
- * instructions - as long as the previous instruction was an invoke, which is checked elsewhere.
- */
- bool CheckNotMoveResult(const uint16_t* insns, int insn_idx) {
- if (((insns[insn_idx] & 0xff) >= Instruction::MOVE_RESULT) &&
- ((insns[insn_idx] & 0xff) <= Instruction::MOVE_RESULT_OBJECT)) {
- Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "invalid use of move-result*";
- return false;
- }
- return true;
- }
-
- /*
- * Verify that the target instruction is not "move-result" or "move-exception". This is to
- * be used when checking branch and switch instructions, but not instructions that can
- * continue.
- */
- bool CheckNotMoveExceptionOrMoveResult(const uint16_t* insns, int insn_idx) {
- return (CheckNotMoveException(insns, insn_idx) && CheckNotMoveResult(insns, insn_idx));
- }
-
- /*
* Control can transfer to "next_insn". Merge the registers from merge_line into the table at
* next_insn, and set the changed flag on the target address if any of the registers were changed.
* In the case of fall-through, update the merge line on a change as its the working line for the
@@ -804,6 +788,19 @@ class MethodVerifier final : public ::art::verifier::MethodVerifier {
}
}
+ ALWAYS_INLINE static bool IsMoveResult(Instruction::Code opcode) {
+ static_assert(Instruction::MOVE_RESULT + 1 == Instruction::MOVE_RESULT_WIDE);
+ static_assert(Instruction::MOVE_RESULT_WIDE + 1 == Instruction::MOVE_RESULT_OBJECT);
+ return Instruction::MOVE_RESULT <= opcode && opcode <= Instruction::MOVE_RESULT_OBJECT;
+ }
+
+ ALWAYS_INLINE static bool IsMoveResultOrMoveException(Instruction::Code opcode) {
+ static_assert(Instruction::MOVE_RESULT + 1 == Instruction::MOVE_RESULT_WIDE);
+ static_assert(Instruction::MOVE_RESULT_WIDE + 1 == Instruction::MOVE_RESULT_OBJECT);
+ static_assert(Instruction::MOVE_RESULT_OBJECT + 1 == Instruction::MOVE_EXCEPTION);
+ return Instruction::MOVE_RESULT <= opcode && opcode <= Instruction::MOVE_EXCEPTION;
+ }
+
NO_INLINE void FailInvalidArgCount(const Instruction* inst, uint32_t arg_count) {
Fail(VERIFY_ERROR_BAD_CLASS_HARD)
<< "invalid arg count (" << arg_count << ") in " << inst->Name();
@@ -823,6 +820,111 @@ class MethodVerifier final : public ::art::verifier::MethodVerifier {
<< "bad method index " << method_idx << " (max " << dex_file_->NumMethodIds() << ")";
}
+ 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;
+ }
+
+ NO_INLINE void FailSwitchTargetOffsetOutOfRange(uint32_t dex_pc,
+ uint32_t end_dex_pc,
+ uint32_t switch_payload_dex_pc,
+ int32_t offset,
+ uint32_t target_index) {
+ FailTargetOffsetOutOfRange(dex_pc, end_dex_pc, offset);
+ LastFailureMessageStream()
+ << " in switch payload at " << reinterpret_cast<void*>(switch_payload_dex_pc)
+ << ", target index " << target_index;
+ }
+
+ NO_INLINE void FailSwitchTargetMidInstruction(uint32_t dex_pc,
+ uint32_t target_dex_pc,
+ uint32_t switch_payload_dex_pc,
+ uint32_t target_index) {
+ FailTargetMidInstruction(dex_pc, target_dex_pc);
+ LastFailureMessageStream()
+ << " in switch payload at " << reinterpret_cast<void*>(switch_payload_dex_pc)
+ << ", target index " << target_index;
+ }
+
+ NO_INLINE void FailSwitchTargetIsMoveResultOrMoveException(uint32_t dex_pc,
+ uint32_t target_dex_pc,
+ Instruction::Code target_opcode,
+ uint32_t switch_payload_dex_pc,
+ uint32_t target_index) {
+ FailBranchTargetIsMoveResultOrMoveException(dex_pc, target_dex_pc, target_opcode);
+ LastFailureMessageStream()
+ << " in switch payload at " << reinterpret_cast<void*>(switch_payload_dex_pc)
+ << ", target index " << target_index;
+ }
+
NO_INLINE void FailForRegisterType(uint32_t vsrc,
const RegType& check_type,
const RegType& src_type,
@@ -1576,14 +1678,16 @@ bool MethodVerifier<kVerifierDebug>::ScanTryCatchBlocks() {
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()) {
- Fail(VERIFY_ERROR_BAD_CLASS_HARD)
- << "exception handler starts at bad address (" << dex_pc << ")";
+ 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";
return false;
}
- if (!CheckNotMoveResult(code_item_accessor_.Insns(), dex_pc)) {
- Fail(VERIFY_ERROR_BAD_CLASS_HARD)
- << "exception handler begins with move-result* (" << dex_pc << ")";
+ 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*";
return false;
}
GetModifiableInstructionFlags(dex_pc).SetBranchTarget();
@@ -1653,7 +1757,7 @@ bool MethodVerifier<kVerifierDebug>::VerifyInstructions() {
#define DEFINE_CASE(opcode, c, p, format, index, flags, eflags, vflags) \
case opcode: { \
constexpr Instruction::Code kOpcode = enum_cast<Instruction::Code>(opcode); \
- if (!VerifyInstruction<kOpcode>(inst, dex_pc, inst_data)) { \
+ if (!VerifyInstruction<kOpcode>(dex_pc, end_dex_pc, inst, inst_data)) { \
DCHECK_NE(failures_.size(), 0U); \
return false; \
} \
@@ -1681,8 +1785,9 @@ bool MethodVerifier<kVerifierDebug>::VerifyInstructions() {
template <bool kVerifierDebug>
template <Instruction::Code kDispatchOpcode>
-inline bool MethodVerifier<kVerifierDebug>::VerifyInstruction(const Instruction* inst,
- uint32_t code_offset,
+inline bool MethodVerifier<kVerifierDebug>::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
@@ -1773,13 +1878,13 @@ inline bool MethodVerifier<kVerifierDebug>::VerifyInstruction(const Instruction*
DCHECK_EQ(kVerifyExtra, inst->GetVerifyExtraFlags());
switch (kVerifyExtra) {
case Instruction::kVerifyArrayData:
- result = result && CheckArrayData(code_offset);
+ result = result && CheckArrayData(dex_pc, end_dex_pc, inst);
break;
case Instruction::kVerifyBranchTarget:
- result = result && CheckBranchTarget(code_offset);
+ result = result && CheckAndMarkBranchTarget<kFormat>(dex_pc, end_dex_pc, inst, inst_data);
break;
case Instruction::kVerifySwitchTargets:
- result = result && CheckSwitchTargets(code_offset);
+ result = result && CheckAndMarkSwitchTargets(dex_pc, end_dex_pc, inst, inst_data);
break;
case Instruction::kVerifyVarArgNonZero:
// Fall-through.
@@ -1864,82 +1969,77 @@ bool MethodVerifier<kVerifierDebug>::CheckNewArray(dex::TypeIndex idx) {
}
template <bool kVerifierDebug>
-bool MethodVerifier<kVerifierDebug>::CheckArrayData(uint32_t cur_offset) {
- const uint32_t insn_count = code_item_accessor_.InsnsSizeInCodeUnits();
- const uint16_t* insns = code_item_accessor_.Insns() + cur_offset;
- const uint16_t* array_data;
- int32_t array_data_offset;
-
- DCHECK_LT(cur_offset, insn_count);
- /* make sure the start of the array data table is in range */
- array_data_offset = insns[1] | (static_cast<int32_t>(insns[2]) << 16);
- if (UNLIKELY(static_cast<int32_t>(cur_offset) + array_data_offset < 0 ||
- cur_offset + array_data_offset + 2 >= insn_count)) {
- Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "invalid array data start: at " << cur_offset
- << ", data offset " << array_data_offset
- << ", count " << insn_count;
+bool MethodVerifier<kVerifierDebug>::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);
return false;
}
- /* offset to array data table is a relative branch-style offset */
- array_data = insns + array_data_offset;
- // Make sure the table is at an even dex pc, that is, 32-bit aligned.
- if (UNLIKELY(!IsAligned<4>(array_data))) {
- Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "unaligned array data table: at " << cur_offset
- << ", data offset " << array_data_offset;
+ // 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);
return false;
}
- // Make sure the array-data is marked as an opcode. This ensures that it was reached when
- // traversing the code item linearly. It is an approximation for a by-spec padding value.
- if (UNLIKELY(!GetInstructionFlags(cur_offset + array_data_offset).IsOpcode())) {
- Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "array data table at " << cur_offset
- << ", data offset " << array_data_offset
- << " not correctly visited, probably bad padding.";
+ // 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);
return false;
}
-
- uint32_t value_width = array_data[1];
- uint32_t value_count = *reinterpret_cast<const uint32_t*>(&array_data[2]);
- uint32_t table_size = 4 + (value_width * value_count + 1) / 2;
- /* make sure the end of the switch is in range */
- if (UNLIKELY(cur_offset + array_data_offset + table_size > insn_count)) {
- Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "invalid array data end: at " << cur_offset
- << ", data offset " << array_data_offset << ", end "
- << cur_offset + array_data_offset + table_size
- << ", count " << insn_count;
+ 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);
return false;
}
+ // 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);
return true;
}
template <bool kVerifierDebug>
-bool MethodVerifier<kVerifierDebug>::CheckBranchTarget(uint32_t cur_offset) {
+template <Instruction::Format kFormat>
+bool MethodVerifier<kVerifierDebug>::CheckAndMarkBranchTarget(uint32_t dex_pc,
+ uint32_t end_dex_pc,
+ const Instruction* inst,
+ uint16_t inst_data) {
int32_t offset;
- bool isConditional, selfOkay;
- if (!GetBranchOffset(cur_offset, &offset, &isConditional, &selfOkay)) {
+ if constexpr (kFormat == Instruction::k22t) { // if-<cond>?
+ offset = inst->VRegC(kFormat);
+ } else if constexpr (kFormat == Instruction::k21t) { // if-<cond>z?
+ offset = inst->VRegB(kFormat, /*unused*/ inst_data);
+ } else { // goto
+ offset = inst->VRegA(kFormat, inst_data);
+ }
+ // Only `goto/32` instruction can target itself. For other instructions `offset` must not be 0.
+ DCHECK_EQ(kFormat == Instruction::k30t,
+ code_item_accessor_.InstructionAt(dex_pc).Opcode() == Instruction::GOTO_32);
+ if (kFormat != Instruction::k30t && UNLIKELY(offset == 0)) {
+ FailBranchOffsetZero(dex_pc);
return false;
}
- if (UNLIKELY(!selfOkay && offset == 0)) {
- Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "branch offset of zero not allowed at"
- << reinterpret_cast<void*>(cur_offset);
+ if (!IsOffsetInRange(dex_pc, end_dex_pc, offset)) {
+ FailTargetOffsetOutOfRange(dex_pc, end_dex_pc, offset);
return false;
}
- // Check for 32-bit overflow. This isn't strictly necessary if we can depend on the runtime
- // to have identical "wrap-around" behavior, but it's unwise to depend on that.
- if (UNLIKELY(((int64_t) cur_offset + (int64_t) offset) != (int64_t) (cur_offset + offset))) {
- Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "branch target overflow "
- << reinterpret_cast<void*>(cur_offset) << " +" << offset;
+ uint32_t target_dex_pc = dex_pc + offset;
+ if (UNLIKELY(!GetInstructionFlags(target_dex_pc).IsOpcode())) {
+ FailTargetMidInstruction(dex_pc, target_dex_pc);
return false;
}
- int32_t abs_offset = cur_offset + offset;
- if (UNLIKELY(abs_offset < 0 ||
- (uint32_t) abs_offset >= code_item_accessor_.InsnsSizeInCodeUnits() ||
- !GetInstructionFlags(abs_offset).IsOpcode())) {
- Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "invalid branch target " << offset << " (-> "
- << reinterpret_cast<void*>(abs_offset) << ") at "
- << reinterpret_cast<void*>(cur_offset);
+ Instruction::Code target_opcode = inst->RelativeAt(offset)->Opcode();
+ if (UNLIKELY(IsMoveResultOrMoveException(target_opcode))) {
+ FailBranchTargetIsMoveResultOrMoveException(dex_pc, target_dex_pc, target_opcode);
return false;
}
- GetModifiableInstructionFlags(abs_offset).SetBranchTarget();
+ GetModifiableInstructionFlags(target_dex_pc).SetBranchTarget();
return true;
}
@@ -1984,37 +2084,37 @@ bool MethodVerifier<kVerifierDebug>::GetBranchOffset(uint32_t cur_offset,
}
template <bool kVerifierDebug>
-bool MethodVerifier<kVerifierDebug>::CheckSwitchTargets(uint32_t cur_offset) {
- const uint32_t insn_count = code_item_accessor_.InsnsSizeInCodeUnits();
- DCHECK_LT(cur_offset, insn_count);
- const uint16_t* insns = code_item_accessor_.Insns() + cur_offset;
- /* make sure the start of the switch is in range */
- int32_t switch_offset = insns[1] | (static_cast<int32_t>(insns[2]) << 16);
- if (UNLIKELY(static_cast<int32_t>(cur_offset) + switch_offset < 0 ||
- cur_offset + switch_offset + 2 > insn_count)) {
- Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "invalid switch start: at " << cur_offset
- << ", switch offset " << switch_offset
- << ", count " << insn_count;
+bool MethodVerifier<kVerifierDebug>::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);
return false;
}
- /* offset to switch table is a relative branch-style offset */
- const uint16_t* switch_insns = insns + switch_offset;
- // Make sure the table is at an even dex pc, that is, 32-bit aligned.
- if (UNLIKELY(!IsAligned<4>(switch_insns))) {
- Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "unaligned switch table: at " << cur_offset
- << ", switch offset " << switch_offset;
+ // 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);
return false;
}
- // Make sure the switch data is marked as an opcode. This ensures that it was reached when
- // traversing the code item linearly. It is an approximation for a by-spec padding value.
- if (UNLIKELY(!GetInstructionFlags(cur_offset + switch_offset).IsOpcode())) {
- Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "switch table at " << cur_offset
- << ", switch offset " << switch_offset
- << " not correctly visited, probably bad padding.";
+ // 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);
return false;
}
- bool is_packed_switch = (*insns & 0xff) == Instruction::PACKED_SWITCH;
+ /* 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);
+
+ bool is_packed_switch = inst->Opcode(inst_data) == Instruction::PACKED_SWITCH;
+ DCHECK_IMPLIES(!is_packed_switch, inst->Opcode(inst_data) == Instruction::SPARSE_SWITCH);
uint32_t switch_count = switch_insns[1];
int32_t targets_offset;
@@ -2028,21 +2128,15 @@ bool MethodVerifier<kVerifierDebug>::CheckSwitchTargets(uint32_t cur_offset) {
targets_offset = 2 + 2 * switch_count;
expected_signature = Instruction::kSparseSwitchSignature;
}
- uint32_t table_size = targets_offset + switch_count * 2;
- if (UNLIKELY(switch_insns[0] != expected_signature)) {
- Fail(VERIFY_ERROR_BAD_CLASS_HARD)
- << StringPrintf("wrong signature for switch table (%x, wanted %x)",
- switch_insns[0], expected_signature);
- return false;
- }
- /* make sure the end of the switch is in range */
- if (UNLIKELY(cur_offset + switch_offset + table_size > (uint32_t) insn_count)) {
- Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "invalid switch end: at " << cur_offset
- << ", switch offset " << switch_offset
- << ", end " << (cur_offset + switch_offset + table_size)
- << ", count " << insn_count;
+ uint16_t signature = switch_insns[0];
+ if (UNLIKELY(signature != expected_signature)) {
+ FailBadSwitchPayloadSignature(dex_pc, switch_payload_dex_pc, signature, expected_signature);
return false;
}
+ // The table size has been verified in `ComputeWidthsAndCountOps()`.
+ uint32_t table_size = targets_offset + switch_count * 2;
+ DCHECK_LT(switch_payload_dex_pc, end_dex_pc);
+ DCHECK_LE(table_size, end_dex_pc - switch_payload_dex_pc);
constexpr int32_t keys_offset = 2;
if (switch_count > 1) {
@@ -2052,8 +2146,7 @@ bool MethodVerifier<kVerifierDebug>::CheckSwitchTargets(uint32_t cur_offset) {
int32_t max_first_key =
std::numeric_limits<int32_t>::max() - (static_cast<int32_t>(switch_count) - 1);
if (UNLIKELY(first_key > max_first_key)) {
- Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "invalid packed switch: first_key=" << first_key
- << ", switch_count=" << switch_count;
+ FailPackedSwitchKeyOverflow(dex_pc, switch_payload_dex_pc, first_key, switch_count);
return false;
}
} else {
@@ -2064,8 +2157,7 @@ bool MethodVerifier<kVerifierDebug>::CheckSwitchTargets(uint32_t cur_offset) {
static_cast<int32_t>(switch_insns[keys_offset + targ * 2]) |
static_cast<int32_t>(switch_insns[keys_offset + targ * 2 + 1] << 16);
if (UNLIKELY(key <= last_key)) {
- Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "invalid sparse switch: last key=" << last_key
- << ", this=" << key;
+ FailSparseSwitchPayloadKeyOrder(dex_pc, switch_payload_dex_pc, last_key, key);
return false;
}
last_key = key;
@@ -2076,17 +2168,22 @@ bool MethodVerifier<kVerifierDebug>::CheckSwitchTargets(uint32_t cur_offset) {
for (uint32_t targ = 0; targ < switch_count; targ++) {
int32_t offset = static_cast<int32_t>(switch_insns[targets_offset + targ * 2]) |
static_cast<int32_t>(switch_insns[targets_offset + targ * 2 + 1] << 16);
- int32_t abs_offset = cur_offset + offset;
- if (UNLIKELY(abs_offset < 0 ||
- abs_offset >= static_cast<int32_t>(insn_count) ||
- !GetInstructionFlags(abs_offset).IsOpcode())) {
- Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "invalid switch target " << offset
- << " (-> " << reinterpret_cast<void*>(abs_offset) << ") at "
- << reinterpret_cast<void*>(cur_offset)
- << "[" << targ << "]";
+ if (!IsOffsetInRange(dex_pc, end_dex_pc, offset)) {
+ FailSwitchTargetOffsetOutOfRange(dex_pc, end_dex_pc, switch_payload_dex_pc, offset, targ);
return false;
}
- GetModifiableInstructionFlags(abs_offset).SetBranchTarget();
+ uint32_t target_dex_pc = dex_pc + offset;
+ if (UNLIKELY(!GetInstructionFlags(target_dex_pc).IsOpcode())) {
+ FailSwitchTargetMidInstruction(dex_pc, target_dex_pc, switch_payload_dex_pc, targ);
+ return false;
+ }
+ Instruction::Code target_opcode = inst->RelativeAt(offset)->Opcode();
+ if (UNLIKELY(IsMoveResultOrMoveException(target_opcode))) {
+ FailSwitchTargetIsMoveResultOrMoveException(
+ dex_pc, target_dex_pc, target_opcode, switch_payload_dex_pc, targ);
+ return false;
+ }
+ GetModifiableInstructionFlags(target_dex_pc).SetBranchTarget();
}
return true;
}
@@ -2627,7 +2724,7 @@ bool MethodVerifier<kVerifierDebug>::CodeFlowVerifyInstruction(uint32_t* start_g
/* check the register contents */
bool success = VerifyRegisterType(vregA, use_src ? src_type : return_type);
if (!success) {
- AppendToLastFailMessage(StringPrintf(" return-1nr on invalid register v%d", vregA));
+ LastFailureMessageStream() << " return-1nr on invalid register v" << vregA;
}
}
}
@@ -2643,7 +2740,7 @@ bool MethodVerifier<kVerifierDebug>::CodeFlowVerifyInstruction(uint32_t* start_g
const uint32_t vregA = inst->VRegA_11x(inst_data);
bool success = VerifyRegisterTypeWide(vregA, return_type.GetKind());
if (!success) {
- AppendToLastFailMessage(StringPrintf(" return-wide on invalid register v%d", vregA));
+ LastFailureMessageStream() << " return-wide on invalid register v" << vregA;
}
}
}
@@ -3004,19 +3101,16 @@ bool MethodVerifier<kVerifierDebug>::CodeFlowVerifyInstruction(uint32_t* start_g
<< component_type;
} else {
// Now verify if the element width in the table matches the element width declared in
- // the array
+ // the array. The signature has been verified by `CheckArrayData()`.
const uint16_t* array_data =
insns + (insns[1] | (static_cast<int32_t>(insns[2]) << 16));
- if (array_data[0] != Instruction::kArrayDataSignature) {
- Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "invalid magic for array-data";
- } else {
- size_t elem_width = Primitive::ComponentSize(component_type.GetPrimitiveType());
- // Since we don't compress the data in Dex, expect to see equal width of data stored
- // in the table and expected from the array class.
- if (array_data[1] != elem_width) {
- Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "array-data size mismatch (" << array_data[1]
- << " vs " << elem_width << ")";
- }
+ 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 << ")";
}
}
}
@@ -3778,10 +3872,7 @@ bool MethodVerifier<kVerifierDebug>::CodeFlowVerifyInstruction(uint32_t* start_g
return false;
}
DCHECK_EQ(isConditional, (opcode_flags & Instruction::kContinue) != 0);
- if (!CheckNotMoveExceptionOrMoveResult(code_item_accessor_.Insns(),
- work_insn_idx_ + branch_target)) {
- return false;
- }
+ DCHECK(!IsMoveResultOrMoveException(inst->RelativeAt(branch_target)->Opcode()));
/* update branch target, set "changed" if appropriate */
if (nullptr != branch_line) {
if (!UpdateRegisters(work_insn_idx_ + branch_target, branch_line.get(), false)) {
@@ -3825,9 +3916,7 @@ bool MethodVerifier<kVerifierDebug>::CodeFlowVerifyInstruction(uint32_t* start_g
(static_cast<int32_t>(switch_insns[offset_to_targets + targ * 2 + 1]) << 16);
abs_offset = work_insn_idx_ + offset;
DCHECK_LT(abs_offset, code_item_accessor_.InsnsSizeInCodeUnits());
- if (!CheckNotMoveExceptionOrMoveResult(code_item_accessor_.Insns(), abs_offset)) {
- return false;
- }
+ DCHECK(!IsMoveResultOrMoveException(inst->RelativeAt(offset)->Opcode()));
if (!UpdateRegisters(abs_offset, work_line_.get(), false)) {
return false;
}
@@ -3911,7 +4000,9 @@ bool MethodVerifier<kVerifierDebug>::CodeFlowVerifyInstruction(uint32_t* start_g
}
// The only way to get to a move-exception instruction is to get thrown there. Make sure the
// next instruction isn't one.
- if (!CheckNotMoveException(code_item_accessor_.Insns(), next_insn_idx)) {
+ 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) << "invalid use of move-exception";
return false;
}
if (nullptr != fallthrough_line) {
@@ -4118,9 +4209,8 @@ ArtMethod* MethodVerifier<kVerifierDebug>::ResolveMethodAndCheckAccess(
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()) {
- std::string append(" in attempt to access method ");
- append += dex_file_->GetMethodName(method_id);
- AppendToLastFailMessage(append);
+ LastFailureMessageStream()
+ << " in attempt to access method " << dex_file_->GetMethodName(method_id);
return nullptr;
}
if (klass_type.IsUnresolvedTypes()) {