diff options
-rw-r--r-- | runtime/Android.bp | 1 | ||||
-rw-r--r-- | runtime/verifier/method_verifier.cc | 39 | ||||
-rw-r--r-- | runtime/verifier/method_verifier.h | 13 | ||||
-rw-r--r-- | runtime/verifier/reg_type.cc | 7 | ||||
-rw-r--r-- | runtime/verifier/reg_type.h | 21 | ||||
-rw-r--r-- | runtime/verifier/reg_type_cache.cc | 8 | ||||
-rw-r--r-- | runtime/verifier/reg_type_cache.h | 2 | ||||
-rw-r--r-- | runtime/verifier/reg_type_test.cc | 53 | ||||
-rw-r--r-- | runtime/verifier/register_line-inl.h | 57 | ||||
-rw-r--r-- | runtime/verifier/register_line.cc | 88 | ||||
-rw-r--r-- | runtime/verifier/register_line.h | 68 | ||||
-rw-r--r-- | runtime/verifier/register_line_test.cc | 168 |
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(), ®_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, ®_types)); + RegisterLineArenaUniquePtr line2(RegisterLine::Create(kNumRegs, allocator, ®_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 |