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]