Reland "[ Observatory ] Add basic records support to Observatory"

This reverts commit 3ab116198c8879e1be72720851eb9739fc5ae26a.

TEST=N/A

Change-Id: I19487d72532ad47210d4ff7e6a18dd709e74a62a
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/269740
Reviewed-by: Derek Xu <derekx@google.com>
Commit-Queue: Ben Konyi <bkonyi@google.com>
diff --git a/runtime/observatory/lib/src/elements/helpers/any_ref.dart b/runtime/observatory/lib/src/elements/helpers/any_ref.dart
index ac5e1e3..222f40d 100644
--- a/runtime/observatory/lib/src/elements/helpers/any_ref.dart
+++ b/runtime/observatory/lib/src/elements/helpers/any_ref.dart
@@ -93,6 +93,8 @@
     }
   } else if (ref is M.Sentinel) {
     return new SentinelValueElement(ref, queue: queue).element;
+  } else if (ref is num || ref is String) {
+    return new SpanElement()..text = ref.toString();
   }
   throw new Exception('Unknown ref type (${ref.runtimeType})');
 }
diff --git a/runtime/observatory/lib/src/elements/instance_ref.dart b/runtime/observatory/lib/src/elements/instance_ref.dart
index 819a499..01ba033 100644
--- a/runtime/observatory/lib/src/elements/instance_ref.dart
+++ b/runtime/observatory/lib/src/elements/instance_ref.dart
@@ -128,6 +128,7 @@
       case M.InstanceKind.functionType:
       case M.InstanceKind.typeRef:
       case M.InstanceKind.typeParameter:
+      case M.InstanceKind.recordType:
         return [
           new AnchorElement(href: Uris.inspect(_isolate, object: _instance))
             ..text = _instance.name
@@ -194,12 +195,10 @@
             ]
         ];
       case M.InstanceKind.mirrorReference:
-        return [
-          new AnchorElement(href: Uris.inspect(_isolate, object: _instance))
-            ..classes = ['emphasize']
-            ..text = _instance.clazz!.name
-        ];
       case M.InstanceKind.weakProperty:
+      case M.InstanceKind.finalizer:
+      case M.InstanceKind.weakReference:
+      case M.InstanceKind.record:
         return [
           new AnchorElement(href: Uris.inspect(_isolate, object: _instance))
             ..classes = ['emphasize']
@@ -216,6 +215,7 @@
       case M.InstanceKind.mirrorReference:
       case M.InstanceKind.stackTrace:
       case M.InstanceKind.weakProperty:
+      case M.InstanceKind.recordType:
         return true;
       case M.InstanceKind.list:
       case M.InstanceKind.map:
@@ -339,6 +339,18 @@
                   queue: _r.queue)
               .element,
         ];
+      case M.InstanceKind.recordType:
+        final fields = _loadedInstance!.fields!.toList();
+        return [
+          for (int i = 0; i < fields.length; ++i) ...[
+            new SpanElement()..text = '${fields[i].name} = ',
+            new InstanceRefElement(
+                    _isolate, fields[i].value!.asValue!, _objects,
+                    queue: _r.queue)
+                .element,
+            if (i + 1 != fields.length) new BRElement(),
+          ]
+        ];
       default:
         return [];
     }
diff --git a/runtime/observatory/lib/src/elements/instance_view.dart b/runtime/observatory/lib/src/elements/instance_view.dart
index 2063c28..4c4d690 100644
--- a/runtime/observatory/lib/src/elements/instance_view.dart
+++ b/runtime/observatory/lib/src/elements/instance_view.dart
@@ -335,9 +335,12 @@
                     ..content = <Element>[
                       new DivElement()
                         ..classes = ['memberList']
-                        ..children = fields
-                            .map<Element>((f) => member(f.decl, f.value))
-                            .toList()
+                        ..children = fields.map<Element>((f) {
+                          final name = _instance.kind == M.InstanceKind.record
+                              ? f.name
+                              : f.decl;
+                          return member(name, f.value);
+                        }).toList()
                     ])
                   .element
             ]
diff --git a/runtime/observatory/lib/src/models/objects/instance.dart b/runtime/observatory/lib/src/models/objects/instance.dart
index 8102095..1b2d58e 100644
--- a/runtime/observatory/lib/src/models/objects/instance.dart
+++ b/runtime/observatory/lib/src/models/objects/instance.dart
@@ -126,6 +126,18 @@
 
   /// An instance of the Dart class RawReceivePort
   receivePort,
