summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--runtime/Android.bp1
-rw-r--r--runtime/verifier/method_verifier.cc39
-rw-r--r--runtime/verifier/method_verifier.h13
-rw-r--r--runtime/verifier/reg_type.cc7
-rw-r--r--runtime/verifier/reg_type.h21
-rw-r--r--runtime/verifier/reg_type_cache.cc8
-rw-r--r--runtime/verifier/reg_type_cache.h2
-rw-r--r--runtime/verifier/reg_type_test.cc53
-rw-r--r--runtime/verifier/register_line-inl.h57
-rw-r--r--runtime/verifier/register_line.cc88
-rw-r--r--runtime/verifier/register_line.h68
-rw-r--r--runtime/verifier/register_line_test.cc168
12 files changed, 379 insertions, 146 deletions
diff --git a/runtime/Android.bp b/runtime/Android.bp
index ff9870e908..079e7d1185 100644
--- a/runtime/Android.bp
+++ b/runtime/Android.bp
@@ -1129,6 +1129,7 @@ art_cc_defaults {
"vdex_file_test.cc",
"verifier/method_verifier_test.cc",
"verifier/reg_type_test.cc",
+ "verifier/register_line_test.cc",
],
static_libs: [
"libgmock",
diff --git a/runtime/verifier/method_verifier.cc b/runtime/verifier/method_verifier.cc
index 52907beb5d..27bc440f42 100644
--- a/runtime/verifier/method_verifier.cc
+++ b/runtime/verifier/method_verifier.cc
@@ -2427,12 +2427,11 @@ bool MethodVerifier<kVerifierDebug>::CodeFlowVerifyInstruction(uint32_t* start_g
<< "new-instance on primitive, interface or abstract class" << res_type;
// Soft failure so carry on to set register type.
}
- const RegType& uninit_type = reg_types_.Uninitialized(res_type, work_insn_idx_);
- // Any registers holding previous allocations from this address that have not yet been
- // initialized must be marked invalid.
- work_line_->MarkUninitRefsAsInvalid(this, uninit_type);
- // add the new uninitialized reference to the register state
- work_line_->SetRegisterType<LockOp::kClear>(inst->VRegA_21c(), uninit_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();
+ work_line_->DCheckUniqueNewInstanceDexPc(this, work_insn_idx_);
+ work_line_->SetRegisterTypeForNewInstance(vA, uninit_type, work_insn_idx_);
break;
}
case Instruction::NEW_ARRAY:
@@ -2878,7 +2877,7 @@ bool MethodVerifier<kVerifierDebug>::CodeFlowVerifyInstruction(uint32_t* start_g
* allowing the latter only if the "this" argument is the same as the "this" argument to
* this method (which implies that we're in a constructor ourselves).
*/
- const RegType& this_type = work_line_->GetInvocationThis(this, inst);
+ const RegType& this_type = GetInvocationThis(inst);
if (this_type.IsConflict()) // failure.
break;
@@ -2908,7 +2907,7 @@ bool MethodVerifier<kVerifierDebug>::CodeFlowVerifyInstruction(uint32_t* start_g
* Replace the uninitialized reference with an initialized one. We need to do this for all
* registers that have the same object instance in them, not just the "this" register.
*/
- work_line_->MarkRefsAsInitialized(this, this_type);
+ work_line_->MarkRefsAsInitialized(this, inst->VRegC());
}
const RegType& return_type = reg_types_.FromTypeIndex(return_type_idx);
if (!return_type.IsLowHalf()) {
@@ -2953,7 +2952,7 @@ bool MethodVerifier<kVerifierDebug>::CodeFlowVerifyInstruction(uint32_t* start_g
/* Get the type of the "this" arg, which should either be a sub-interface of called
* interface or Object (see comments in RegType::JoinClass).
*/
- const RegType& this_type = work_line_->GetInvocationThis(this, inst);
+ const RegType& this_type = GetInvocationThis(inst);
if (this_type.IsZeroOrNull()) {
/* null pointer always passes (and always fails at runtime) */
} else {
@@ -3831,7 +3830,7 @@ ArtMethod* MethodVerifier<kVerifierDebug>::VerifyInvocationArgsFromIterator(
* rigorous check here (which is okay since we have to do it at runtime).
*/
if (method_type != METHOD_STATIC) {
- const RegType& actual_arg_type = work_line_->GetInvocationThis(this, inst);
+ const RegType& actual_arg_type = GetInvocationThis(inst);
if (actual_arg_type.IsConflict()) { // GetInvocationThis failed.
CHECK(flags_.have_pending_hard_failure_);
return nullptr;
@@ -4142,7 +4141,7 @@ bool MethodVerifier<kVerifierDebug>::CheckSignaturePolymorphicMethod(ArtMethod*
template <bool kVerifierDebug>
bool MethodVerifier<kVerifierDebug>::CheckSignaturePolymorphicReceiver(const Instruction* inst) {
- const RegType& this_type = work_line_->GetInvocationThis(this, inst);
+ const RegType& this_type = GetInvocationThis(inst);
if (this_type.IsZeroOrNull()) {
/* null pointer always passes (and always fails at run time) */
return true;
@@ -5219,5 +5218,23 @@ void MethodVerifier::FailureData::Merge(const MethodVerifier::FailureData& fd) {
types |= fd.types;
}
+const RegType& MethodVerifier::GetInvocationThis(const Instruction* inst) {
+ DCHECK(inst->IsInvoke());
+ const size_t args_count = inst->VRegA();
+ if (args_count < 1) {
+ Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "invoke lacks 'this'";
+ return reg_types_.Conflict();
+ }
+ const uint32_t this_reg = inst->VRegC();
+ const RegType& this_type = work_line_->GetRegisterType(this, this_reg);
+ if (!this_type.IsReferenceTypes()) {
+ Fail(VERIFY_ERROR_BAD_CLASS_HARD)
+ << "tried to get class from non-reference register v" << this_reg
+ << " (type=" << this_type << ")";
+ return reg_types_.Conflict();
+ }
+ return this_type;
+}
+
} // namespace verifier
} // namespace art
diff --git a/runtime/verifier/method_verifier.h b/runtime/verifier/method_verifier.h
index 711b455c2d..5b1f2e0750 100644
--- a/runtime/verifier/method_verifier.h
+++ b/runtime/verifier/method_verifier.h
@@ -57,8 +57,6 @@ class DexCache;
namespace verifier {
class MethodVerifier;
-class RegisterLine;
-using RegisterLineArenaUniquePtr = std::unique_ptr<RegisterLine, RegisterLineArenaDelete>;
class RegType;
class RegTypeCache;
struct ScopedNewLine;
@@ -253,6 +251,16 @@ class MethodVerifier {
std::string* hard_failure_msg)
REQUIRES_SHARED(Locks::mutator_lock_);
+ /*
+ * Get the "this" pointer from a non-static method invocation. This returns the RegType so the
+ * caller can decide whether it needs the reference to be initialized or not.
+ *
+ * The argument count is in vA, and the first argument is in vC, for both "simple" and "range"
+ * versions. We just need to make sure vA is >= 1 and then return vC.
+ */
+ const RegType& GetInvocationThis(const Instruction* inst)
+ REQUIRES_SHARED(Locks::mutator_lock_);
+
// For VerifierDepsTest. TODO: Refactor.
// Run verification on the method. Returns true if verification completes and false if the input
@@ -342,6 +350,7 @@ class MethodVerifier {
friend class art::Thread;
friend class ClassVerifier;
+ friend class RegisterLineTest;
friend class VerifierDepsTest;
DISALLOW_COPY_AND_ASSIGN(MethodVerifier);
diff --git a/runtime/verifier/reg_type.cc b/runtime/verifier/reg_type.cc
index 23f1ff0cab..1202c4c87e 100644
--- a/runtime/verifier/reg_type.cc
+++ b/runtime/verifier/reg_type.cc
@@ -159,8 +159,7 @@ std::string UnresolvedReferenceType::Dump() const {
std::string UnresolvedUninitializedRefType::Dump() const {
std::stringstream result;
result << "Unresolved And Uninitialized Reference: "
- << PrettyDescriptor(std::string(GetDescriptor()).c_str())
- << " Allocation PC: " << GetAllocationPc();
+ << PrettyDescriptor(std::string(GetDescriptor()).c_str());
return result.str();
}
@@ -180,14 +179,12 @@ std::string ReferenceType::Dump() const {
std::string UninitializedReferenceType::Dump() const {
std::stringstream result;
result << "Uninitialized Reference: " << mirror::Class::PrettyDescriptor(GetClass());
- result << " Allocation PC: " << GetAllocationPc();
return result.str();
}
std::string UninitializedThisReferenceType::Dump() const {
std::stringstream result;
result << "Uninitialized This Reference: " << mirror::Class::PrettyDescriptor(GetClass());
- result << "Allocation PC: " << GetAllocationPc();
return result.str();
}
@@ -725,11 +722,9 @@ void RegType::CheckInvariants() const {
}
void UninitializedThisReferenceType::CheckInvariants() const {
- CHECK_EQ(GetAllocationPc(), 0U) << *this;
}
void UnresolvedUninitializedThisRefType::CheckInvariants() const {
- CHECK_EQ(GetAllocationPc(), 0U) << *this;
CHECK(!descriptor_.empty()) << *this;
CHECK(!HasClass()) << *this;
}
diff --git a/runtime/verifier/reg_type.h b/runtime/verifier/reg_type.h
index 6d52aafceb..4854a7bd65 100644
--- a/runtime/verifier/reg_type.h
+++ b/runtime/verifier/reg_type.h
@@ -767,24 +767,15 @@ class UninitializedType : public RegType {
public:
UninitializedType(Handle<mirror::Class> klass,
const std::string_view& descriptor,
- uint32_t allocation_pc,
uint16_t cache_id)
- : RegType(klass, descriptor, cache_id), allocation_pc_(allocation_pc) {}
+ : RegType(klass, descriptor, cache_id) {}
bool IsUninitializedTypes() const override;
bool IsNonZeroReferenceTypes() const override;
- uint32_t GetAllocationPc() const {
- DCHECK(IsUninitializedTypes());
- return allocation_pc_;
- }
-
AssignmentType GetAssignmentTypeImpl() const override {
return AssignmentType::kReference;
}
-
- private:
- const uint32_t allocation_pc_;
};
// Similar to ReferenceType but not yet having been passed to a constructor.
@@ -792,10 +783,9 @@ class UninitializedReferenceType final : public UninitializedType {
public:
UninitializedReferenceType(Handle<mirror::Class> klass,
const std::string_view& descriptor,
- uint32_t allocation_pc,
uint16_t cache_id)
REQUIRES_SHARED(Locks::mutator_lock_)
- : UninitializedType(klass, descriptor, allocation_pc, cache_id) {
+ : UninitializedType(klass, descriptor, cache_id) {
CheckConstructorInvariants(this);
}
@@ -812,10 +802,9 @@ class UnresolvedUninitializedRefType final : public UninitializedType {
public:
UnresolvedUninitializedRefType(Handle<mirror::Class> klass,
const std::string_view& descriptor,
- uint32_t allocation_pc,
uint16_t cache_id)
REQUIRES_SHARED(Locks::mutator_lock_)
- : UninitializedType(klass, descriptor, allocation_pc, cache_id) {
+ : UninitializedType(klass, descriptor, cache_id) {
CheckConstructorInvariants(this);
}
@@ -837,7 +826,7 @@ class UninitializedThisReferenceType final : public UninitializedType {
const std::string_view& descriptor,
uint16_t cache_id)
REQUIRES_SHARED(Locks::mutator_lock_)
- : UninitializedType(klass, descriptor, 0, cache_id) {
+ : UninitializedType(klass, descriptor, cache_id) {
CheckConstructorInvariants(this);
}
@@ -857,7 +846,7 @@ class UnresolvedUninitializedThisRefType final : public UninitializedType {
const std::string_view& descriptor,
uint16_t cache_id)
REQUIRES_SHARED(Locks::mutator_lock_)
- : UninitializedType(klass, descriptor, 0, cache_id) {
+ : UninitializedType(klass, descriptor, cache_id) {
CheckConstructorInvariants(this);
}
diff --git a/runtime/verifier/reg_type_cache.cc b/runtime/verifier/reg_type_cache.cc
index 98769fab44..e9cc482046 100644
--- a/runtime/verifier/reg_type_cache.cc
+++ b/runtime/verifier/reg_type_cache.cc
@@ -392,37 +392,31 @@ const RegType& RegTypeCache::FromUnresolvedSuperClass(const RegType& child) {
null_handle_, child.GetId(), this, entries_.size()));
}
-const UninitializedType& RegTypeCache::Uninitialized(const RegType& type, uint32_t allocation_pc) {
+const UninitializedType& RegTypeCache::Uninitialized(const RegType& type) {
UninitializedType* entry = nullptr;
const std::string_view& descriptor(type.GetDescriptor());
if (type.IsUnresolvedTypes()) {
for (size_t i = kNumPrimitivesAndSmallConstants; i < entries_.size(); i++) {
const RegType* cur_entry = entries_[i];
if (cur_entry->IsUnresolvedAndUninitializedReference() &&
- down_cast<const UnresolvedUninitializedRefType*>(cur_entry)->GetAllocationPc()
- == allocation_pc &&
(cur_entry->GetDescriptor() == descriptor)) {
return *down_cast<const UnresolvedUninitializedRefType*>(cur_entry);
}
}
entry = new (&allocator_) UnresolvedUninitializedRefType(null_handle_,
descriptor,
- allocation_pc,
entries_.size());
} else {
ObjPtr<mirror::Class> klass = type.GetClass();
for (size_t i = kNumPrimitivesAndSmallConstants; i < entries_.size(); i++) {
const RegType* cur_entry = entries_[i];
if (cur_entry->IsUninitializedReference() &&
- down_cast<const UninitializedReferenceType*>(cur_entry)
- ->GetAllocationPc() == allocation_pc &&
cur_entry->GetClass() == klass) {
return *down_cast<const UninitializedReferenceType*>(cur_entry);
}
}
entry = new (&allocator_) UninitializedReferenceType(handles_.NewHandle(klass),
descriptor,
- allocation_pc,
entries_.size());
}
return AddEntry(entry);
diff --git a/runtime/verifier/reg_type_cache.h b/runtime/verifier/reg_type_cache.h
index d977958afe..61710a21a5 100644
--- a/runtime/verifier/reg_type_cache.h
+++ b/runtime/verifier/reg_type_cache.h
@@ -144,7 +144,7 @@ class RegTypeCache {
const ReferenceType& JavaLangThrowable() REQUIRES_SHARED(Locks::mutator_lock_);
const ReferenceType& JavaLangObject() REQUIRES_SHARED(Locks::mutator_lock_);
- const UninitializedType& Uninitialized(const RegType& type, uint32_t allocation_pc)
+ const UninitializedType& Uninitialized(const RegType& type)
REQUIRES_SHARED(Locks::mutator_lock_);
// Create an uninitialized 'this' argument for the given type.
const UninitializedType& UninitializedThisArgument(const RegType& type)
diff --git a/runtime/verifier/reg_type_test.cc b/runtime/verifier/reg_type_test.cc
index 693dff4602..03224822b2 100644
--- a/runtime/verifier/reg_type_test.cc
+++ b/runtime/verifier/reg_type_test.cc
@@ -411,18 +411,14 @@ TEST_F(RegTypeReferenceTest, UnresolvedUnintializedType) {
EXPECT_TRUE(ref_type_0.IsUnresolvedReference());
const RegType& ref_type = cache.FromDescriptor("Ljava/lang/DoesNotExist;");
EXPECT_TRUE(ref_type_0.Equals(ref_type));
- // Create an uninitialized type of this unresolved type
- const RegType& unresolved_unintialised = cache.Uninitialized(ref_type, 1101ull);
- EXPECT_TRUE(unresolved_unintialised.IsUnresolvedAndUninitializedReference());
- EXPECT_TRUE(unresolved_unintialised.IsUninitializedTypes());
- EXPECT_TRUE(unresolved_unintialised.IsNonZeroReferenceTypes());
- // Create an uninitialized type of this unresolved type with different PC
- const RegType& ref_type_unresolved_unintialised_1 = cache.Uninitialized(ref_type, 1102ull);
- EXPECT_TRUE(unresolved_unintialised.IsUnresolvedAndUninitializedReference());
- EXPECT_FALSE(unresolved_unintialised.Equals(ref_type_unresolved_unintialised_1));
- // Create an uninitialized type of this unresolved type with the same PC
- const RegType& unresolved_unintialised_2 = cache.Uninitialized(ref_type, 1101ull);
- EXPECT_TRUE(unresolved_unintialised.Equals(unresolved_unintialised_2));
+ // Create an uninitialized type of this unresolved type.
+ const RegType& unresolved_uninitialized = cache.Uninitialized(ref_type);
+ EXPECT_TRUE(unresolved_uninitialized.IsUnresolvedAndUninitializedReference());
+ EXPECT_TRUE(unresolved_uninitialized.IsUninitializedTypes());
+ EXPECT_TRUE(unresolved_uninitialized.IsNonZeroReferenceTypes());
+ // Create an another uninitialized type of this unresolved type.
+ const RegType& unresolved_uninitialized_2 = cache.Uninitialized(ref_type);
+ EXPECT_TRUE(unresolved_uninitialized.Equals(unresolved_uninitialized_2));
}
TEST_F(RegTypeReferenceTest, Dump) {
@@ -434,8 +430,8 @@ TEST_F(RegTypeReferenceTest, Dump) {
const RegType& unresolved_ref = cache.FromDescriptor("Ljava/lang/DoesNotExist;");
const RegType& unresolved_ref_another = cache.FromDescriptor("Ljava/lang/DoesNotExistEither;");
const RegType& resolved_ref = cache.JavaLangString();
- const RegType& resolved_unintialiesd = cache.Uninitialized(resolved_ref, 10);
- const RegType& unresolved_unintialized = cache.Uninitialized(unresolved_ref, 12);
+ const RegType& resolved_uninitialized = cache.Uninitialized(resolved_ref);
+ const RegType& unresolved_uninitialized = cache.Uninitialized(unresolved_ref);
const RegType& unresolved_merged = cache.FromUnresolvedMerge(
unresolved_ref, unresolved_ref_another, /* verifier= */ nullptr);
@@ -443,10 +439,10 @@ TEST_F(RegTypeReferenceTest, Dump) {
EXPECT_EQ(expected, unresolved_ref.Dump());
expected = "Reference: java.lang.String";
EXPECT_EQ(expected, resolved_ref.Dump());
- expected ="Uninitialized Reference: java.lang.String Allocation PC: 10";
- EXPECT_EQ(expected, resolved_unintialiesd.Dump());
- expected = "Unresolved And Uninitialized Reference: java.lang.DoesNotExist Allocation PC: 12";
- EXPECT_EQ(expected, unresolved_unintialized.Dump());
+ expected ="Uninitialized Reference: java.lang.String";
+ EXPECT_EQ(expected, resolved_uninitialized .Dump());
+ expected = "Unresolved And Uninitialized Reference: java.lang.DoesNotExist";
+ EXPECT_EQ(expected, unresolved_uninitialized.Dump());
expected = "UnresolvedMergedReferences(Zero/null | Unresolved Reference: java.lang.DoesNotExist, Unresolved Reference: java.lang.DoesNotExistEither)";
EXPECT_EQ(expected, unresolved_merged.Dump());
}
@@ -468,9 +464,9 @@ TEST_F(RegTypeReferenceTest, JavalangString) {
EXPECT_TRUE(ref_type.IsReference());
// Create an uninitialized type out of this:
- const RegType& ref_type_unintialized = cache.Uninitialized(ref_type, 0110ull);
- EXPECT_TRUE(ref_type_unintialized.IsUninitializedReference());
- EXPECT_FALSE(ref_type_unintialized.IsUnresolvedAndUninitializedReference());
+ const RegType& ref_type_uninitialized = cache.Uninitialized(ref_type);
+ EXPECT_TRUE(ref_type_uninitialized.IsUninitializedReference());
+ EXPECT_FALSE(ref_type_uninitialized.IsUnresolvedAndUninitializedReference());
}
TEST_F(RegTypeReferenceTest, JavalangObject) {
@@ -747,20 +743,19 @@ TEST_F(RegTypeTest, MergeSemiLatticeRef) {
ASSERT_TRUE(unresolved_ab.IsUnresolvedMergedReference());
const RegType& uninit_this = cache.UninitializedThisArgument(obj);
- const RegType& uninit_obj_0 = cache.Uninitialized(obj, 0u);
- const RegType& uninit_obj_1 = cache.Uninitialized(obj, 1u);
+ const RegType& uninit_obj = cache.Uninitialized(obj);
const RegType& uninit_unres_this = cache.UninitializedThisArgument(unresolved_a);
- const RegType& uninit_unres_a_0 = cache.Uninitialized(unresolved_a, 0);
- const RegType& uninit_unres_b_0 = cache.Uninitialized(unresolved_b, 0);
+ const RegType& uninit_unres_a = cache.Uninitialized(unresolved_a);
+ const RegType& uninit_unres_b = cache.Uninitialized(unresolved_b);
const RegType& number = cache.FromDescriptor("Ljava/lang/Number;");
ASSERT_FALSE(number.IsUnresolvedReference());
const RegType& integer = cache.FromDescriptor("Ljava/lang/Integer;");
ASSERT_FALSE(integer.IsUnresolvedReference());
- const RegType& uninit_number_0 = cache.Uninitialized(number, 0u);
- const RegType& uninit_integer_0 = cache.Uninitialized(integer, 0u);
+ const RegType& uninit_number = cache.Uninitialized(number);
+ const RegType& uninit_integer = cache.Uninitialized(integer);
const RegType& number_arr = cache.FromDescriptor("[Ljava/lang/Number;");
ASSERT_FALSE(number_arr.IsUnresolvedReference());
@@ -789,7 +784,7 @@ TEST_F(RegTypeTest, MergeSemiLatticeRef) {
const RegType& unresolved_ab_int = cache.FromUnresolvedMerge(unresolved_ab, integer, nullptr);
ASSERT_TRUE(unresolved_ab_int.IsUnresolvedMergedReference());
std::vector<const RegType*> uninitialized_types = {
- &uninit_this, &uninit_obj_0, &uninit_obj_1, &uninit_number_0, &uninit_integer_0
+ &uninit_this, &uninit_obj, &uninit_number, &uninit_integer
};
std::vector<const RegType*> unresolved_types = {
&unresolved_a,
@@ -803,7 +798,7 @@ TEST_F(RegTypeTest, MergeSemiLatticeRef) {
&unresolved_ab_int
};
std::vector<const RegType*> uninit_unresolved_types = {
- &uninit_unres_this, &uninit_unres_a_0, &uninit_unres_b_0
+ &uninit_unres_this, &uninit_unres_a, &uninit_unres_b
};
std::vector<const RegType*> plain_nonobj_classes = { &number, &integer };
std::vector<const RegType*> plain_nonobj_arr_classes = {
diff --git a/runtime/verifier/register_line-inl.h b/runtime/verifier/register_line-inl.h
index 3967aaa88e..3371e9448d 100644
--- a/runtime/verifier/register_line-inl.h
+++ b/runtime/verifier/register_line-inl.h
@@ -87,6 +87,16 @@ inline void RegisterLine::SetResultRegisterTypeWide(const RegType& new_type1,
result_[1] = new_type2.GetId();
}
+inline void RegisterLine::SetRegisterTypeForNewInstance(uint32_t vdst,
+ const RegType& uninit_type,
+ uint32_t dex_pc) {
+ DCHECK_LT(vdst, num_regs_);
+ DCHECK(NeedsAllocationDexPc(uninit_type));
+ SetRegisterType<LockOp::kClear>(vdst, uninit_type);
+ EnsureAllocationDexPcsAvailable();
+ allocation_dex_pcs_[vdst] = dex_pc;
+}
+
inline void RegisterLine::CopyRegister1(MethodVerifier* verifier, uint32_t vdst, uint32_t vsrc,
TypeCategory cat) {
DCHECK(cat == kTypeCategory1nr || cat == kTypeCategoryRef);
@@ -96,6 +106,8 @@ inline void RegisterLine::CopyRegister1(MethodVerifier* verifier, uint32_t vdst,
<< type << "'";
return;
}
+ // FIXME: If `vdst == vsrc`, we clear locking information before we try to copy it below. Adding
+ // `move-object v1, v1` to the middle of `OK.runStraightLine()` in run-test 088 makes it fail.
SetRegisterType<LockOp::kClear>(vdst, type);
if (!type.IsConflict() && // Allow conflicts to be copied around.
((cat == kTypeCategory1nr && !type.IsCategory1Types()) ||
@@ -104,6 +116,10 @@ inline void RegisterLine::CopyRegister1(MethodVerifier* verifier, uint32_t vdst,
<< " cat=" << static_cast<int>(cat);
} else if (cat == kTypeCategoryRef) {
CopyRegToLockDepth(vdst, vsrc);
+ if (allocation_dex_pcs_ != nullptr) {
+ // Copy allocation dex pc for uninitialized types. (Copy unused value for other types.)
+ allocation_dex_pcs_[vdst] = allocation_dex_pcs_[vsrc];
+ }
}
}
@@ -119,6 +135,10 @@ inline void RegisterLine::CopyRegister2(MethodVerifier* verifier, uint32_t vdst,
}
}
+inline bool RegisterLine::NeedsAllocationDexPc(const RegType& reg_type) {
+ return reg_type.IsUninitializedReference() || reg_type.IsUnresolvedAndUninitializedReference();
+}
+
inline bool RegisterLine::VerifyRegisterType(MethodVerifier* verifier, uint32_t vsrc,
const RegType& check_type) {
// Verify the src register type against the check type refining the type of the register
@@ -155,6 +175,30 @@ inline bool RegisterLine::VerifyRegisterType(MethodVerifier* verifier, uint32_t
return true;
}
+inline void RegisterLine::DCheckUniqueNewInstanceDexPc(MethodVerifier* verifier, uint32_t dex_pc) {
+ if (kIsDebugBuild && allocation_dex_pcs_ != nullptr) {
+ // Note: We do not clear the `allocation_dex_pcs_` entries when copying data from
+ // a register line without `allocation_dex_pcs_`, or when we merge types and find
+ // a conflict, so the same dex pc can remain in the `allocation_dex_pcs_` array
+ // but it cannot be recorded for a `new-instance` uninitialized type.
+ RegTypeCache* reg_types = verifier->GetRegTypeCache();
+ for (uint32_t i = 0; i != num_regs_; ++i) {
+ if (NeedsAllocationDexPc(reg_types->GetFromId(line_[i]))) {
+ CHECK_NE(allocation_dex_pcs_[i], dex_pc) << i << " " << reg_types->GetFromId(line_[i]);
+ }
+ }
+ }
+}
+
+inline void RegisterLine::EnsureAllocationDexPcsAvailable() {
+ DCHECK_NE(num_regs_, 0u);
+ if (allocation_dex_pcs_ == nullptr) {
+ ScopedArenaAllocatorAdapter<uint32_t> allocator(monitors_.get_allocator());
+ allocation_dex_pcs_ = allocator.allocate(num_regs_);
+ std::fill_n(allocation_dex_pcs_, num_regs_, kNoDexPc);
+ }
+}
+
inline void RegisterLine::VerifyMonitorStackEmpty(MethodVerifier* verifier) const {
if (MonitorStackDepth() != 0) {
verifier->Fail(VERIFY_ERROR_LOCKING, /*pending_exc=*/ false);
@@ -180,6 +224,7 @@ inline RegisterLine::RegisterLine(size_t num_regs,
ScopedArenaAllocator& allocator,
RegTypeCache* reg_types)
: num_regs_(num_regs),
+ allocation_dex_pcs_(nullptr),
monitors_(allocator.Adapter(kArenaAllocVerifier)),
reg_to_lock_depths_(std::less<uint32_t>(),
allocator.Adapter(kArenaAllocVerifier)),
@@ -211,8 +256,18 @@ inline void RegisterLine::ClearRegToLockDepth(size_t reg, size_t depth) {
inline void RegisterLineArenaDelete::operator()(RegisterLine* ptr) const {
if (ptr != nullptr) {
+ uint32_t num_regs = ptr->NumRegs();
+ uint32_t* allocation_dex_pcs = ptr->allocation_dex_pcs_;
ptr->~RegisterLine();
- ProtectMemory(ptr, RegisterLine::ComputeSize(ptr->NumRegs()));
+ ProtectMemory(ptr, RegisterLine::ComputeSize(num_regs));
+ if (allocation_dex_pcs != nullptr) {
+ struct AllocationDexPcsDelete : ArenaDelete<uint32_t> {
+ void operator()(uint32_t* ptr, size_t size) {
+ ProtectMemory(ptr, size);
+ }
+ };
+ AllocationDexPcsDelete()(allocation_dex_pcs, num_regs * sizeof(*allocation_dex_pcs));
+ }
}
}
diff --git a/runtime/verifier/register_line.cc b/runtime/verifier/register_line.cc
index 62f2eb3ec0..e2ce9c082a 100644
--- a/runtime/verifier/register_line.cc
+++ b/runtime/verifier/register_line.cc
@@ -46,30 +46,6 @@ bool RegisterLine::CheckConstructorReturn(MethodVerifier* verifier) const {
return this_initialized_;
}
-const RegType& RegisterLine::GetInvocationThis(MethodVerifier* verifier, const Instruction* inst,
- bool allow_failure) {
- DCHECK(inst->IsInvoke());
- const size_t args_count = inst->VRegA();
- if (args_count < 1) {
- if (!allow_failure) {
- verifier->Fail(VERIFY_ERROR_BAD_CLASS_HARD) << "invoke lacks 'this'";
- }
- return verifier->GetRegTypeCache()->Conflict();
- }
- /* Get the element type of the array held in vsrc */
- const uint32_t this_reg = inst->VRegC();
- const RegType& this_type = GetRegisterType(verifier, this_reg);
- if (!this_type.IsReferenceTypes()) {
- if (!allow_failure) {
- verifier->Fail(VERIFY_ERROR_BAD_CLASS_HARD)
- << "tried to get class from non-reference register v" << this_reg
- << " (type=" << this_type << ")";
- }
- return verifier->GetRegTypeCache()->Conflict();
- }
- return this_type;
-}
-
bool RegisterLine::VerifyRegisterTypeWide(MethodVerifier* verifier, uint32_t vsrc,
const RegType& check_type1,
const RegType& check_type2) {
@@ -94,20 +70,47 @@ bool RegisterLine::VerifyRegisterTypeWide(MethodVerifier* verifier, uint32_t vsr
return true;
}
-void RegisterLine::MarkRefsAsInitialized(MethodVerifier* verifier, const RegType& uninit_type) {
+void RegisterLine::CopyFromLine(const RegisterLine* src) {
+ DCHECK_EQ(num_regs_, src->num_regs_);
+ memcpy(&line_, &src->line_, num_regs_ * sizeof(uint16_t));
+ // Copy `allocation_dex_pcs_`. Note that if the `src` does not have `allocation_dex_pcs_`
+ // allocated, we retain the array allocated for this register line to avoid wasting
+ // memory by allocating a new array later. This means that the `allocation_dex_pcs_` can
+ // be filled with bogus values not tied to a `new-instance` uninitialized type.
+ if (src->allocation_dex_pcs_ != nullptr) {
+ EnsureAllocationDexPcsAvailable();
+ memcpy(allocation_dex_pcs_, src->allocation_dex_pcs_, num_regs_ * sizeof(uint32_t));
+ }
+ monitors_ = src->monitors_;
+ reg_to_lock_depths_ = src->reg_to_lock_depths_;
+ this_initialized_ = src->this_initialized_;
+}
+
+void RegisterLine::MarkRefsAsInitialized(MethodVerifier* verifier, uint32_t vsrc) {
+ const RegType& uninit_type = GetRegisterType(verifier, vsrc);
DCHECK(uninit_type.IsUninitializedTypes());
const RegType& init_type = verifier->GetRegTypeCache()->FromUninitialized(uninit_type);
size_t changed = 0;
- for (uint32_t i = 0; i < num_regs_; i++) {
- if (GetRegisterType(verifier, i).Equals(uninit_type)) {
- line_[i] = init_type.GetId();
- changed++;
- }
- }
// Is this initializing "this"?
if (uninit_type.IsUninitializedThisReference() ||
uninit_type.IsUnresolvedAndUninitializedThisReference()) {
this_initialized_ = true;
+ for (uint32_t i = 0; i < num_regs_; i++) {
+ if (GetRegisterType(verifier, i).Equals(uninit_type)) {
+ line_[i] = init_type.GetId();
+ changed++;
+ }
+ }
+ } else {
+ DCHECK(NeedsAllocationDexPc(uninit_type));
+ DCHECK(allocation_dex_pcs_ != nullptr);
+ uint32_t dex_pc = allocation_dex_pcs_[vsrc];
+ for (uint32_t i = 0; i < num_regs_; i++) {
+ if (GetRegisterType(verifier, i).Equals(uninit_type) && allocation_dex_pcs_[i] == dex_pc) {
+ line_[i] = init_type.GetId();
+ changed++;
+ }
+ }
}
DCHECK_GT(changed, 0u);
}
@@ -155,15 +158,6 @@ std::string RegisterLine::Dump(MethodVerifier* verifier) const {
return result;
}
-void RegisterLine::MarkUninitRefsAsInvalid(MethodVerifier* verifier, const RegType& uninit_type) {
- for (size_t i = 0; i < num_regs_; i++) {
- if (GetRegisterType(verifier, i).Equals(uninit_type)) {
- line_[i] = verifier->GetRegTypeCache()->Conflict().GetId();
- ClearAllRegToLockDepths(i);
- }
- }
-}
-
void RegisterLine::CopyResultRegister1(MethodVerifier* verifier, uint32_t vdst, bool is_reference) {
const RegType& type = verifier->GetRegTypeCache()->GetFromId(result_[0]);
if ((!is_reference && !type.IsCategory1Types()) ||
@@ -432,6 +426,20 @@ bool RegisterLine::MergeRegisters(MethodVerifier* verifier, const RegisterLine*
incoming_reg_type, verifier->GetRegTypeCache(), verifier);
changed = changed || !cur_type.Equals(new_type);
line_[idx] = new_type.GetId();
+ } else {
+ auto needs_allocation_dex_pc = [&]() {
+ return NeedsAllocationDexPc(verifier->GetRegTypeCache()->GetFromId(line_[idx]));
+ };
+ DCHECK_IMPLIES(needs_allocation_dex_pc(), allocation_dex_pcs_ != nullptr);
+ DCHECK_IMPLIES(needs_allocation_dex_pc(), incoming_line->allocation_dex_pcs_ != nullptr);
+ // Check for allocation dex pc mismatch first to try and avoid costly virtual calls.
+ // For methods without any `new-instance` instructions, the `allocation_dex_pcs_` is null.
+ if (allocation_dex_pcs_ != nullptr &&
+ incoming_line->allocation_dex_pcs_ != nullptr &&
+ allocation_dex_pcs_[idx] != incoming_line->allocation_dex_pcs_[idx] &&
+ needs_allocation_dex_pc()) {
+ line_[idx] = verifier->GetRegTypeCache()->Conflict().GetId();
+ }
}
}
if (monitors_.size() > 0 || incoming_line->monitors_.size() > 0) {
diff --git a/runtime/verifier/register_line.h b/runtime/verifier/register_line.h
index fc8c4cbc6c..f6424e9878 100644
--- a/runtime/verifier/register_line.h
+++ b/runtime/verifier/register_line.h
@@ -128,6 +128,15 @@ class RegisterLine {
void SetResultRegisterTypeWide(const RegType& new_type1, const RegType& new_type2)
REQUIRES_SHARED(Locks::mutator_lock_);
+ /*
+ * Set register type for a `new-instance` instruction.
+ * For `new-instance`, we additionally record the allocation dex pc for vreg `vdst`.
+ * This is used to keep track of registers that hold the same uninitialized reference,
+ * so that we can update them all when a constructor is called on any of them.
+ */
+ void SetRegisterTypeForNewInstance(uint32_t vdst, const RegType& uninit_type, uint32_t dex_pc)
+ REQUIRES_SHARED(Locks::mutator_lock_);
+
// Get the type of register vsrc.
const RegType& GetRegisterType(MethodVerifier* verifier, uint32_t vsrc) const;
@@ -142,13 +151,7 @@ class RegisterLine {
const RegType& check_type2)
REQUIRES_SHARED(Locks::mutator_lock_);
- void CopyFromLine(const RegisterLine* src) {
- DCHECK_EQ(num_regs_, src->num_regs_);
- memcpy(&line_, &src->line_, num_regs_ * sizeof(uint16_t));
- monitors_ = src->monitors_;
- reg_to_lock_depths_ = src->reg_to_lock_depths_;
- this_initialized_ = src->this_initialized_;
- }
+ void CopyFromLine(const RegisterLine* src);
std::string Dump(MethodVerifier* verifier) const REQUIRES_SHARED(Locks::mutator_lock_);
@@ -159,20 +162,19 @@ class RegisterLine {
}
/*
- * We're creating a new instance of class C at address A. Any registers holding instances
- * previously created at address A must be initialized by now. If not, we mark them as "conflict"
- * to prevent them from being used (otherwise, MarkRefsAsInitialized would mark the old ones and
- * the new ones at the same time).
+ * In debug mode, assert that the register line does not contain an uninitialized register
+ * type for a `new-instance` allocation at a specific dex pc. We do this check before recording
+ * the uninitialized register type and dex pc for a `new-instance` instruction.
*/
- void MarkUninitRefsAsInvalid(MethodVerifier* verifier, const RegType& uninit_type)
+ void DCheckUniqueNewInstanceDexPc(MethodVerifier* verifier, uint32_t dex_pc)
REQUIRES_SHARED(Locks::mutator_lock_);
/*
- * Update all registers holding "uninit_type" to instead hold the corresponding initialized
- * reference type. This is called when an appropriate constructor is invoked -- all copies of
- * the reference must be marked as initialized.
+ * Update all registers holding the uninitialized type currently recorded for vreg `vsrc` to
+ * instead hold the corresponding initialized reference type. This is called when an appropriate
+ * constructor is invoked -- all copies of the reference must be marked as initialized.
*/
- void MarkRefsAsInitialized(MethodVerifier* verifier, const RegType& uninit_type)
+ void MarkRefsAsInitialized(MethodVerifier* verifier, uint32_t vsrc)
REQUIRES_SHARED(Locks::mutator_lock_);
/*
@@ -217,21 +219,6 @@ class RegisterLine {
ALWAYS_INLINE static size_t ComputeSize(size_t num_regs);
/*
- * Get the "this" pointer from a non-static method invocation. This returns the RegType so the
- * caller can decide whether it needs the reference to be initialized or not. (Can also return
- * kRegTypeZero if the reference can only be zero at this point.)
- *
- * The argument count is in vA, and the first argument is in vC, for both "simple" and "range"
- * versions. We just need to make sure vA is >= 1 and then return vC.
- * allow_failure will return Conflict() instead of causing a verification failure if there is an
- * error.
- */
- const RegType& GetInvocationThis(MethodVerifier* verifier,
- const Instruction* inst,
- bool allow_failure = false)
- REQUIRES_SHARED(Locks::mutator_lock_);
-
- /*
* Verify types for a simple two-register instruction (e.g. "neg-int").
* "dst_type" is stored into vA, and "src_type" is verified against vB.
*/
@@ -382,6 +369,12 @@ class RegisterLine {
}
private:
+ // For uninitialized types we need to check for allocation dex pc mismatch when merging.
+ // This does not apply to uninitialized "this" reference types.
+ static bool NeedsAllocationDexPc(const RegType& reg_type);
+
+ void EnsureAllocationDexPcsAvailable();
+
void CopyRegToLockDepth(size_t dst, size_t src) {
auto it = reg_to_lock_depths_.find(src);
if (it != reg_to_lock_depths_.end()) {
@@ -420,12 +413,17 @@ class RegisterLine {
RegisterLine(size_t num_regs, ScopedArenaAllocator& allocator, RegTypeCache* reg_types);
- // Storage for the result register's type, valid after an invocation.
- uint16_t result_[2];
+ static constexpr uint32_t kNoDexPc = static_cast<uint32_t>(-1);
// Length of reg_types_
const uint32_t num_regs_;
+ // Storage for the result register's type, valid after an invocation.
+ uint16_t result_[2];
+
+ // Track allocation dex pcs for `new-instance` results moved to other registers.
+ uint32_t* allocation_dex_pcs_;
+
// A stack of monitor enter locations.
ScopedArenaVector<uint32_t> monitors_;
@@ -440,6 +438,8 @@ class RegisterLine {
// An array of RegType Ids associated with each dex register.
uint16_t line_[1];
+ friend class RegisterLineArenaDelete;
+
DISALLOW_COPY_AND_ASSIGN(RegisterLine);
};
@@ -448,6 +448,8 @@ class RegisterLineArenaDelete : public ArenaDelete<RegisterLine> {
void operator()(RegisterLine* ptr) const;
};
+using RegisterLineArenaUniquePtr = std::unique_ptr<RegisterLine, RegisterLineArenaDelete>;
+
} // namespace verifier
} // namespace art
diff --git a/runtime/verifier/register_line_test.cc b/runtime/verifier/register_line_test.cc
new file mode 100644
index 0000000000..d7460bf83f
--- /dev/null
+++ b/runtime/verifier/register_line_test.cc
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "register_line-inl.h"
+
+#include "common_runtime_test.h"
+#include "method_verifier.h"
+#include "reg_type_cache-inl.h"
+
+namespace art HIDDEN {
+namespace verifier {
+
+class RegisterLineTest : public CommonRuntimeTest {
+ protected:
+ RegisterLineTest() {
+ use_boot_image_ = true; // Make the Runtime creation cheaper.
+ }
+
+ MethodVerifier* CreateVerifier(Thread* self,
+ RegTypeCache* reg_types,
+ Handle<mirror::DexCache> dex_cache,
+ ArtMethod* method) REQUIRES_SHARED(Locks::mutator_lock_) {
+ return MethodVerifier::CreateVerifier(
+ self,
+ reg_types,
+ /*verifier_deps=*/ nullptr,
+ dex_cache,
+ *method->GetDeclaringClass()->GetClassDef(),
+ method->GetCodeItem(),
+ method->GetDexMethodIndex(),
+ method->GetAccessFlags(),
+ /*verify_to_dump=*/ false,
+ /*api_level=*/ 0u);
+ }
+
+ ScopedArenaAllocator& GetScopedArenaAllocator(MethodVerifier* verifier) {
+ return verifier->allocator_;
+ }
+};
+
+// Helper class to avoid thread safety analysis errors from gtest's `operator<<`
+// instantiation for `RegType` not being marked as holding the mutator lock.
+class RegTypeWrapper {
+ public:
+ explicit RegTypeWrapper(const RegType& reg_type) : reg_type_(reg_type) {}
+ private:
+ const RegType& reg_type_;
+ friend std::ostream& operator<<(std::ostream& os, const RegTypeWrapper& rtw);
+};
+
+std::ostream& operator<<(std::ostream& os, const RegTypeWrapper& rtw) NO_THREAD_SAFETY_ANALYSIS {
+ return os << rtw.reg_type_;
+}
+
+TEST_F(RegisterLineTest, NewInstanceDexPcsMerging) {
+ ArenaPool* arena_pool = Runtime::Current()->GetArenaPool();
+ ScopedObjectAccess soa(Thread::Current());
+ StackHandleScope<2u> hs(soa.Self());
+ Handle<mirror::Class> object_class = hs.NewHandle(GetClassRoot<mirror::Object>());
+ Handle<mirror::DexCache> dex_cache = hs.NewHandle(object_class->GetDexCache());
+ const DexFile* dex_file = dex_cache->GetDexFile();
+ ScopedNullHandle<mirror::ClassLoader> loader;
+ RegTypeCache reg_types(soa.Self(), class_linker_, arena_pool, loader, dex_file);
+ ArtMethod* method = object_class->FindClassMethod("wait", "()V", kRuntimePointerSize);
+ ASSERT_TRUE(method != nullptr);
+ std::unique_ptr<MethodVerifier> verifier(
+ CreateVerifier(soa.Self(), &reg_types, dex_cache, method));
+ const RegType& resolved_type1 = reg_types.FromDescriptor("Ljava/lang/Object;");
+ const RegType& resolved_type2 = reg_types.FromDescriptor("Ljava/lang/String;");
+ const RegType& unresolved_type1 = reg_types.FromDescriptor("Ljava/lang/DoesNotExist;");
+ const RegType& unresolved_type2 = reg_types.FromDescriptor("Ljava/lang/DoesNotExistEither;");
+ const RegType& uninit_resolved_type1 = reg_types.Uninitialized(resolved_type1);
+ const RegType& uninit_resolved_type2 = reg_types.Uninitialized(resolved_type2);
+ const RegType& uninit_unresolved_type1 = reg_types.Uninitialized(unresolved_type1);
+ const RegType& uninit_unresolved_type2 = reg_types.Uninitialized(unresolved_type2);
+ const RegType& conflict = reg_types.Conflict();
+
+ struct TestCase {
+ const RegType& reg_type1;
+ uint32_t dex_pc1;
+ const RegType& reg_type2;
+ uint32_t dex_pc2;
+ const RegType& expected;
+ };
+ const TestCase test_cases[] = {
+ // Merge the same uninitialized type and allocation dex pc.
+ {uninit_resolved_type1, 1u, uninit_resolved_type1, 1u, uninit_resolved_type1},
+ {uninit_resolved_type2, 1u, uninit_resolved_type2, 1u, uninit_resolved_type2},
+ {uninit_unresolved_type1, 1u, uninit_unresolved_type1, 1u, uninit_unresolved_type1},
+ {uninit_unresolved_type2, 1u, uninit_unresolved_type2, 1u, uninit_unresolved_type2},
+ // Merge the same uninitialized type and different allocation dex pcs.
+ {uninit_resolved_type1, 1u, uninit_resolved_type1, 2u, conflict},
+ {uninit_resolved_type2, 1u, uninit_resolved_type2, 2u, conflict},
+ {uninit_unresolved_type1, 1u, uninit_unresolved_type1, 2u, conflict},
+ {uninit_unresolved_type2, 1u, uninit_unresolved_type2, 2u, conflict},
+ // Merge different uninitialized types and the same allocation dex pc.
+ {uninit_resolved_type1, 1u, uninit_resolved_type2, 1u, conflict},
+ {uninit_resolved_type1, 1u, uninit_unresolved_type1, 1u, conflict},
+ {uninit_resolved_type1, 1u, uninit_unresolved_type2, 1u, conflict},
+ {uninit_resolved_type2, 1u, uninit_resolved_type1, 1u, conflict},
+ {uninit_resolved_type2, 1u, uninit_unresolved_type1, 1u, conflict},
+ {uninit_resolved_type2, 1u, uninit_unresolved_type2, 1u, conflict},
+ {uninit_unresolved_type1, 1u, uninit_resolved_type1, 1u, conflict},
+ {uninit_unresolved_type1, 1u, uninit_resolved_type2, 1u, conflict},
+ {uninit_unresolved_type1, 1u, uninit_unresolved_type2, 1u, conflict},
+ {uninit_unresolved_type2, 1u, uninit_resolved_type1, 1u, conflict},
+ {uninit_unresolved_type2, 1u, uninit_resolved_type2, 1u, conflict},
+ {uninit_unresolved_type2, 1u, uninit_unresolved_type1, 1u, conflict},
+ // Merge uninitialized types with their initialized counterparts.
+ {uninit_resolved_type1, 1u, resolved_type1, 1u, conflict},
+ {uninit_resolved_type2, 1u, resolved_type2, 1u, conflict},
+ {uninit_unresolved_type1, 1u, unresolved_type1, 1u, conflict},
+ {uninit_unresolved_type2, 1u, unresolved_type2, 1u, conflict},
+ {resolved_type1, 1u, uninit_resolved_type1, 1u, conflict},
+ {resolved_type2, 1u, uninit_resolved_type2, 1u, conflict},
+ {unresolved_type1, 1u, uninit_unresolved_type1, 1u, conflict},
+ {unresolved_type2, 1u, uninit_unresolved_type2, 1u, conflict},
+ };
+
+ constexpr size_t kNumRegs = 1u;
+ constexpr uint32_t kVReg = 0u;
+ ScopedArenaAllocator& allocator = GetScopedArenaAllocator(verifier.get());
+ RegisterLineArenaUniquePtr line1(RegisterLine::Create(kNumRegs, allocator, &reg_types));
+ RegisterLineArenaUniquePtr line2(RegisterLine::Create(kNumRegs, allocator, &reg_types));
+ for (const TestCase& test_case : test_cases) {
+ ASSERT_TRUE(test_case.reg_type1.IsUninitializedTypes() ||
+ test_case.reg_type2.IsUninitializedTypes());
+ auto set_reg_type_and_dex_pc = [&](RegisterLine* line,
+ const RegType& reg_type,
+ uint32_t dex_pc,
+ const RegType& other_reg_type)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ if (reg_type.IsUninitializedTypes()) {
+ line->SetRegisterTypeForNewInstance(kVReg, reg_type, dex_pc);
+ } else {
+ // Initialize the allocation dex pc using the `other_reg_type`, then set the `reg_type`.
+ line->SetRegisterTypeForNewInstance(kVReg, other_reg_type, dex_pc);
+ line->SetRegisterType<LockOp::kClear>(kVReg, reg_type);
+ }
+ };
+ set_reg_type_and_dex_pc(
+ line1.get(), test_case.reg_type1, test_case.dex_pc1, test_case.reg_type2);
+ set_reg_type_and_dex_pc(
+ line2.get(), test_case.reg_type2, test_case.dex_pc2, test_case.reg_type1);
+ line1->MergeRegisters(verifier.get(), line2.get());
+ const RegType& result = line1->GetRegisterType(verifier.get(), kVReg);
+ ASSERT_TRUE(result.Equals(test_case.expected))
+ << RegTypeWrapper(test_case.reg_type1) << " @" << test_case.dex_pc1 << " merge with "
+ << RegTypeWrapper(test_case.reg_type2) << " @" << test_case.dex_pc2 << " yielded "
+ << RegTypeWrapper(result) << " but we expected " << RegTypeWrapper(test_case.expected);
+ }
+}
+
+} // namespace verifier
+} // namespace art