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>());
 }