+
+  /// An instance of Record.
+  record,
+
+  /// An instance of RecordType
+  recordType,
+
+  /// An instance of Finalizer
+  finalizer,
+
+  /// An instance of WeakReference
+  weakReference,
 }
 
 bool isTypedData(InstanceKind? kind) {
@@ -463,7 +475,8 @@
 
 abstract class BoundField {
   FieldRef? get decl;
-  Guarded<InstanceRef>? get value;
+  Guarded<dynamic>? get value;
+  dynamic get name;
 }
 
 abstract class NativeField {
diff --git a/runtime/observatory/lib/src/service/object.dart b/runtime/observatory/lib/src/service/object.dart
index cafadae..5815ca6 100644
--- a/runtime/observatory/lib/src/service/object.dart
+++ b/runtime/observatory/lib/src/service/object.dart
@@ -2108,7 +2108,7 @@
     _map.clear();
     map.forEach((k, v) => _map[k] = v);
 
-    name = _map['name'];
+    name = _map['name']?.toString();
     vmName = (_map.containsKey('_vmName') ? _map['_vmName'] : name);
   }
 
@@ -2791,25 +2791,33 @@
       return M.InstanceKind.typeRef;
     case 'ReceivePort':
       return M.InstanceKind.receivePort;
+    case '_RecordType':
+      return M.InstanceKind.recordType;
+    case '_Record':
+      return M.InstanceKind.record;
+    case 'Finalizer':
+      return M.InstanceKind.finalizer;
+    case 'WeakReference':
+      return M.InstanceKind.weakReference;
   }
   var message = 'Unrecognized instance kind: $s';
   Logger.root.severe(message);
   throw new ArgumentError(message);
 }
 
