[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_)                                                 \