Handle record flag in object-refvisitor.

Test: 849-records
Bug: 297966050
Change-Id: I49063df4b2b61b8f01bc1b6fff2d16906d0ac39f
diff --git a/perfetto_hprof/perfetto_hprof.cc b/perfetto_hprof/perfetto_hprof.cc
index a6ad4a4..ca89abe 100644
--- a/perfetto_hprof/perfetto_hprof.cc
+++ b/perfetto_hprof/perfetto_hprof.cc
@@ -507,8 +507,10 @@
   using perfetto::protos::pbzero::HeapGraphType;
   switch (class_flags) {
     case art::mirror::kClassFlagNormal:
+    case art::mirror::kClassFlagRecord:
       return HeapGraphType::KIND_NORMAL;
     case art::mirror::kClassFlagNoReferenceFields:
+    case art::mirror::kClassFlagNoReferenceFields | art::mirror::kClassFlagRecord:
       return HeapGraphType::KIND_NOREFERENCES;
     case art::mirror::kClassFlagString | art::mirror::kClassFlagNoReferenceFields:
       return HeapGraphType::KIND_STRING;
diff --git a/runtime/gc/collector/mark_sweep-inl.h b/runtime/gc/collector/mark_sweep-inl.h
index e4993ce..2257b0d 100644
--- a/runtime/gc/collector/mark_sweep-inl.h
+++ b/runtime/gc/collector/mark_sweep-inl.h
@@ -40,7 +40,7 @@
     uint32_t class_flags = klass->GetClassFlags();
     if ((class_flags & mirror::kClassFlagNoReferenceFields) != 0) {
       ++no_reference_class_count_;
-    } else if (class_flags == mirror::kClassFlagNormal) {
+    } else if (class_flags == mirror::kClassFlagNormal || class_flags == mirror::kClassFlagRecord) {
       ++normal_count_;
     } else if (class_flags == mirror::kClassFlagObjectArray) {
       ++object_array_count_;
diff --git a/runtime/mirror/object-refvisitor-inl.h b/runtime/mirror/object-refvisitor-inl.h
index 4c72cd5..de60c8e 100644
--- a/runtime/mirror/object-refvisitor-inl.h
+++ b/runtime/mirror/object-refvisitor-inl.h
@@ -26,6 +26,40 @@
 namespace art {
 namespace mirror {
 
+template <VerifyObjectFlags kVerifyFlags,
+          ReadBarrierOption kReadBarrierOption>
+static void CheckNoReferenceField(ObjPtr<mirror::Class> klass)
+    REQUIRES_SHARED(art::Locks::mutator_lock_) {
+  if (!kIsDebugBuild) {
+    return;
+  }
+  CHECK(!klass->IsClassClass<kVerifyFlags>());
+  CHECK((!klass->IsObjectArrayClass<kVerifyFlags, kReadBarrierOption>()));
+  // String still has instance fields for reflection purposes but these don't exist in
+  // actual string instances.
+  if (!klass->IsStringClass<kVerifyFlags>()) {
+    size_t total_reference_instance_fields = 0;
+    ObjPtr<Class> super_class = klass;
+    do {
+      total_reference_instance_fields +=
+          super_class->NumReferenceInstanceFields<kVerifyFlags>();
+      super_class = super_class->GetSuperClass<kVerifyFlags, kReadBarrierOption>();
+    } while (super_class != nullptr);
+    // The only reference field should be the object's class.
+    CHECK_EQ(total_reference_instance_fields, 1u);
+  }
+}
+
+template <VerifyObjectFlags kVerifyFlags>
+static void CheckNormalClass(ObjPtr<mirror::Class> klass)
+    REQUIRES_SHARED(art::Locks::mutator_lock_) {
+  DCHECK(!klass->IsVariableSize<kVerifyFlags>());
+  DCHECK(!klass->IsClassClass<kVerifyFlags>());
+  DCHECK(!klass->IsStringClass<kVerifyFlags>());
+  DCHECK(!klass->IsClassLoaderClass<kVerifyFlags>());
+  DCHECK(!klass->IsArrayClass<kVerifyFlags>());
+}
+
 template <bool kVisitNativeRoots,
           VerifyObjectFlags kVerifyFlags,
           ReadBarrierOption kReadBarrierOption,
@@ -36,58 +70,58 @@
   visitor(this, ClassOffset(), /* is_static= */ false);
   ObjPtr<Class> klass = GetClass<kVerifyFlags, kReadBarrierOption>();
   const uint32_t class_flags = klass->GetClassFlags<kVerifyNone>();
-  if (LIKELY(class_flags == kClassFlagNormal)) {
-    DCHECK((!klass->IsVariableSize<kVerifyFlags>()));
+  if (LIKELY(class_flags == kClassFlagNormal) || class_flags == kClassFlagRecord) {
+    CheckNormalClass<kVerifyFlags>(klass);
     VisitInstanceFieldsReferences<kVerifyFlags, kReadBarrierOption>(klass, visitor);
-    DCHECK((!klass->IsClassClass<kVerifyFlags>()));
-    DCHECK(!klass->IsStringClass<kVerifyFlags>());
-    DCHECK(!klass->IsClassLoaderClass<kVerifyFlags>());
-    DCHECK((!klass->IsArrayClass<kVerifyFlags>()));
-  } else {
-    if ((class_flags & kClassFlagNoReferenceFields) == 0) {
-      DCHECK(!klass->IsStringClass<kVerifyFlags>());
-      if (class_flags == kClassFlagClass) {
-        DCHECK((klass->IsClassClass<kVerifyFlags>()));
-        ObjPtr<Class> as_klass = AsClass<kVerifyNone>();
-        as_klass->VisitReferences<kVisitNativeRoots, kVerifyFlags, kReadBarrierOption>(klass,
-                                                                                       visitor);
-      } else if (class_flags == kClassFlagObjectArray) {
-        DCHECK((klass->IsObjectArrayClass<kVerifyFlags>()));
-        AsObjectArray<mirror::Object, kVerifyNone>()->VisitReferences(visitor);
-      } else if ((class_flags & kClassFlagReference) != 0) {
-        VisitInstanceFieldsReferences<kVerifyFlags, kReadBarrierOption>(klass, visitor);
-        ref_visitor(klass, AsReference<kVerifyFlags, kReadBarrierOption>());
-      } else if (class_flags == kClassFlagDexCache) {
-        ObjPtr<mirror::DexCache> const dex_cache = AsDexCache<kVerifyFlags, kReadBarrierOption>();
-        dex_cache->VisitReferences<kVisitNativeRoots,
-                                   kVerifyFlags,
-                                   kReadBarrierOption>(klass, visitor);
-      } else {
-        ObjPtr<mirror::ClassLoader> const class_loader =
-            AsClassLoader<kVerifyFlags, kReadBarrierOption>();
-        class_loader->VisitReferences<kVisitNativeRoots,
-                                      kVerifyFlags,
-                                      kReadBarrierOption>(klass, visitor);
-      }
-    } else if (kIsDebugBuild) {
-      CHECK((!klass->IsClassClass<kVerifyFlags>()));
-      CHECK((!klass->IsObjectArrayClass<kVerifyFlags>()));
-      // String still has instance fields for reflection purposes but these don't exist in
-      // actual string instances.
-      if (!klass->IsStringClass<kVerifyFlags>()) {
-        size_t total_reference_instance_fields = 0;
-        ObjPtr<Class> super_class = klass;
-        do {
-          total_reference_instance_fields +=
-              super_class->NumReferenceInstanceFields<kVerifyFlags>();
-          super_class = super_class->GetSuperClass<kVerifyFlags, kReadBarrierOption>();
-        } while (super_class != nullptr);
-        // The only reference field should be the object's class. This field is handled at the
-        // beginning of the function.
-        CHECK_EQ(total_reference_instance_fields, 1u);
-      }
-    }
+    return;
   }
+
+  if ((class_flags & kClassFlagNoReferenceFields) != 0) {
+    CheckNoReferenceField<kVerifyFlags, kReadBarrierOption>(klass);
+    return;
+  }
+
+  DCHECK(!klass->IsStringClass<kVerifyFlags>());
+  if (class_flags == kClassFlagClass) {
+    DCHECK(klass->IsClassClass<kVerifyFlags>());
+    ObjPtr<Class> as_klass = AsClass<kVerifyNone>();
+    as_klass->VisitReferences<kVisitNativeRoots, kVerifyFlags, kReadBarrierOption>(klass, visitor);
+    return;
+  }
+
+  if (class_flags == kClassFlagObjectArray) {
+    DCHECK((klass->IsObjectArrayClass<kVerifyFlags, kReadBarrierOption>()));
+    AsObjectArray<mirror::Object, kVerifyNone>()->VisitReferences(visitor);
+    return;
+  }
+
+  if ((class_flags & kClassFlagReference) != 0) {
+    VisitInstanceFieldsReferences<kVerifyFlags, kReadBarrierOption>(klass, visitor);
+    ref_visitor(klass, AsReference<kVerifyFlags, kReadBarrierOption>());
+    return;
+  }
+
+  if (class_flags == kClassFlagDexCache) {
+    DCHECK(klass->IsDexCacheClass<kVerifyFlags>());
+    ObjPtr<mirror::DexCache> const dex_cache = AsDexCache<kVerifyFlags, kReadBarrierOption>();
+    dex_cache->VisitReferences<kVisitNativeRoots,
+                               kVerifyFlags,
+                               kReadBarrierOption>(klass, visitor);
+    return;
+  }
+
+  if (class_flags == kClassFlagClassLoader) {
+    DCHECK(klass->IsClassLoaderClass<kVerifyFlags>());
+    ObjPtr<mirror::ClassLoader> const class_loader =
+        AsClassLoader<kVerifyFlags, kReadBarrierOption>();
+    class_loader->VisitReferences<kVisitNativeRoots,
+                                  kVerifyFlags,
+                                  kReadBarrierOption>(klass, visitor);
+    return;
+  }
+
+  LOG(FATAL) << "Unexpected class flags: " << std::hex << class_flags
+            << " for " << klass->PrettyClass();
 }
 
 // Could be called with from-space address of the object as we access klass and
@@ -106,85 +140,64 @@
   ObjPtr<Class> klass = GetClass<kVerifyFlags, kReadBarrierOption>();
   DCHECK(klass != nullptr) << "obj=" << this;
   const uint32_t class_flags = klass->GetClassFlags<kVerifyNone>();
-  if (LIKELY(class_flags == kClassFlagNormal)) {
-    DCHECK((!klass->IsVariableSize<kVerifyFlags>()));
+  if (LIKELY(class_flags == kClassFlagNormal) || class_flags == kClassFlagRecord) {
+    CheckNormalClass<kVerifyFlags>(klass);
     VisitInstanceFieldsReferences<kVerifyFlags, kReadBarrierOption>(klass, visitor);
     size = kFetchObjSize ? klass->GetObjectSize<kSizeOfFlags>() : 0;
-    DCHECK((!klass->IsClassClass<kVerifyFlags>()));
-    DCHECK(!klass->IsStringClass<kVerifyFlags>());
-    DCHECK(!klass->IsClassLoaderClass<kVerifyFlags>());
-    DCHECK((!klass->IsArrayClass<kVerifyFlags>()));
-  } else {
-    if ((class_flags & kClassFlagNoReferenceFields) == 0) {
-      DCHECK(!klass->IsStringClass<kVerifyFlags>());
-      if (class_flags == kClassFlagClass) {
-        DCHECK((klass->IsClassClass<kVerifyFlags>()));
-        ObjPtr<Class> as_klass = ObjPtr<Class>::DownCast(this);
-        as_klass->VisitReferences<kVisitNativeRoots, kVerifyFlags, kReadBarrierOption>(klass,
-                                                                                       visitor);
-        size = kFetchObjSize ? as_klass->SizeOf<kSizeOfFlags>() : 0;
-      } else if (class_flags == kClassFlagObjectArray) {
-        DCHECK((klass->IsObjectArrayClass<kVerifyFlags, kReadBarrierOption>()));
-        ObjPtr<ObjectArray<Object>> obj_arr = ObjPtr<ObjectArray<Object>>::DownCast(this);
-        obj_arr->VisitReferences(visitor, begin, end);
-        size = kFetchObjSize ?
-                   obj_arr->SizeOf<kSizeOfFlags, kReadBarrierOption, /*kIsObjArray*/ true>() :
-                   0;
-      } else if ((class_flags & kClassFlagReference) != 0) {
-        VisitInstanceFieldsReferences<kVerifyFlags, kReadBarrierOption>(klass, visitor);
-        // Visit referent also as this is about updating the reference only.
-        // There is no reference processing happening here.
-        visitor(this, mirror::Reference::ReferentOffset(), /* is_static= */ false);
-        size = kFetchObjSize ? klass->GetObjectSize<kSizeOfFlags>() : 0;
-      } else if (class_flags == kClassFlagDexCache) {
-        ObjPtr<DexCache> const dex_cache = ObjPtr<DexCache>::DownCast(this);
-        dex_cache->VisitReferences<kVisitNativeRoots,
-                                   kVerifyFlags,
-                                   kReadBarrierOption>(klass, visitor);
-        size = kFetchObjSize ? klass->GetObjectSize<kSizeOfFlags>() : 0;
-      } else {
-        ObjPtr<ClassLoader> const class_loader = ObjPtr<ClassLoader>::DownCast(this);
-        class_loader->VisitReferences<kVisitNativeRoots,
-                                      kVerifyFlags,
-                                      kReadBarrierOption>(klass, visitor);
-        size = kFetchObjSize ? klass->GetObjectSize<kSizeOfFlags>() : 0;
-      }
+  } else if ((class_flags & kClassFlagNoReferenceFields) != 0) {
+    CheckNoReferenceField<kVerifyFlags, kReadBarrierOption>(klass);
+    if ((class_flags & kClassFlagString) != 0) {
+      size = kFetchObjSize ? static_cast<String*>(this)->SizeOf<kSizeOfFlags>() : 0;
+    } else if (klass->IsArrayClass<kVerifyFlags>()) {
+      // TODO: We can optimize this by implementing a SizeOf() version which takes
+      // component-size-shift as an argument, thereby avoiding multiple loads of
+      // component_type.
+      size = kFetchObjSize
+             ? static_cast<Array*>(this)->SizeOf<kSizeOfFlags, kReadBarrierOption>()
+             : 0;
     } else {
-      DCHECK((!klass->IsClassClass<kVerifyFlags>()));
-      DCHECK((!klass->IsObjectArrayClass<kVerifyFlags, kReadBarrierOption>()));
-      if ((class_flags & kClassFlagString) != 0) {
-        size = kFetchObjSize ? static_cast<String*>(this)->SizeOf<kSizeOfFlags>() : 0;
-      } else if (klass->IsArrayClass<kVerifyFlags>()) {
-        // TODO: We can optimize this by implementing a SizeOf() version which takes
-        // component-size-shift as an argument, thereby avoiding multiple loads of
-        // component_type.
-        size = kFetchObjSize
-               ? static_cast<Array*>(this)->SizeOf<kSizeOfFlags, kReadBarrierOption>()
-               : 0;
-      } else {
-        DCHECK_EQ(class_flags, kClassFlagNoReferenceFields)
-            << "class_flags: " << std::hex << class_flags;
-        // Only possibility left is of a normal klass instance with no references.
-        size = kFetchObjSize ? klass->GetObjectSize<kSizeOfFlags>() : 0;
-      }
-
-      if (kIsDebugBuild) {
-        // String still has instance fields for reflection purposes but these don't exist in
-        // actual string instances.
-        if (!klass->IsStringClass<kVerifyFlags>()) {
-          size_t total_reference_instance_fields = 0;
-          ObjPtr<Class> super_class = klass;
-          do {
-            total_reference_instance_fields +=
-                super_class->NumReferenceInstanceFields<kVerifyFlags>();
-            super_class = super_class->GetSuperClass<kVerifyFlags, kReadBarrierOption>();
-          } while (super_class != nullptr);
-          // The only reference field should be the object's class. This field is handled at the
-          // beginning of the function.
-          CHECK_EQ(total_reference_instance_fields, 1u);
-        }
-      }
+      DCHECK_EQ(class_flags, kClassFlagNoReferenceFields)
+          << "class_flags: " << std::hex << class_flags;
+      // Only possibility left is of a normal klass instance with no references.
+      size = kFetchObjSize ? klass->GetObjectSize<kSizeOfFlags>() : 0;
     }
+  } else if (class_flags == kClassFlagClass) {
+    DCHECK(klass->IsClassClass<kVerifyFlags>());
+    ObjPtr<Class> as_klass = ObjPtr<Class>::DownCast(this);
+    as_klass->VisitReferences<kVisitNativeRoots, kVerifyFlags, kReadBarrierOption>(klass,
+                                                                                   visitor);
+    size = kFetchObjSize ? as_klass->SizeOf<kSizeOfFlags>() : 0;
+  } else if (class_flags == kClassFlagObjectArray) {
+    DCHECK((klass->IsObjectArrayClass<kVerifyFlags, kReadBarrierOption>()));
+    ObjPtr<ObjectArray<Object>> obj_arr = ObjPtr<ObjectArray<Object>>::DownCast(this);
+    obj_arr->VisitReferences(visitor, begin, end);
+    size = kFetchObjSize ?
+               obj_arr->SizeOf<kSizeOfFlags, kReadBarrierOption, /*kIsObjArray*/ true>() :
+               0;
+  } else if ((class_flags & kClassFlagReference) != 0) {
+    VisitInstanceFieldsReferences<kVerifyFlags, kReadBarrierOption>(klass, visitor);
+    // Visit referent also as this is about updating the reference only.
+    // There is no reference processing happening here.
+    visitor(this, mirror::Reference::ReferentOffset(), /* is_static= */ false);
+    size = kFetchObjSize ? klass->GetObjectSize<kSizeOfFlags>() : 0;
+  } else if (class_flags == kClassFlagDexCache) {
+    DCHECK(klass->IsDexCacheClass<kVerifyFlags>());
+    ObjPtr<DexCache> const dex_cache = ObjPtr<DexCache>::DownCast(this);
+    dex_cache->VisitReferences<kVisitNativeRoots,
+                               kVerifyFlags,
+                               kReadBarrierOption>(klass, visitor);
+    size = kFetchObjSize ? klass->GetObjectSize<kSizeOfFlags>() : 0;
+  } else if (class_flags == kClassFlagClassLoader) {
+    DCHECK(klass->IsClassLoaderClass<kVerifyFlags>());
+    ObjPtr<ClassLoader> const class_loader = ObjPtr<ClassLoader>::DownCast(this);
+    class_loader->VisitReferences<kVisitNativeRoots,
+                                  kVerifyFlags,
+                                  kReadBarrierOption>(klass, visitor);
+    size = kFetchObjSize ? klass->GetObjectSize<kSizeOfFlags>() : 0;
+  } else {
+    LOG(FATAL) << "Unexpected class flags: " << std::hex << class_flags
+               << " for " << klass->PrettyClass();
+    size = -1;
   }
   visitor(this, ClassOffset(), /* is_static= */ false);
   return size;
diff --git a/test/849-records/build.py b/test/849-records/build.py
new file mode 100644
index 0000000..3f9392c
--- /dev/null
+++ b/test/849-records/build.py
@@ -0,0 +1,22 @@
+#
+# Copyright (C) 2023 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.
+
+
+def build(ctx):
+  # Use 17 and record annotations to compile records and generate annotations
+  # that let the runtime know what is a record class.
+  ctx.default_build(javac_source_arg="17",
+                    javac_target_arg="17",
+                    d8_flags=["-JDcom.android.tools.r8.emitRecordAnnotationsInDex"])
diff --git a/test/849-records/expected-stderr.txt b/test/849-records/expected-stderr.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/849-records/expected-stderr.txt
diff --git a/test/849-records/expected-stdout.txt b/test/849-records/expected-stdout.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/849-records/expected-stdout.txt
diff --git a/test/849-records/info.txt b/test/849-records/info.txt
new file mode 100644
index 0000000..08cb76b
--- /dev/null
+++ b/test/849-records/info.txt
@@ -0,0 +1 @@
+Regression test for b/297966050.
diff --git a/test/849-records/src/Main.java b/test/849-records/src/Main.java
new file mode 100644
index 0000000..3512e0b
--- /dev/null
+++ b/test/849-records/src/Main.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+class Main {
+  public static void main(String[] args) {
+    Foo f = new Foo(args);
+    if (!f.getClass().isRecord()) {
+      throw new Error("Expected " + f.getClass() + " to be a record");
+    }
+    // Trigger a GC, which used to crash when visiting an instance of a record class.
+    Runtime.getRuntime().gc();
+  }
+
+  record Foo(Object o) {}
+}
diff --git a/test/run_test_build.py b/test/run_test_build.py
index eb2de12..c5b76c2 100755
--- a/test/run_test_build.py
+++ b/test/run_test_build.py
@@ -72,7 +72,7 @@
     self.java_home = Path(os.environ.get("JAVA_HOME")).absolute()
     self.java_path = self.java_home / "bin/java"
     self.javac_path = self.java_home / "bin/javac"
-    self.javac_args = "-g -Xlint:-options -source 1.8 -target 1.8"
+    self.javac_args = "-g -Xlint:-options"
 
     # Helper functions to execute tools.
     self.d8 = functools.partial(self.run, args.d8.absolute())
@@ -215,6 +215,8 @@
       smali_args=[],
       use_smali=True,
       use_jasmin=True,
+      javac_source_arg="1.8",
+      javac_target_arg="1.8"
     ):
     javac_classpath = javac_classpath.copy()  # Do not modify default value.
 
@@ -280,7 +282,8 @@
       dst_dir.mkdir(exist_ok=True)
       args = self.javac_args.split(" ") + javac_args
       args += ["-implicit:none", "-encoding", "utf8", "-d", dst_dir]
-      if not self.jvm:
+      args += ["-source", javac_source_arg, "-target", javac_target_arg]
+      if not self.jvm and float(javac_target_arg) < 17.0:
         args += ["-bootclasspath", self.bootclasspath]
       if javac_classpath:
         args += ["-classpath", javac_classpath]