-class Guarded<T extends ServiceObject> implements M.Guarded<T> {
+class Guarded<T> implements M.Guarded<T> {
   bool get isValue => asValue != null;
   bool get isSentinel => asSentinel != null;
   final Sentinel? asSentinel;
   final T? asValue;
 
-  factory Guarded(ServiceObject obj) {
+  factory Guarded(dynamic obj) {
     if (obj is Sentinel) {
       return new Guarded.fromSentinel(obj);
     } else if (obj is T) {
       return new Guarded.fromValue(obj);
     }
-    throw new Exception('${obj.type} is neither Sentinel or $T');
+    throw new Exception('${obj.runtimeType} is neither Sentinel or $T');
   }
 
   Guarded.fromSentinel(this.asSentinel) : asValue = null;
@@ -2817,9 +2825,11 @@
 }
 
 class BoundField implements M.BoundField {
-  final Field decl;
-  final Guarded<Instance> value;
-  BoundField(this.decl, value) : value = new Guarded(value);
+  final Field? decl;
+  // String|int
+  final dynamic name;
+  final Guarded<dynamic> value;
+  BoundField(this.decl, this.name, value) : value = new Guarded(value);
 }
 
 class NativeField implements M.NativeField {
@@ -2916,7 +2926,7 @@
   Instance._empty(ServiceObjectOwner? owner) : super._empty(owner);
 
   void _update(Map map, bool mapIsRef) {
-    // Extract full properties.1
+    // Extract full properties.
     _upgradeCollection(map, isolate);
     super._update(map, mapIsRef);
 
@@ -2925,7 +2935,7 @@
     // Coerce absence to false.
     valueAsStringIsTruncated = map['valueAsStringIsTruncated'] == true;
     closureFunction = map['closureFunction'];
-    name = map['name'];
+    name = map['name']?.toString();
     length = map['length'];
     pattern = map['pattern'];
     typeClass = map['typeClass'];
@@ -2958,7 +2968,7 @@
     if (map['fields'] != null) {
       var fields = <BoundField>[];
       for (var f in map['fields']) {
-        fields.add(new BoundField(f['decl'], f['value']));
+        fields.add(new BoundField(f['decl'], f['name'], f['value']));
       }
       this.fields = fields;
     } else {
diff --git a/runtime/observatory/tests/ui/inspector.dart b/runtime/observatory/tests/ui/inspector.dart
index e8fc90a..8f9ae74 100644
--- a/runtime/observatory/tests/ui/inspector.dart
+++ b/runtime/observatory/tests/ui/inspector.dart
@@ -2,6 +2,9 @@
 // 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.
 
+// ignore_for_file: experiment_not_enabled
+// @dart = 2.19
+
 // See inspector.txt for expected behavior.
 
 library manual_inspector_test;
@@ -43,10 +46,13 @@
   var blockCopying;
   var blockFull;
   var blockFullWithChain;
+  var blockType;
   var boundedType;
   var capability;
   var counter;
   var expando;
+  var finalizer;
+  var finalizerEntry;
   var float32x4;
   var float64;
   var float64x2;
@@ -62,7 +68,10 @@
   var mirrorReference;
   var portReceive;
   var portSend;
+  var record;
+  var recordType;
   var regex;
+  late var sentinel;  // Not initialized
   var smi;
   var stacktrace;
   var string;
@@ -76,9 +85,12 @@
   var theTrue;
   var type;
   var typeParameter;
-  var typedData;
+  var typedDataArray;
+  var typedDataView;
+  var typedDataUnmodifiableView;
   var userTag;
   var weakProperty;
+  var weakReference;
 
   genStackTrace() {
     try {
@@ -141,16 +153,19 @@
     array[0] = 1;
     array[1] = 2;
     array[2] = 3;
-    bigint = 1 << 65;
+    bigint = BigInt.one << 65;
     blockClean = genCleanBlock();
     blockCopying = genCopyingBlock();
     blockFull = genFullBlock();
     blockFullWithChain = genFullBlockWithChain();
+    blockType = blockClean.runtimeType;
     boundedType = extractPrivateField(
         reflect(new B<int>()).type.typeVariables.single, '_reflectee');
     counter = new Counter("CounterName", "Counter description");
     expando = new Expando("expando-name");
     expando[array] = 'The weakly associated value';
+    finalizer = Finalizer<dynamic>((_){});
+    finalizer.attach(this, this);
     float32x4 = new Float32x4(0.0, -1.0, 3.14, 2e28);
     float64 = 3.14;
     float64x2 = new Float64x2(0.0, 3.14);
@@ -170,6 +185,8 @@
     mirrorReference = extractPrivateField(mirrorClass, '_reflectee');
     portReceive = new RawReceivePort();
     portSend = portReceive.sendPort;
+    record = (1, 2, three: 3, four: 4);
+    recordType = record.runtimeType;
     regex = new RegExp("a*b+c");
     smi = 7;
     stacktrace = genStackTrace();
@@ -185,10 +202,13 @@
     type = String;
     typeParameter =
         extractPrivateField(reflectClass(A).typeVariables.single, '_reflectee');
-    typedData = extractPrivateField(new ByteData(64), '_typedData');
+    typedDataArray = Uint8List(32);
+    typedDataView = Uint8List.view(typedDataArray.buffer, 16);
+    typedDataUnmodifiableView = UnmodifiableUint8ListView(typedDataArray);
     userTag = new UserTag("Example tag name");
     weakProperty =
         extractPrivateField(expando, '_data').firstWhere((e) => e != null);
+    weakReference = WeakReference(this);
 
     Isolate.spawn(secondMain, "Hello2").then((otherIsolate) {
       isolate = otherIsolate;
diff --git a/runtime/observatory/tests/ui/inspector_part.dart b/runtime/observatory/tests/ui/inspector_part.dart
index 06589e3..6222354 100644
--- a/runtime/observatory/tests/ui/inspector_part.dart
+++ b/runtime/observatory/tests/ui/inspector_part.dart
@@ -2,6 +2,8 @@
 // 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.19
+
 part of manual_inspector_test;
 
 functionInPart() {}
diff --git a/runtime/observatory_2/lib/src/elements/helpers/any_ref.dart b/runtime/observatory_2/lib/src/elements/helpers/any_ref.dart
index 0ae2930..a1f2d96 100644
--- a/runtime/observatory_2/lib/src/elements/helpers/any_ref.dart
+++ b/runtime/observatory_2/lib/src/elements/helpers/any_ref.dart
@@ -93,6 +93,8 @@
     }
   } else if (ref is M.Sentinel) {
     return new SentinelValueElement(ref, queue: queue).element;
+  } else if (ref is num || ref is String) {
+    return new SpanElement()..text = ref.toString();
   }
   throw new Exception('Unknown ref type (${ref.runtimeType})');
 }
diff --git a/runtime/observatory_2/lib/src/elements/instance_ref.dart b/runtime/observatory_2/lib/src/elements/instance_ref.dart
index a4b3f9e3..e98148e 100644
--- a/runtime/observatory_2/lib/src/elements/instance_ref.dart
+++ b/runtime/observatory_2/lib/src/elements/instance_ref.dart
@@ -127,6 +127,7 @@
       case M.InstanceKind.functionType:
       case M.InstanceKind.typeRef:
       case M.InstanceKind.typeParameter:
+      case M.InstanceKind.recordType:
         return [
           new AnchorElement(href: Uris.inspect(_isolate, object: _instance))
             ..text = _instance.name
@@ -192,12 +193,10 @@
             ]
         ];
       case M.InstanceKind.mirrorReference:
-        return [
-          new AnchorElement(href: Uris.inspect(_isolate, object: _instance))
-            ..classes = ['emphasize']
-            ..text = _instance.clazz.name
-        ];
       case M.InstanceKind.weakProperty:
+      case M.InstanceKind.finalizer:
+      case M.InstanceKind.weakReference:
+      case M.InstanceKind.record:
         return [
           new AnchorElement(href: Uris.inspect(_isolate, object: _instance))
             ..classes = ['emphasize']
@@ -214,6 +213,7 @@
       case M.InstanceKind.mirrorReference:
       case M.InstanceKind.stackTrace:
       case M.InstanceKind.weakProperty:
+      case M.InstanceKind.recordType:
         return true;
       case M.InstanceKind.list:
       case M.InstanceKind.map:
@@ -337,6 +337,17 @@
                   queue: _r.queue)
               .element,
         ];
+      case M.InstanceKind.recordType:
+        final fields = _loadedInstance.fields.toList();
+        return [
+          for (int i = 0; i < fields.length; ++i) ...[
+            new SpanElement()..text = '${fields[i].name} = ',
+            new InstanceRefElement(_isolate, fields[i].value.asValue, _objects,
+                    queue: _r.queue)
+                .element,
+            if (i + 1 != fields.length) new BRElement(),
+          ]
+        ];
       default:
         return [];
     }
diff --git a/runtime/observatory_2/lib/src/elements/instance_view.dart b/runtime/observatory_2/lib/src/elements/instance_view.dart
index fcc1812..592a978 100644
--- a/runtime/observatory_2/lib/src/elements/instance_view.dart
+++ b/runtime/observatory_2/lib/src/elements/instance_view.dart
@@ -334,9 +334,12 @@
                     ..content = <Element>[
                       new DivElement()
                         ..classes = ['memberList']
-                        ..children = fields
-                            .map<Element>((f) => member(f.decl, f.value))
-                            .toList()
+                        ..children = fields.map<Element>((f) {
+                          final name = _instance.kind == M.InstanceKind.record
+                              ? f.name
+                              : f.decl;
+                          return member(name, f.value);
+                        }).toList()
                     ])
                   .element
             ]
diff --git a/runtime/observatory_2/lib/src/models/objects/instance.dart b/runtime/observatory_2/lib/src/models/objects/instance.dart
index b21c572..ff914bc 100644
--- a/runtime/observatory_2/lib/src/models/objects/instance.dart
+++ b/runtime/observatory_2/lib/src/models/objects/instance.dart
@@ -126,6 +126,18 @@
 
   /// An instance of the Dart class RawReceivePort
   receivePort,
+
+  /// An instance of Record.
+  record,
+
+  /// An instance of RecordType
+  recordType,
+
+  /// An instance of Finalizer
+  finalizer,
+
+  /// An instance of WeakReference
+  weakReference,
 }
 
 bool isTypedData(InstanceKind kind) {
@@ -454,7 +466,8 @@
 
 abstract class BoundField {
   FieldRef get decl;
-  Guarded<InstanceRef> get value;
+  dynamic get name;
+  Guarded<dynamic> get value;
 }
 
 abstract class NativeField {
diff --git a/runtime/observatory_2/lib/src/service/object.dart b/runtime/observatory_2/lib/src/service/object.dart
index 5c9f6df..bfe5b05 100644
--- a/runtime/observatory_2/lib/src/service/object.dart
+++ b/runtime/observatory_2/lib/src/service/object.dart
@@ -2118,7 +2118,7 @@
     _map.clear();
     _map.addAll(map);
 
-    name = _map['name'];
+    name = _map['name']?.toString();
     vmName = (_map.containsKey('_vmName') ? _map['_vmName'] : name);
   }
 
@@ -2800,25 +2800,33 @@
       return M.InstanceKind.typeRef;
     case 'ReceivePort':
       return M.InstanceKind.receivePort;
+    case '_Record':
+      return M.InstanceKind.record;
+    case '_RecordType':
+      return M.InstanceKind.recordType;
+    case 'Finalizer':
+      return M.InstanceKind.finalizer;
+    case 'WeakReference':
+      return M.InstanceKind.weakReference;
   }
   var message = 'Unrecognized instance kind: $s';
   Logger.root.severe(message);
   throw new ArgumentError(message);
 }
 
