Improve to-space invariant error reporting in the CC collector.
In particular, distinguish references in an unused region from
the region space (which should never be encountered) from a
reference in a non-moving space.
Test: art/test/testrunner/testrunner.py
Bug: 72758079
Bug: 73004523
Change-Id: Iac632ae71dce9a36b33c14586eeb709dc2d7e7ce
diff --git a/runtime/gc/collector/concurrent_copying.cc b/runtime/gc/collector/concurrent_copying.cc
index 1e0c0b1..f6e74da 100644
--- a/runtime/gc/collector/concurrent_copying.cc
+++ b/runtime/gc/collector/concurrent_copying.cc
@@ -440,7 +440,7 @@
if (kUseBakerReadBarrier && kGrayDirtyImmuneObjects) {
cc->GrayAllNewlyDirtyImmuneObjects();
if (kIsDebugBuild) {
- // Check that all non-gray immune objects only refernce immune objects.
+ // Check that all non-gray immune objects only reference immune objects.
cc->VerifyGrayImmuneObjects();
}
}
@@ -1806,27 +1806,82 @@
}
}
-// Assert the to-space invariant.
+std::string ConcurrentCopying::DumpReferenceInfo(mirror::Object* ref,
+ const char* ref_name,
+ std::string indent) {
+ std::ostringstream oss;
+ oss << indent << heap_->GetVerification()->DumpObjectInfo(ref, ref_name) << '\n';
+ if (ref != nullptr) {
+ if (kUseBakerReadBarrier) {
+ oss << indent << ref_name << "->GetMarkBit()=" << ref->GetMarkBit() << '\n';
+ oss << indent << ref_name << "->GetReadBarrierState()=" << ref->GetReadBarrierState() << '\n';
+ }
+ }
+ if (region_space_->HasAddress(ref)) {
+ oss << indent << "Region containing " << ref_name << ":" << '\n';
+ region_space_->DumpRegionForObject(oss, ref);
+ if (region_space_bitmap_ != nullptr) {
+ oss << indent << "region_space_bitmap_->Test(" << ref_name << ")="
+ << std::boolalpha << region_space_bitmap_->Test(ref) << std::noboolalpha;
+ }
+ }
+ return oss.str();
+}
+
+std::string ConcurrentCopying::DumpHeapReference(mirror::Object* obj,
+ MemberOffset offset,
+ mirror::Object* ref) {
+ std::ostringstream oss;
+ std::string indent = " ";
+ oss << indent << "Invalid reference: ref=" << ref
+ << " referenced from: object=" << obj << " offset= " << offset << '\n';
+ // Information about `obj`.
+ oss << DumpReferenceInfo(obj, "obj", indent) << '\n';
+ // Information about `ref`.
+ oss << DumpReferenceInfo(ref, "ref", indent);
+ return oss.str();
+}
+
void ConcurrentCopying::AssertToSpaceInvariant(mirror::Object* obj,
MemberOffset offset,
mirror::Object* ref) {
- CHECK_EQ(heap_->collector_type_, kCollectorTypeCC);
+ CHECK_EQ(heap_->collector_type_, kCollectorTypeCC) << static_cast<size_t>(heap_->collector_type_);
if (is_asserting_to_space_invariant_) {
- using RegionType = space::RegionSpace::RegionType;
- space::RegionSpace::RegionType type = region_space_->GetRegionType(ref);
- if (type == RegionType::kRegionTypeToSpace) {
- // OK.
- return;
- } else if (type == RegionType::kRegionTypeUnevacFromSpace) {
- CHECK(IsMarkedInUnevacFromSpace(ref)) << ref;
- } else if (UNLIKELY(type == RegionType::kRegionTypeFromSpace)) {
- // Not OK. Do extra logging.
- if (obj != nullptr) {
- LogFromSpaceRefHolder(obj, offset);
+ if (region_space_->HasAddress(ref)) {
+ // Check to-space invariant in region space (moving space).
+ using RegionType = space::RegionSpace::RegionType;
+ space::RegionSpace::RegionType type = region_space_->GetRegionType(ref);
+ if (type == RegionType::kRegionTypeToSpace) {
+ // OK.
+ return;
+ } else if (type == RegionType::kRegionTypeUnevacFromSpace) {
+ if (!IsMarkedInUnevacFromSpace(ref)) {
+ LOG(FATAL_WITHOUT_ABORT) << "Found unmarked reference in unevac from-space:";
+ LOG(FATAL_WITHOUT_ABORT) << DumpHeapReference(obj, offset, ref);
+ }
+ CHECK(IsMarkedInUnevacFromSpace(ref)) << ref;
+ } else {
+ // Not OK: either a from-space ref or a reference in an unused region.
+ // Do extra logging.
+ if (type == RegionType::kRegionTypeFromSpace) {
+ LOG(FATAL_WITHOUT_ABORT) << "Found from-space reference:";
+ } else {
+ LOG(FATAL_WITHOUT_ABORT) << "Found reference in region with type " << type << ":";
+ }
+ LOG(FATAL_WITHOUT_ABORT) << DumpHeapReference(obj, offset, ref);
+ if (obj != nullptr) {
+ LogFromSpaceRefHolder(obj, offset);
+ }
+ ref->GetLockWord(false).Dump(LOG_STREAM(FATAL_WITHOUT_ABORT));
+ LOG(FATAL_WITHOUT_ABORT) << "Non-free regions:";
+ region_space_->DumpNonFreeRegions(LOG_STREAM(FATAL_WITHOUT_ABORT));
+ PrintFileToLog("/proc/self/maps", LogSeverity::FATAL_WITHOUT_ABORT);
+ MemMap::DumpMaps(LOG_STREAM(FATAL_WITHOUT_ABORT), true);
+ LOG(FATAL) << "Invalid reference " << ref
+ << " referenced from object " << obj << " at offset " << offset;
}
- ref->GetLockWord(false).Dump(LOG_STREAM(FATAL_WITHOUT_ABORT));
- CHECK(false) << "Found from-space ref " << ref << " " << ref->PrettyTypeOf();
} else {
+ // Check to-space invariant in non-moving space.
AssertToSpaceInvariantInNonMovingSpace(obj, ref);
}
}
@@ -1857,39 +1912,66 @@
}
};
+std::string ConcurrentCopying::DumpGcRoot(mirror::Object* ref) {
+ std::ostringstream oss;
+ std::string indent = " ";
+ oss << indent << "Invalid GC root: ref=" << ref << '\n';
+ // Information about `ref`.
+ oss << DumpReferenceInfo(ref, "ref", indent);
+ return oss.str();
+}
+
void ConcurrentCopying::AssertToSpaceInvariant(GcRootSource* gc_root_source,
mirror::Object* ref) {
- CHECK(heap_->collector_type_ == kCollectorTypeCC) << static_cast<size_t>(heap_->collector_type_);
+ CHECK_EQ(heap_->collector_type_, kCollectorTypeCC) << static_cast<size_t>(heap_->collector_type_);
if (is_asserting_to_space_invariant_) {
- if (region_space_->IsInToSpace(ref)) {
- // OK.
- return;
- } else if (region_space_->IsInUnevacFromSpace(ref)) {
- CHECK(IsMarkedInUnevacFromSpace(ref)) << ref;
- } else if (region_space_->IsInFromSpace(ref)) {
- // Not OK. Do extra logging.
- if (gc_root_source == nullptr) {
- // No info.
- } else if (gc_root_source->HasArtField()) {
- ArtField* field = gc_root_source->GetArtField();
- LOG(FATAL_WITHOUT_ABORT) << "gc root in field " << field << " "
- << ArtField::PrettyField(field);
- RootPrinter root_printer;
- field->VisitRoots(root_printer);
- } else if (gc_root_source->HasArtMethod()) {
- ArtMethod* method = gc_root_source->GetArtMethod();
- LOG(FATAL_WITHOUT_ABORT) << "gc root in method " << method << " "
- << ArtMethod::PrettyMethod(method);
- RootPrinter root_printer;
- method->VisitRoots(root_printer, kRuntimePointerSize);
+ if (region_space_->HasAddress(ref)) {
+ // Check to-space invariant in region space (moving space).
+ using RegionType = space::RegionSpace::RegionType;
+ space::RegionSpace::RegionType type = region_space_->GetRegionType(ref);
+ if (type == RegionType::kRegionTypeToSpace) {
+ // OK.
+ return;
+ } else if (type == RegionType::kRegionTypeUnevacFromSpace) {
+ if (!IsMarkedInUnevacFromSpace(ref)) {
+ LOG(FATAL_WITHOUT_ABORT) << "Found unmarked reference in unevac from-space:";
+ LOG(FATAL_WITHOUT_ABORT) << DumpGcRoot(ref);
+ }
+ CHECK(IsMarkedInUnevacFromSpace(ref)) << ref;
+ } else {
+ // Not OK: either a from-space ref or a reference in an unused region.
+ // Do extra logging.
+ if (type == RegionType::kRegionTypeFromSpace) {
+ LOG(FATAL_WITHOUT_ABORT) << "Found from-space reference:";
+ } else {
+ LOG(FATAL_WITHOUT_ABORT) << "Found reference in region with type " << type << ":";
+ }
+ LOG(FATAL_WITHOUT_ABORT) << DumpGcRoot(ref);
+ if (gc_root_source == nullptr) {
+ // No info.
+ } else if (gc_root_source->HasArtField()) {
+ ArtField* field = gc_root_source->GetArtField();
+ LOG(FATAL_WITHOUT_ABORT) << "gc root in field " << field << " "
+ << ArtField::PrettyField(field);
+ RootPrinter root_printer;
+ field->VisitRoots(root_printer);
+ } else if (gc_root_source->HasArtMethod()) {
+ ArtMethod* method = gc_root_source->GetArtMethod();
+ LOG(FATAL_WITHOUT_ABORT) << "gc root in method " << method << " "
+ << ArtMethod::PrettyMethod(method);
+ RootPrinter root_printer;
+ method->VisitRoots(root_printer, kRuntimePointerSize);
+ }
+ ref->GetLockWord(false).Dump(LOG_STREAM(FATAL_WITHOUT_ABORT));
+ LOG(FATAL_WITHOUT_ABORT) << "Non-free regions:";
+ region_space_->DumpNonFreeRegions(LOG_STREAM(FATAL_WITHOUT_ABORT));
+ PrintFileToLog("/proc/self/maps", LogSeverity::FATAL_WITHOUT_ABORT);
+ MemMap::DumpMaps(LOG_STREAM(FATAL_WITHOUT_ABORT), true);
+ LOG(FATAL) << "Invalid reference " << ref;
}
- ref->GetLockWord(false).Dump(LOG_STREAM(FATAL_WITHOUT_ABORT));
- region_space_->DumpNonFreeRegions(LOG_STREAM(FATAL_WITHOUT_ABORT));
- PrintFileToLog("/proc/self/maps", LogSeverity::FATAL_WITHOUT_ABORT);
- MemMap::DumpMaps(LOG_STREAM(FATAL_WITHOUT_ABORT), true);
- CHECK(false) << "Found from-space ref " << ref << " " << ref->PrettyTypeOf();
} else {
- AssertToSpaceInvariantInNonMovingSpace(nullptr, ref);
+ // Check to-space invariant in non-moving space.
+ AssertToSpaceInvariantInNonMovingSpace(/* obj */ nullptr, ref);
}
}
}
@@ -1944,6 +2026,7 @@
void ConcurrentCopying::AssertToSpaceInvariantInNonMovingSpace(mirror::Object* obj,
mirror::Object* ref) {
+ CHECK(!region_space_->HasAddress(ref)) << "obj=" << obj << " ref=" << ref;
// In a non-moving spaces. Check that the ref is marked.
if (immune_spaces_.ContainsObject(ref)) {
if (kUseBakerReadBarrier) {
@@ -2428,6 +2511,9 @@
to_ref = nullptr;
}
} else {
+ // At this point, `from_ref` should not be in the region space
+ // (i.e. within an "unused" region).
+ DCHECK(!region_space_->HasAddress(from_ref)) << from_ref;
// from_ref is in a non-moving space.
if (immune_spaces_.ContainsObject(from_ref)) {
// An immune object is alive.
@@ -2598,7 +2684,12 @@
DCHECK(rb_mark_bit_stack_ != nullptr);
const auto* limit = rb_mark_bit_stack_->End();
for (StackReference<mirror::Object>* it = rb_mark_bit_stack_->Begin(); it != limit; ++it) {
- CHECK(it->AsMirrorPtr()->AtomicSetMarkBit(1, 0));
+ CHECK(it->AsMirrorPtr()->AtomicSetMarkBit(1, 0))
+ << "rb_mark_bit_stack_->Begin()" << rb_mark_bit_stack_->Begin() << '\n'
+ << "rb_mark_bit_stack_->End()" << rb_mark_bit_stack_->End() << '\n'
+ << "rb_mark_bit_stack_->IsFull()"
+ << std::boolalpha << rb_mark_bit_stack_->IsFull() << std::noboolalpha << '\n'
+ << DumpReferenceInfo(it->AsMirrorPtr(), "*it");
}
rb_mark_bit_stack_->Reset();
}
diff --git a/runtime/gc/collector/concurrent_copying.h b/runtime/gc/collector/concurrent_copying.h
index 8b4b58e..42d37d8 100644
--- a/runtime/gc/collector/concurrent_copying.h
+++ b/runtime/gc/collector/concurrent_copying.h
@@ -100,8 +100,10 @@
space::RegionSpace* RegionSpace() {
return region_space_;
}
+ // Assert the to-space invariant for a heap reference `ref` held in `obj` at offset `offset`.
void AssertToSpaceInvariant(mirror::Object* obj, MemberOffset offset, mirror::Object* ref)
REQUIRES_SHARED(Locks::mutator_lock_);
+ // Assert the to-space invariant for a GC root reference `ref`.
void AssertToSpaceInvariant(GcRootSource* gc_root_source, mirror::Object* ref)
REQUIRES_SHARED(Locks::mutator_lock_);
bool IsInToSpace(mirror::Object* ref) REQUIRES_SHARED(Locks::mutator_lock_) {
@@ -232,6 +234,16 @@
void ComputeUnevacFromSpaceLiveRatio();
void LogFromSpaceRefHolder(mirror::Object* obj, MemberOffset offset)
REQUIRES_SHARED(Locks::mutator_lock_);
+ // Dump information about reference `ref` and return it as a string.
+ // Use `ref_name` to name the reference in messages. Each message is prefixed with `indent`.
+ std::string DumpReferenceInfo(mirror::Object* ref, const char* ref_name, std::string indent = "")
+ REQUIRES_SHARED(Locks::mutator_lock_);
+ // Dump information about heap reference `ref`, referenced from object `obj` at offset `offset`,
+ // and return it as a string.
+ std::string DumpHeapReference(mirror::Object* obj, MemberOffset offset, mirror::Object* ref)
+ REQUIRES_SHARED(Locks::mutator_lock_);
+ // Dump information about GC root `ref` and return it as a string.
+ std::string DumpGcRoot(mirror::Object* ref) REQUIRES_SHARED(Locks::mutator_lock_);
void AssertToSpaceInvariantInNonMovingSpace(mirror::Object* obj, mirror::Object* ref)
REQUIRES_SHARED(Locks::mutator_lock_);
void ReenableWeakRefAccess(Thread* self) REQUIRES_SHARED(Locks::mutator_lock_);
diff --git a/runtime/gc/space/region_space.cc b/runtime/gc/space/region_space.cc
index 45cfff9..5d64bf6 100644
--- a/runtime/gc/space/region_space.cc
+++ b/runtime/gc/space/region_space.cc
@@ -418,6 +418,12 @@
<< reinterpret_cast<void*>(Begin()) << "-" << reinterpret_cast<void*>(Limit());
}
+void RegionSpace::DumpRegionForObject(std::ostream& os, mirror::Object* obj) {
+ CHECK(HasAddress(obj));
+ MutexLock mu(Thread::Current(), region_lock_);
+ RefToRegionUnlocked(obj)->Dump(os);
+}
+
void RegionSpace::DumpRegions(std::ostream& os) {
MutexLock mu(Thread::Current(), region_lock_);
for (size_t i = 0; i < num_regions_; ++i) {
diff --git a/runtime/gc/space/region_space.h b/runtime/gc/space/region_space.h
index ef8aa52..d383d09 100644
--- a/runtime/gc/space/region_space.h
+++ b/runtime/gc/space/region_space.h
@@ -94,6 +94,8 @@
void Dump(std::ostream& os) const;
void DumpRegions(std::ostream& os) REQUIRES(!region_lock_);
+ // Dump region containing object `obj`. Precondition: `obj` is in the region space.
+ void DumpRegionForObject(std::ostream& os, mirror::Object* obj) REQUIRES(!region_lock_);
void DumpNonFreeRegions(std::ostream& os) REQUIRES(!region_lock_);
size_t RevokeThreadLocalBuffers(Thread* thread) REQUIRES(!region_lock_);
diff --git a/runtime/gc/verification.cc b/runtime/gc/verification.cc
index d99b377..fb5db11 100644
--- a/runtime/gc/verification.cc
+++ b/runtime/gc/verification.cc
@@ -140,7 +140,7 @@
if (!IsValidHeapObjectAddress(k1)) {
return false;
}
- // k should be class class, take the class again to verify.
+ // `k1` should be class class, take the class again to verify.
// Note that this check may not be valid for the no image space since the class class might move
// around from moving GC.
mirror::Class* k2 = k1->GetClass<kVerifyNone, kWithoutReadBarrier>();
diff --git a/runtime/mirror/object-inl.h b/runtime/mirror/object-inl.h
index 6e2a07c..9685488 100644
--- a/runtime/mirror/object-inl.h
+++ b/runtime/mirror/object-inl.h
@@ -160,7 +160,8 @@
template<VerifyObjectFlags kVerifyFlags>
inline bool Object::InstanceOf(ObjPtr<Class> klass) {
DCHECK(klass != nullptr);
- DCHECK(GetClass<kVerifyNone>() != nullptr);
+ DCHECK(GetClass<kVerifyNone>() != nullptr)
+ << "this=" << std::hex << reinterpret_cast<uintptr_t>(this) << std::dec;
return klass->IsAssignableFrom(GetClass<kVerifyFlags>());
}