[vm] Avoid repeating fields in heapsnapshot class descriptions
Fixes https://github.com/dart-lang/sdk/issues/49710
TEST=tests/vm/dart{,_2}/heap_snapshot_regress_49710_test.dart
Change-Id: Ib2c31a582381dcfaf12739b9d0c7e28d98063791
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/255813
Reviewed-by: Ryan Macnak <rmacnak@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
diff --git a/runtime/tests/vm/dart/heap_snapshot_regress_49710_test.dart b/runtime/tests/vm/dart/heap_snapshot_regress_49710_test.dart
new file mode 100644
index 0000000..ad5adb4
--- /dev/null
+++ b/runtime/tests/vm/dart/heap_snapshot_regress_49710_test.dart
@@ -0,0 +1,30 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:_internal';
+
+import 'package:expect/expect.dart';
+import 'package:path/path.dart' as path;
+
+import 'heap_snapshot_test.dart';
+import 'use_flag_test_helper.dart';
+
+main() async {
+ if (const bool.fromEnvironment('dart.vm.product')) return;
+
+ await withTempDir('heap_snapshot_test', (String dir) async {
+ final file = path.join(dir, 'state1.heapsnapshot');
+ VMInternalsForTesting.writeHeapSnapshotToFile(file);
+ final snapshot = loadHeapSnapshotFromFile(file);
+ for (final klass in snapshot.classes) {
+ // Ensure field indices are unique.
+ final fields = klass.fields.toList()..sort((a, b) => a.index - b.index);
+ int lastIndex = -1;
+ for (int i = 0; i < fields.length; ++i) {
+ Expect.notEquals(lastIndex, fields[i].index);
+ lastIndex = fields[i].index;
+ }
+ }
+ });
+}
diff --git a/runtime/tests/vm/dart_2/heap_snapshot_regress_49710_test.dart b/runtime/tests/vm/dart_2/heap_snapshot_regress_49710_test.dart
new file mode 100644
index 0000000..be1c546
--- /dev/null
+++ b/runtime/tests/vm/dart_2/heap_snapshot_regress_49710_test.dart
@@ -0,0 +1,32 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// @dart=2.9
+
+import 'dart:_internal';
+
+import 'package:expect/expect.dart';
+import 'package:path/path.dart' as path;
+
+import 'heap_snapshot_test.dart';
+import 'use_flag_test_helper.dart';
+
+main() async {
+ if (const bool.fromEnvironment('dart.vm.product')) return;
+
+ await withTempDir('heap_snapshot_test', (String dir) async {
+ final file = path.join(dir, 'state1.heapsnapshot');
+ VMInternalsForTesting.writeHeapSnapshotToFile(file);
+ final snapshot = loadHeapSnapshotFromFile(file);
+ for (final klass in snapshot.classes) {
+ // Ensure field indices are unique.
+ final fields = klass.fields.toList()..sort((a, b) => a.index - b.index);
+ int lastIndex = -1;
+ for (int i = 0; i < fields.length; ++i) {
+ Expect.notEquals(lastIndex, fields[i].index);
+ lastIndex = fields[i].index;
+ }
+ }
+ });
+}
diff --git a/runtime/vm/object_graph.cc b/runtime/vm/object_graph.cc
index 6265e3e..9ff0050 100644
--- a/runtime/vm/object_graph.cc
+++ b/runtime/vm/object_graph.cc
@@ -1338,16 +1338,18 @@
}
WriteUtf8(""); // Reserved
+ bool via_offsets_table = false;
intptr_t field_count = 0;
intptr_t min_offset = kIntptrMax;
for (const auto& entry : OffsetsTable::offsets_table()) {
if (entry.class_id == cid) {
+ via_offsets_table = true;
field_count++;
intptr_t offset = entry.offset;
min_offset = Utils::Minimum(min_offset, offset);
}
}
- if (cls.is_finalized()) {
+ if (!via_offsets_table && cls.is_finalized()) {
do {
fields = cls.fields();
if (!fields.IsNull()) {
@@ -1376,7 +1378,7 @@
WriteUtf8(""); // Reserved
}
}
- if (cls.is_finalized()) {
+ if (!via_offsets_table && cls.is_finalized()) {
do {
fields = cls.fields();
if (!fields.IsNull()) {
diff --git a/runtime/vm/raw_object_fields.cc b/runtime/vm/raw_object_fields.cc
index a7e7a92..3753236 100644
--- a/runtime/vm/raw_object_fields.cc
+++ b/runtime/vm/raw_object_fields.cc
@@ -166,7 +166,7 @@
F(LinkedHashMap, hash_mask_) \
F(LinkedHashMap, data_) \
F(LinkedHashMap, used_data_) \
- F(LinkedHashSet, deleted_keys_) \
+ F(LinkedHashMap, deleted_keys_) \
F(LinkedHashSet, type_arguments_) \
F(LinkedHashSet, index_) \
F(LinkedHashSet, hash_mask_) \