-class Guarded<T extends ServiceObject> implements M.Guarded<T> {
+class Guarded<T> implements M.Guarded<T> {
   bool get isValue => asValue != null;
   bool get isSentinel => asSentinel != null;
   final Sentinel asSentinel;
   final T asValue;
 
-  factory Guarded(ServiceObject obj) {
+  factory Guarded(dynamic obj) {
     if (obj is Sentinel) {
       return new Guarded.fromSentinel(obj);
     } else if (obj is T) {
       return new Guarded.fromValue(obj);
     }
-    throw new Exception('${obj.type} is neither Sentinel or $T');
+    throw new Exception('${obj.runtimeType} is neither Sentinel or $T');
   }
 
   Guarded.fromSentinel(this.asSentinel) : asValue = null;
@@ -2827,8 +2835,10 @@
 
 class BoundField implements M.BoundField {
   final Field decl;
-  final Guarded<Instance> value;
-  BoundField(this.decl, value) : value = new Guarded(value);
+  // String|int
+  final dynamic name;
+  final Guarded<dynamic> value;
+  BoundField(this.decl, this.name, value) : value = new Guarded(value);
 }
 
 class NativeField implements M.NativeField {
@@ -2929,7 +2939,7 @@
   Instance._empty(ServiceObjectOwner owner) : super._empty(owner);
 
   void _update(Map map, bool mapIsRef) {
-    // Extract full properties.1
+    // Extract full properties.
     _upgradeCollection(map, isolate);
     super._update(map, mapIsRef);
 
@@ -2938,7 +2948,7 @@
     // Coerce absence to false.
     valueAsStringIsTruncated = map['valueAsStringIsTruncated'] == true;
     closureFunction = map['closureFunction'];
-    name = map['name'];
+    name = map['name']?.toString();
     length = map['length'];
     pattern = map['pattern'];
     typeClass = map['typeClass'];
@@ -2971,7 +2981,7 @@
     if (map['fields'] != null) {
       var fields = <BoundField>[];
       for (var f in map['fields']) {
-        fields.add(new BoundField(f['decl'], f['value']));
+        fields.add(new BoundField(f['decl'], f['name'], f['value']));
       }
       this.fields = fields;
     } else {
diff --git a/runtime/vm/object_service.cc b/runtime/vm/object_service.cc
index 053c1c7..b33d279 100644
--- a/runtime/vm/object_service.cc
+++ b/runtime/vm/object_service.cc
@@ -1128,7 +1128,7 @@
 
   Array& field_array = Array::Handle();
   Field& field = Field::Handle();
-  Instance& field_value = Instance::Handle();
+  Object& field_value = Object::Handle();
   {
     JSONArray jsarr(jsobj, "fields");
     for (intptr_t i = classes.length() - 1; i >= 0; i--) {
@@ -1137,7 +1137,7 @@
         for (intptr_t j = 0; j < field_array.Length(); j++) {
           field ^= field_array.At(j);
           if (!field.is_static()) {
-            field_value ^= GetField(field);
+            field_value = GetField(field);
             JSONObject jsfield(&jsarr);
             jsfield.AddProperty("type", "BoundField");
             jsfield.AddProperty("decl", field);