Version 2.15.0-24.0.dev

Merge commit '5b3cadc7e6d7be94ef959e3733357980ff69c684' into 'dev'
diff --git a/benchmarks/SDKArtifactSizes/dart/SDKArtifactSizes.dart b/benchmarks/SDKArtifactSizes/dart/SDKArtifactSizes.dart
index ece1bcc..5987a50 100644
--- a/benchmarks/SDKArtifactSizes/dart/SDKArtifactSizes.dart
+++ b/benchmarks/SDKArtifactSizes/dart/SDKArtifactSizes.dart
@@ -63,4 +63,24 @@
         '$rootDir/dart-sdk/bin/snapshots/$snapshot.dart.snapshot';
     await reportArtifactSize(snapshotPath, snapshot);
   }
+
+  // Measure the (compressed) sdk size.
+  final tempDir = Directory.systemTemp.createTempSync('dartdev');
+  final sdkArchive =
+      compress(File(Platform.resolvedExecutable).parent.parent, tempDir);
+  await reportArtifactSize(sdkArchive?.path, 'sdk');
+  tempDir.deleteSync(recursive: true);
+}
+
+File compress(Directory sourceDir, Directory targetDir) {
+  final outFile = File('${targetDir.path}/sdk.zip');
+
+  if (Platform.isMacOS || Platform.isLinux) {
+    Process.runSync(
+        'zip', ['-r', outFile.absolute.path, sourceDir.absolute.path]);
+  } else {
+    return null;
+  }
+
+  return outFile;
 }
diff --git a/benchmarks/SDKArtifactSizes/dart2/SDKArtifactSizes.dart b/benchmarks/SDKArtifactSizes/dart2/SDKArtifactSizes.dart
index ece1bcc..0cefa4d 100644
--- a/benchmarks/SDKArtifactSizes/dart2/SDKArtifactSizes.dart
+++ b/benchmarks/SDKArtifactSizes/dart2/SDKArtifactSizes.dart
@@ -63,4 +63,24 @@
         '$rootDir/dart-sdk/bin/snapshots/$snapshot.dart.snapshot';
     await reportArtifactSize(snapshotPath, snapshot);
   }
+
+  // Measure the (compressed) sdk size.
+  final tempDir = Directory.systemTemp.createTempSync('dartdev');
+  final sdkArchive =
+      compress(File(Platform.resolvedExecutable).parent.parent, tempDir);
+  await reportArtifactSize(sdkArchive?.path ?? '', 'sdk');
+  tempDir.deleteSync(recursive: true);
+}
+
+File? compress(Directory sourceDir, Directory targetDir) {
+  final outFile = File('${targetDir.path}/sdk.zip');
+
+  if (Platform.isMacOS || Platform.isLinux) {
+    Process.runSync(
+        'zip', ['-r', outFile.absolute.path, sourceDir.absolute.path]);
+  } else {
+    return null;
+  }
+
+  return outFile;
 }
diff --git a/pkg/analysis_server/lib/src/analysis_server.dart b/pkg/analysis_server/lib/src/analysis_server.dart
index 3896905..38529f2 100644
--- a/pkg/analysis_server/lib/src/analysis_server.dart
+++ b/pkg/analysis_server/lib/src/analysis_server.dart
@@ -24,6 +24,7 @@
 import 'package:analysis_server/src/domain_execution.dart';
 import 'package:analysis_server/src/domain_kythe.dart';
 import 'package:analysis_server/src/domain_server.dart';
+import 'package:analysis_server/src/domains/analysis/macro_files.dart';
 import 'package:analysis_server/src/domains/analysis/occurrences.dart';
 import 'package:analysis_server/src/domains/analysis/occurrences_dart.dart';
 import 'package:analysis_server/src/edit/edit_domain.dart';
@@ -54,6 +55,7 @@
 import 'package:analyzer/src/util/file_paths.dart' as file_paths;
 import 'package:analyzer_plugin/protocol/protocol_common.dart' hide Element;
 import 'package:analyzer_plugin/src/utilities/navigation/navigation.dart';
+import 'package:analyzer_plugin/utilities/analyzer_converter.dart';
 import 'package:analyzer_plugin/utilities/navigation/navigation_dart.dart';
 import 'package:http/http.dart' as http;
 import 'package:telemetry/crash_reporting.dart';
@@ -718,7 +720,10 @@
   server.AnalysisNavigationParams _computeNavigationParams(
       String path, CompilationUnit unit) {
     var collector = NavigationCollectorImpl();
-    computeDartNavigation(resourceProvider, collector, unit, null, null);
+    computeDartNavigation(resourceProvider, collector, unit, null, null,
+        analyzerConverter: AnalyzerConverter(
+            locationProvider:
+                MacroElementLocationProvider(MacroFiles(resourceProvider))));
     collector.createRegions();
     return server.AnalysisNavigationParams(
         path, collector.regions, collector.targets, collector.files);
diff --git a/pkg/analysis_server/lib/src/domains/analysis/macro_files.dart b/pkg/analysis_server/lib/src/domains/analysis/macro_files.dart
new file mode 100644
index 0000000..be391be
--- /dev/null
+++ b/pkg/analysis_server/lib/src/domains/analysis/macro_files.dart
@@ -0,0 +1,121 @@
+// Copyright (c) 2021, 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 'package:analyzer/dart/element/element.dart';
+import 'package:analyzer/file_system/file_system.dart';
+import 'package:analyzer/source/line_info.dart';
+import 'package:analyzer/src/dart/element/element.dart';
+import 'package:analyzer/src/util/file_paths.dart' as file_paths;
+import 'package:analyzer_plugin/protocol/protocol_common.dart' as protocol;
+import 'package:analyzer_plugin/utilities/analyzer_converter.dart';
+
+class MacroElementLocationProvider implements ElementLocationProvider {
+  final MacroFiles _macroFiles;
+
+  MacroElementLocationProvider(this._macroFiles);
+
+  @override
+  protocol.Location? forElement(Element element) {
+    if (element is HasMacroGenerationData) {
+      var macro = (element as HasMacroGenerationData).macro;
+      if (macro != null) {
+        return _forElement(element, macro);
+      }
+    }
+  }
+
+  protocol.Location? _forElement(Element element, MacroGenerationData macro) {
+    var unitElement = element.thisOrAncestorOfType<CompilationUnitElement>();
+    if (unitElement is! CompilationUnitElementImpl) {
+      return null;
+    }
+
+    var generatedFile = _macroFiles.generatedFile(unitElement);
+    if (generatedFile == null) {
+      return null;
+    }
+
+    var nameOffset = element.nameOffset;
+    var nameLength = element.nameLength;
+
+    var lineInfo = generatedFile.lineInfo;
+    var offsetLocation = lineInfo.getLocation(nameOffset);
+    var endLocation = lineInfo.getLocation(nameOffset + nameLength);
+
+    return protocol.Location(generatedFile.path, nameOffset, nameLength,
+        offsetLocation.lineNumber, offsetLocation.columnNumber,
+        endLine: endLocation.lineNumber, endColumn: endLocation.columnNumber);
+  }
+}
+
+/// Note, this class changes the file system.
+class MacroFiles {
+  final ResourceProvider _resourceProvider;
+
+  /// Keys are source paths.
+  final Map<String, _MacroGeneratedFile> _files = {};
+
+  MacroFiles(this._resourceProvider);
+
+  /// If [unitElement] has macro-generated elements, write the combined
+  /// content into a new file in `.dart_tool`, and return the description of
+  /// this file.
+  _MacroGeneratedFile? generatedFile(CompilationUnitElementImpl unitElement) {
+    var sourcePath = unitElement.source.fullName;
+
+    var result = _files[sourcePath];
+    if (result != null) {
+      return result;
+    }
+
+    var sourceFile = _resourceProvider.getFile(sourcePath);
+
+    // TODO(scheglov) Use workspace?
+    Folder? packageRoot;
+    for (var parent in sourceFile.parent2.withAncestors) {
+      if (parent.getChildAssumingFile(file_paths.pubspecYaml).exists) {
+        packageRoot = parent;
+        break;
+      }
+    }
+    if (packageRoot == null) {
+      return null;
+    }
+
+    var pathContext = _resourceProvider.pathContext;
+    var relativePath = pathContext.relative(
+      sourcePath,
+      from: packageRoot.path,
+    );
+    var generatedPath = pathContext.join(
+        packageRoot.path, '.dart_tool', 'analyzer', 'macro', relativePath);
+
+    var generatedContent = unitElement.macroGeneratedContent;
+    if (generatedContent == null) {
+      return null;
+    }
+
+    try {
+      _resourceProvider.getFile(generatedPath)
+        ..parent2.create()
+        ..writeAsStringSync(generatedContent);
+    } on FileSystemException {
+      return null;
+    }
+
+    return _files[sourcePath] = _MacroGeneratedFile(
+      generatedPath,
+      generatedContent,
+    );
+  }
+}
+
+class _MacroGeneratedFile {
+  final String path;
+  final String content;
+  final LineInfo lineInfo;
+
+  _MacroGeneratedFile(this.path, this.content)
+      : lineInfo = LineInfo.fromContent(content);
+}
diff --git a/pkg/analysis_server/test/analysis/notification_navigation_test.dart b/pkg/analysis_server/test/analysis/notification_navigation_test.dart
index bdd6ad3..8a8f918 100644
--- a/pkg/analysis_server/test/analysis/notification_navigation_test.dart
+++ b/pkg/analysis_server/test/analysis/notification_navigation_test.dart
@@ -775,6 +775,64 @@
     assertHasTargetString('my.lib');
   }
 
+  Future<void> test_macro_simpleIdentifier_getter() async {
+    // TODO(scheglov) Use PubPackageResolutionTest?
+    newFile('$projectPath/pubspec.yaml', content: '');
+
+    newFile('$projectPath/bin/macro_annotations.dart', content: r'''
+library analyzer.macro.annotations;
+const observable = 0;
+''');
+
+    addTestFile(r'''
+import 'macro_annotations.dart';
+
+class A {
+  @observable
+  int _foo = 0;
+}
+
+void f(A a) {
+  a.foo;
+}
+''');
+
+    await prepareNavigation();
+    assertHasRegionString('foo;', 3);
+
+    var generatedFile = getFile(
+      '/project/.dart_tool/analyzer/macro/bin/test.dart',
+    );
+
+    var generatedContent = generatedFile.readAsStringSync();
+    // TODO(scheglov) Improve macro to be more formatted.
+    expect(generatedContent, r'''
+import 'macro_annotations.dart';
+
+class A {
+  @observable
+  int _foo = 0;
+
+int get foo => _foo;
+
+set foo(int val) {
+  print('Setting foo to ${val}');
+  _foo = val;
+}
+}
+
+void f(A a) {
+  a.foo;
+}
+''');
+
+    assertHasFileTarget(
+      generatedFile.path,
+      generatedContent.indexOf('foo =>'),
+      'foo'.length,
+    );
+  }
+
   Future<void> test_multiplyDefinedElement() async {
     newFile('$projectPath/bin/libA.dart', content: 'library A; int TEST = 1;');
     newFile('$projectPath/bin/libB.dart', content: 'library B; int TEST = 2;');
diff --git a/pkg/analyzer_plugin/lib/utilities/analyzer_converter.dart b/pkg/analyzer_plugin/lib/utilities/analyzer_converter.dart
index 575cd5b..10640fc 100644
--- a/pkg/analyzer_plugin/lib/utilities/analyzer_converter.dart
+++ b/pkg/analyzer_plugin/lib/utilities/analyzer_converter.dart
@@ -17,6 +17,13 @@
 ///
 /// Clients may not extend, implement or mix-in this class.
 class AnalyzerConverter {
+  /// Optional provider for [analyzer.Element] location, used for example to
+  /// override the location of macro-generated elements.
+  final ElementLocationProvider? _locationProvider;
+
+  AnalyzerConverter({ElementLocationProvider? locationProvider})
+      : _locationProvider = locationProvider;
+
   /// Convert the analysis [error] from the 'analyzer' package to an analysis
   /// error defined by the plugin API. If a [lineInfo] is provided then the
   /// error's location will have a start line and start column. If a [severity]
@@ -199,6 +206,12 @@
     if (element == null || element.source == null) {
       return null;
     }
+
+    var result = _locationProvider?.forElement(element);
+    if (result != null) {
+      return result;
+    }
+
     offset ??= element.nameOffset;
     length ??= element.nameLength;
     if (element is analyzer.CompilationUnitElement ||
@@ -412,3 +425,9 @@
         endLine: endLine, endColumn: endColumn);
   }
 }
+
+abstract class ElementLocationProvider {
+  /// Return the location of [element] that this provider wants to override,
+  /// or `null` if the default location should be used.
+  plugin.Location? forElement(analyzer.Element element);
+}
diff --git a/pkg/analyzer_plugin/lib/utilities/navigation/navigation_dart.dart b/pkg/analyzer_plugin/lib/utilities/navigation/navigation_dart.dart
index e0a5372..557e7c1 100644
--- a/pkg/analyzer_plugin/lib/utilities/navigation/navigation_dart.dart
+++ b/pkg/analyzer_plugin/lib/utilities/navigation/navigation_dart.dart
@@ -16,12 +16,15 @@
 import 'package:analyzer_plugin/utilities/navigation/navigation.dart';
 
 NavigationCollector computeDartNavigation(
-    ResourceProvider resourceProvider,
-    NavigationCollector collector,
-    CompilationUnit unit,
-    int? offset,
-    int? length) {
-  var dartCollector = _DartNavigationCollector(collector, offset, length);
+  ResourceProvider resourceProvider,
+  NavigationCollector collector,
+  CompilationUnit unit,
+  int? offset,
+  int? length, {
+  AnalyzerConverter? analyzerConverter,
+}) {
+  var dartCollector = _DartNavigationCollector(
+      collector, offset, length, analyzerConverter ?? AnalyzerConverter());
   var visitor = _DartNavigationComputerVisitor(resourceProvider, dartCollector);
   if (offset == null || length == null) {
     unit.accept(visitor);
@@ -66,9 +69,14 @@
   final NavigationCollector collector;
   final int? requestedOffset;
   final int? requestedLength;
+  final AnalyzerConverter _analyzerConverter;
 
   _DartNavigationCollector(
-      this.collector, this.requestedOffset, this.requestedLength);
+    this.collector,
+    this.requestedOffset,
+    this.requestedLength,
+    this._analyzerConverter,
+  );
 
   void _addRegion(int offset, int length, Element? element) {
     element = element?.nonSynthetic;
@@ -85,15 +93,14 @@
     if (!_isWithinRequestedRange(offset, length)) {
       return;
     }
-    var converter = AnalyzerConverter();
-    var kind = converter.convertElementKind(element.kind);
-    var location = converter.locationFromElement(element);
+    var kind = _analyzerConverter.convertElementKind(element.kind);
+    var location = _analyzerConverter.locationFromElement(element);
     if (location == null) {
       return;
     }
 
     var codeLocation = collector.collectCodeLocations
-        ? _getCodeLocation(element, location, converter)
+        ? _getCodeLocation(element, location)
         : null;
 
     collector.addRegion(offset, length, kind, location,
@@ -122,8 +129,8 @@
   }
 
   /// Get the location of the code (excluding leading doc comments) for this element.
-  protocol.Location? _getCodeLocation(Element element,
-      protocol.Location location, AnalyzerConverter converter) {
+  protocol.Location? _getCodeLocation(
+      Element element, protocol.Location location) {
     var codeElement = element;
     // For synthetic getters created for fields, we need to access the associated
     // variable to get the codeOffset/codeLength.
@@ -162,7 +169,7 @@
       codeOffset = offsetAfterDocs;
     }
 
-    return converter.locationFromElement(element,
+    return _analyzerConverter.locationFromElement(element,
         offset: codeOffset, length: codeLength);
   }
 
diff --git a/pkg/dev_compiler/lib/src/kernel/compiler.dart b/pkg/dev_compiler/lib/src/kernel/compiler.dart
index 57a1c38a..4f3d3c5 100644
--- a/pkg/dev_compiler/lib/src/kernel/compiler.dart
+++ b/pkg/dev_compiler/lib/src/kernel/compiler.dart
@@ -1127,13 +1127,29 @@
       var mixinName =
           getLocalClassName(superclass) + '_' + getLocalClassName(mixinClass);
       var mixinId = _emitTemporaryId(mixinName + '\$');
-      // Collect all forwarding stubs from anonymous mixins classes. These will
-      // contain covariant parameter checks that need to be applied.
-      var forwardingMethodStubs = [
+      // Collect all forwarding stub setters from anonymous mixins classes.
+      // These will contain covariant parameter checks that need to be applied.
+      var savedClassProperties = _classProperties;
+      _classProperties =
+          ClassPropertyModel.build(_types, _extensionTypes, _virtualFields, m);
+
+      var forwardingSetters = {
         for (var procedure in m.procedures)
           if (procedure.isForwardingStub && !procedure.isAbstract)
-            _emitMethodDeclaration(procedure)
-      ];
+            procedure.name.text: procedure
+      };
+
+      var forwardingMethodStubs = <js_ast.Method>[];
+      for (var s in forwardingSetters.values) {
+        forwardingMethodStubs.add(_emitMethodDeclaration(s));
+        // If there are getters matching the setters somewhere above in the
+        // class hierarchy we must also generate a forwarding getter due to the
+        // representation used in the compiled JavaScript.
+        var getterWrapper = _emitSuperAccessorWrapper(s, {}, forwardingSetters);
+        if (getterWrapper != null) forwardingMethodStubs.add(getterWrapper);
+      }
+
+      _classProperties = savedClassProperties;
 
       // Bind the mixin class to a name to workaround a V8 bug with es6 classes
       // and anonymous function names.
diff --git a/runtime/observatory/lib/src/elements/heap_snapshot.dart b/runtime/observatory/lib/src/elements/heap_snapshot.dart
index 1ddb59e..e83ec9e 100644
--- a/runtime/observatory/lib/src/elements/heap_snapshot.dart
+++ b/runtime/observatory/lib/src/elements/heap_snapshot.dart
@@ -1197,7 +1197,7 @@
 
   static Iterable _getChildrenSuccessor(nodeDynamic) {
     SnapshotObject node = nodeDynamic;
-    return node.successors.take(kMaxChildren).toList();
+    return node.successors.toList();
   }
 
   static Iterable _getChildrenPredecessor(nodeDynamic) {
diff --git a/runtime/observatory/lib/src/service/object.dart b/runtime/observatory/lib/src/service/object.dart
index 7d83c71..df4a4cd 100644
--- a/runtime/observatory/lib/src/service/object.dart
+++ b/runtime/observatory/lib/src/service/object.dart
@@ -676,8 +676,6 @@
   String targetCPU = 'unknown';
   String embedder = 'unknown';
   int architectureBits = 0;
-  bool assertsEnabled = false;
-  bool typeChecksEnabled = false;
   int nativeZoneMemoryUsage = 0;
   int pid = 0;
   int mallocUsed = 0;
@@ -1050,8 +1048,6 @@
     maxRSS = map['_maxRSS'];
     currentRSS = map['_currentRSS'];
     profileVM = map['_profilerMode'] == 'VM';
-    assertsEnabled = map['_assertsEnabled'];
-    typeChecksEnabled = map['_typeChecksEnabled'];
     _removeDeadIsolates([
       ...map['isolates'],
       ...map['systemIsolates'],
@@ -1550,7 +1546,7 @@
       // There are sometimes isolate refs in ServiceEvents.
       return vm.getFromMap(map);
     }
-    String mapId = map['id'];
+    String? mapId = map['id'];
     var obj = (mapId != null) ? _cache[mapId] : null;
     if (obj != null) {
       obj.updateFromServiceMap(map);
@@ -1559,7 +1555,7 @@
     // Build the object from the map directly.
     obj = ServiceObject._fromMap(this, map);
     if ((obj != null) && obj.canCache) {
-      _cache[mapId] = obj;
+      _cache[mapId!] = obj;
     }
     return obj;
   }
diff --git a/runtime/observatory/tests/service/object_graph_isolate_group_test.dart b/runtime/observatory/tests/service/object_graph_isolate_group_test.dart
new file mode 100644
index 0000000..d325f75
--- /dev/null
+++ b/runtime/observatory/tests/service/object_graph_isolate_group_test.dart
@@ -0,0 +1,70 @@
+// Copyright (c) 2021, 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.
+
+// VMOptions=--enable_isolate_groups
+
+import 'dart:isolate' as isolate;
+import 'package:observatory/object_graph.dart';
+import 'package:observatory/service_io.dart';
+import 'package:test/test.dart';
+import 'test_helper.dart';
+
+// Make sure these fields are not removed by the tree shaker.
+@pragma("vm:entry-point")
+dynamic bigGlobal;
+
+child(message) {
+  var bigString = message[0] as String;
+  var replyPort = message[1] as isolate.SendPort;
+  bigGlobal = bigString;
+  replyPort.send(null);
+  new isolate.RawReceivePort(); // Keep child alive.
+}
+
+void script() {
+  var bigString = "x" * (1 << 20);
+  var port;
+  for (var i = 0; i < 2; i++) {
+    port = new isolate.RawReceivePort((_) => port.close());
+    isolate.Isolate.spawn(child, [bigString, port.sendPort]);
+  }
+  bigGlobal = bigString;
+  print("Ready");
+}
+
+var tests = <IsolateTest>[
+  (Isolate isolate) async {
+    var graph = await isolate.fetchHeapSnapshot().done;
+
+    // We are assuming the big string is the largest in the heap, and that it
+    // was shared/pass-by-pointer.
+    List<SnapshotObject> strings = graph.objects
+        .where((SnapshotObject obj) => obj.klass.name == "_OneByteString")
+        .toList();
+    strings.sort((u, v) => v.shallowSize - u.shallowSize);
+    SnapshotObject bigString = strings[0];
+    print("bigString: $bigString");
+    expect(bigString.shallowSize, greaterThanOrEqualTo(1 << 20));
+
+    int matchingPredecessors = 0;
+    for (SnapshotObject predecessor in bigString.predecessors) {
+      print("predecessor $predecessor ${predecessor.label}");
+      if (predecessor.label.contains("bigGlobal") &&
+          predecessor.klass.name.contains("Isolate")) {
+        matchingPredecessors++;
+      }
+    }
+
+    for (SnapshotObject object in graph.objects) {
+      if (object.klass.name.contains("Isolate")) {
+        print("$object / ${object.description}");
+      }
+    }
+
+    // Parent and two children. Seeing all 3 means we visited all the field tables.
+    expect(matchingPredecessors, equals(3));
+  }
+];
+
+main(args) => runIsolateTests(args, tests, testeeBefore: script);
diff --git a/runtime/observatory/tests/service/object_graph_vm_test.dart b/runtime/observatory/tests/service/object_graph_vm_test.dart
index a4bf3fe..8a8e7ec 100644
--- a/runtime/observatory/tests/service/object_graph_vm_test.dart
+++ b/runtime/observatory/tests/service/object_graph_vm_test.dart
@@ -90,8 +90,8 @@
       int internalSum = 0;
       int externalSum = 0;
       for (SnapshotObject instance in klass.instances) {
-        if (instance == graph.root) {
-          // The root may have 0 self size.
+        if (instance == graph.root || instance.klass.name.contains("Isolate")) {
+          // The root and fake root subdivisions have 0 self size.
           expect(instance.internalSize, greaterThanOrEqualTo(0));
           expect(instance.externalSize, greaterThanOrEqualTo(0));
           expect(instance.shallowSize, greaterThanOrEqualTo(0));
@@ -122,8 +122,8 @@
     int internalSum = 0;
     int externalSum = 0;
     for (SnapshotObject instance in graph.objects) {
-      if (instance == graph.root) {
-        // The root may have 0 self size.
+      if (instance == graph.root || instance.klass.name.contains("Isolate")) {
+        // The root and fake root subdivisions have 0 self size.
         expect(instance.internalSize, greaterThanOrEqualTo(0));
         expect(instance.externalSize, greaterThanOrEqualTo(0));
         expect(instance.shallowSize, greaterThanOrEqualTo(0));
diff --git a/runtime/observatory_2/lib/src/elements/heap_snapshot.dart b/runtime/observatory_2/lib/src/elements/heap_snapshot.dart
index bfe8412..4fde130 100644
--- a/runtime/observatory_2/lib/src/elements/heap_snapshot.dart
+++ b/runtime/observatory_2/lib/src/elements/heap_snapshot.dart
@@ -1196,7 +1196,7 @@
 
   static Iterable _getChildrenSuccessor(nodeDynamic) {
     SnapshotObject node = nodeDynamic;
-    return node.successors.take(kMaxChildren).toList();
+    return node.successors.toList();
   }
 
   static Iterable _getChildrenPredecessor(nodeDynamic) {
diff --git a/runtime/observatory_2/lib/src/service/object.dart b/runtime/observatory_2/lib/src/service/object.dart
index 14d72c2..5e220dc 100644
--- a/runtime/observatory_2/lib/src/service/object.dart
+++ b/runtime/observatory_2/lib/src/service/object.dart
@@ -677,8 +677,6 @@
   String targetCPU;
   String embedder;
   int architectureBits;
-  bool assertsEnabled = false;
-  bool typeChecksEnabled = false;
   int nativeZoneMemoryUsage = 0;
   int pid = 0;
   int mallocUsed = 0;
@@ -1053,8 +1051,6 @@
     maxRSS = map['_maxRSS'];
     currentRSS = map['_currentRSS'];
     profileVM = map['_profilerMode'] == 'VM';
-    assertsEnabled = map['_assertsEnabled'];
-    typeChecksEnabled = map['_typeChecksEnabled'];
     _removeDeadIsolates([
       ...map['isolates'],
       ...map['systemIsolates'],
diff --git a/runtime/observatory_2/tests/service_2/object_graph_isolate_group_test.dart b/runtime/observatory_2/tests/service_2/object_graph_isolate_group_test.dart
new file mode 100644
index 0000000..b9ddf82
--- /dev/null
+++ b/runtime/observatory_2/tests/service_2/object_graph_isolate_group_test.dart
@@ -0,0 +1,72 @@
+// Copyright (c) 2021, 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.
+
+// VMOptions=--enable_isolate_groups
+
+// @dart = 2.7
+
+import 'dart:isolate' as isolate;
+import 'package:observatory_2/object_graph.dart';
+import 'package:observatory_2/service_io.dart';
+import 'package:test/test.dart';
+import 'test_helper.dart';
+
+// Make sure these fields are not removed by the tree shaker.
+@pragma("vm:entry-point")
+dynamic bigGlobal;
+
+child(message) {
+  var bigString = message[0] as String;
+  var replyPort = message[1] as isolate.SendPort;
+  bigGlobal = bigString;
+  replyPort.send(null);
+  new isolate.RawReceivePort(); // Keep child alive.
+}
+
+void script() {
+  var bigString = "x" * (1 << 20);
+  var port;
+  for (var i = 0; i < 2; i++) {
+    port = new isolate.RawReceivePort((_) => port.close());
+    isolate.Isolate.spawn(child, [bigString, port.sendPort]);
+  }
+  bigGlobal = bigString;
+  print("Ready");
+}
+
+var tests = <IsolateTest>[
+  (Isolate isolate) async {
+    var graph = await isolate.fetchHeapSnapshot().done;
+
+    // We are assuming the big string is the largest in the heap, and that it
+    // was shared/pass-by-pointer.
+    List<SnapshotObject> strings = graph.objects
+        .where((SnapshotObject obj) => obj.klass.name == "_OneByteString")
+        .toList();
+    strings.sort((u, v) => v.shallowSize - u.shallowSize);
+    SnapshotObject bigString = strings[0];
+    print("bigString: $bigString");
+    expect(bigString.shallowSize, greaterThanOrEqualTo(1 << 20));
+
+    int matchingPredecessors = 0;
+    for (SnapshotObject predecessor in bigString.predecessors) {
+      print("predecessor $predecessor ${predecessor.label}");
+      if (predecessor.label.contains("bigGlobal") &&
+          predecessor.klass.name.contains("Isolate")) {
+        matchingPredecessors++;
+      }
+    }
+
+    for (SnapshotObject object in graph.objects) {
+      if (object.klass.name.contains("Isolate")) {
+        print("$object / ${object.description}");
+      }
+    }
+
+    // Parent and two children. Seeing all 3 means we visited all the field tables.
+    expect(matchingPredecessors, equals(3));
+  }
+];
+
+main(args) => runIsolateTests(args, tests, testeeBefore: script);
diff --git a/runtime/observatory_2/tests/service_2/object_graph_vm_test.dart b/runtime/observatory_2/tests/service_2/object_graph_vm_test.dart
index d1cb655..81be62e 100644
--- a/runtime/observatory_2/tests/service_2/object_graph_vm_test.dart
+++ b/runtime/observatory_2/tests/service_2/object_graph_vm_test.dart
@@ -90,8 +90,8 @@
       int internalSum = 0;
       int externalSum = 0;
       for (SnapshotObject instance in klass.instances) {
-        if (instance == graph.root) {
-          // The root may have 0 self size.
+        if (instance == graph.root || instance.klass.name.contains("Isolate")) {
+          // The root and fake root subdivisions have 0 self size.
           expect(instance.internalSize, greaterThanOrEqualTo(0));
           expect(instance.externalSize, greaterThanOrEqualTo(0));
           expect(instance.shallowSize, greaterThanOrEqualTo(0));
@@ -122,8 +122,8 @@
     int internalSum = 0;
     int externalSum = 0;
     for (SnapshotObject instance in graph.objects) {
-      if (instance == graph.root) {
-        // The root may have 0 self size.
+      if (instance == graph.root || instance.klass.name.contains("Isolate")) {
+        // The root and fake root subdivisions have 0 self size.
         expect(instance.internalSize, greaterThanOrEqualTo(0));
         expect(instance.externalSize, greaterThanOrEqualTo(0));
         expect(instance.shallowSize, greaterThanOrEqualTo(0));
diff --git a/runtime/vm/isolate.cc b/runtime/vm/isolate.cc
index 35236ef..b1967f0 100644
--- a/runtime/vm/isolate.cc
+++ b/runtime/vm/isolate.cc
@@ -2628,16 +2628,18 @@
                                   ValidationPolicy validate_frames) {
   ASSERT(visitor != nullptr);
 
+  // Visit objects in the field table.
+  // N.B.: The heap snapshot writer requires visiting the field table first, so
+  // that the pointer visitation order aligns with order of field name metadata.
+  if (!visitor->trace_values_through_fields()) {
+    field_table()->VisitObjectPointers(visitor);
+  }
+
   // Visit objects in the isolate object store.
   if (isolate_object_store() != nullptr) {
     isolate_object_store()->VisitObjectPointers(visitor);
   }
 
-  // Visit objects in the field table.
-  if (!visitor->trace_values_through_fields()) {
-    field_table()->VisitObjectPointers(visitor);
-  }
-
   visitor->clear_gc_root_type();
   // Visit the objects directly referenced from the isolate structure.
   visitor->VisitPointer(reinterpret_cast<ObjectPtr*>(&current_tag_));
@@ -2810,14 +2812,19 @@
 
 void IsolateGroup::VisitObjectPointers(ObjectPointerVisitor* visitor,
                                        ValidationPolicy validate_frames) {
+  VisitSharedPointers(visitor);
+  for (Isolate* isolate : isolates_) {
+    isolate->VisitObjectPointers(visitor, validate_frames);
+  }
+  VisitStackPointers(visitor, validate_frames);
+}
+
+void IsolateGroup::VisitSharedPointers(ObjectPointerVisitor* visitor) {
   // if class table is shared, it's stored on isolate group
   if (class_table() != nullptr) {
     // Visit objects in the class table.
     class_table()->VisitObjectPointers(visitor);
   }
-  for (Isolate* isolate : isolates_) {
-    isolate->VisitObjectPointers(visitor, validate_frames);
-  }
   api_state()->VisitObjectPointersUnlocked(visitor);
   // Visit objects in the object store.
   if (object_store() != nullptr) {
@@ -2825,7 +2832,6 @@
   }
   visitor->VisitPointer(reinterpret_cast<ObjectPtr*>(&saved_unlinked_calls_));
   initial_field_table()->VisitObjectPointers(visitor);
-  VisitStackPointers(visitor, validate_frames);
 
   // Visit the boxed_field_list_.
   // 'boxed_field_list_' access via mutator and background compilation threads
diff --git a/runtime/vm/isolate.h b/runtime/vm/isolate.h
index d7c8e5d..8b4f88b 100644
--- a/runtime/vm/isolate.h
+++ b/runtime/vm/isolate.h
@@ -713,6 +713,7 @@
   // running, and the visitor must not allocate.
   void VisitObjectPointers(ObjectPointerVisitor* visitor,
                            ValidationPolicy validate_frames);
+  void VisitSharedPointers(ObjectPointerVisitor* visitor);
   void VisitStackPointers(ObjectPointerVisitor* visitor,
                           ValidationPolicy validate_frames);
   void VisitObjectIdRingPointers(ObjectPointerVisitor* visitor);
diff --git a/runtime/vm/object_graph.cc b/runtime/vm/object_graph.cc
index 40e55a83..749c0f3 100644
--- a/runtime/vm/object_graph.cc
+++ b/runtime/vm/object_graph.cc
@@ -812,8 +812,6 @@
         HandleVisitor(Thread::Current()),
         writer_(writer) {}
 
-  virtual bool trace_values_through_fields() const { return true; }
-
   void VisitObject(ObjectPtr obj) {
     if (obj->IsPseudoObject()) return;
 
@@ -865,6 +863,13 @@
 
 static const intptr_t kMaxStringElements = 128;
 
+enum ExtraCids {
+  kRootExtraCid = 1,  // 1-origin
+  kIsolateExtraCid = 2,
+
+  kNumExtraCids = 2,
+};
+
 class Pass2Visitor : public ObjectVisitor,
                      public ObjectPointerVisitor,
                      public HandleVisitor {
@@ -876,13 +881,11 @@
         isolate_group_(thread()->isolate_group()),
         writer_(writer) {}
 
-  virtual bool trace_values_through_fields() const { return true; }
-
   void VisitObject(ObjectPtr obj) {
     if (obj->IsPseudoObject()) return;
 
     intptr_t cid = obj->GetClassId();
-    writer_->WriteUnsigned(cid);
+    writer_->WriteUnsigned(cid + kNumExtraCids);
     writer_->WriteUnsigned(discount_sizes_ ? 0 : obj->untag()->HeapSize());
 
     if (cid == kNullCid) {
@@ -891,6 +894,16 @@
       writer_->WriteUnsigned(kBoolData);
       writer_->WriteUnsigned(
           static_cast<uintptr_t>(static_cast<BoolPtr>(obj)->untag()->value_));
+    } else if (cid == kSentinelCid) {
+      if (obj == Object::sentinel().ptr()) {
+        writer_->WriteUnsigned(kNameData);
+        writer_->WriteUtf8("uninitialized");
+      } else if (obj == Object::transition_sentinel().ptr()) {
+        writer_->WriteUnsigned(kNameData);
+        writer_->WriteUtf8("initializing");
+      } else {
+        writer_->WriteUnsigned(kNoData);
+      }
     } else if (cid == kSmiCid) {
       UNREACHABLE();
     } else if (cid == kMintCid) {
@@ -1073,6 +1086,16 @@
     }
   }
 
+  void CountExtraRefs(intptr_t count) {
+    ASSERT(!writing_);
+    counted_ += count;
+  }
+  void WriteExtraRef(intptr_t oid) {
+    ASSERT(writing_);
+    written_++;
+    writer_->WriteUnsigned(oid);
+  }
+
  private:
   IsolateGroup* isolate_group_;
   HeapSnapshotWriter* const writer_;
@@ -1105,6 +1128,36 @@
   DISALLOW_COPY_AND_ASSIGN(Pass3Visitor);
 };
 
+class CollectStaticFieldNames : public ObjectVisitor {
+ public:
+  CollectStaticFieldNames(intptr_t field_table_size,
+                          const char** field_table_names)
+      : ObjectVisitor(),
+        field_table_size_(field_table_size),
+        field_table_names_(field_table_names),
+        field_(Field::Handle()) {}
+
+  void VisitObject(ObjectPtr obj) {
+    if (obj->IsField()) {
+      field_ ^= obj;
+      if (field_.is_static()) {
+        intptr_t id = field_.field_id();
+        if (id > 0) {
+          ASSERT(id < field_table_size_);
+          field_table_names_[id] = field_.UserVisibleNameCString();
+        }
+      }
+    }
+  }
+
+ private:
+  intptr_t field_table_size_;
+  const char** field_table_names_;
+  Field& field_;
+
+  DISALLOW_COPY_AND_ASSIGN(CollectStaticFieldNames);
+};
+
 void HeapSnapshotWriter::Write() {
   HeapIterationScope iteration(thread());
 
@@ -1134,7 +1187,46 @@
     Array& fields = Array::Handle();
     Field& field = Field::Handle();
 
-    WriteUnsigned(class_count_);
+    intptr_t field_table_size = isolate()->field_table()->NumFieldIds();
+    const char** field_table_names =
+        thread()->zone()->Alloc<const char*>(field_table_size);
+    for (intptr_t i = 0; i < field_table_size; i++) {
+      field_table_names[i] = nullptr;
+    }
+    {
+      CollectStaticFieldNames visitor(field_table_size, field_table_names);
+      iteration.IterateObjects(&visitor);
+    }
+
+    WriteUnsigned(class_count_ + kNumExtraCids);
+    {
+      ASSERT(kRootExtraCid == 1);
+      WriteUnsigned(0);   // Flags
+      WriteUtf8("Root");  // Name
+      WriteUtf8("");      // Library name
+      WriteUtf8("");      // Library uri
+      WriteUtf8("");      // Reserved
+      WriteUnsigned(0);   // Field count
+    }
+    {
+      ASSERT(kIsolateExtraCid == 2);
+      WriteUnsigned(0);      // Flags
+      WriteUtf8("Isolate");  // Name
+      WriteUtf8("");         // Library name
+      WriteUtf8("");         // Library uri
+      WriteUtf8("");         // Reserved
+
+      WriteUnsigned(field_table_size);  // Field count
+      for (intptr_t i = 0; i < field_table_size; i++) {
+        intptr_t flags = 1;  // Strong.
+        WriteUnsigned(flags);
+        WriteUnsigned(i);  // Index.
+        const char* name = field_table_names[i];
+        WriteUtf8(name == nullptr ? "" : name);
+        WriteUtf8("");  // Reserved
+      }
+    }
+    ASSERT(kNumExtraCids == 2);
     for (intptr_t cid = 1; cid <= class_count_; cid++) {
       if (!class_table->HasValidClassAt(cid)) {
         WriteUnsigned(0);  // Flags
@@ -1227,13 +1319,22 @@
 
   SetupCountingPages();
 
+  intptr_t num_isolates = 0;
   {
     Pass1Visitor visitor(this);
 
-    // Root "object".
+    // Root "objects".
     ++object_count_;
-    isolate()->VisitObjectPointers(&visitor,
-                                   ValidationPolicy::kDontValidateFrames);
+    isolate_group()->VisitSharedPointers(&visitor);
+    isolate_group()->ForEachIsolate(
+        [&](Isolate* isolate) {
+          ++object_count_;
+          isolate->VisitObjectPointers(&visitor,
+                                       ValidationPolicy::kDontValidateFrames);
+          ++num_isolates;
+        },
+        /*at_safepoint=*/true);
+    CountReferences(num_isolates);
 
     // Heap objects.
     iteration.IterateVMIsolateObjects(&visitor);
@@ -1249,16 +1350,35 @@
     WriteUnsigned(reference_count_);
     WriteUnsigned(object_count_);
 
-    // Root "object".
-    WriteUnsigned(0);  // cid
-    WriteUnsigned(0);  // shallowSize
-    WriteUnsigned(kNoData);
-    visitor.DoCount();
-    isolate()->VisitObjectPointers(&visitor,
-                                   ValidationPolicy::kDontValidateFrames);
-    visitor.DoWrite();
-    isolate()->VisitObjectPointers(&visitor,
-                                   ValidationPolicy::kDontValidateFrames);
+    // Root "objects".
+    {
+      WriteUnsigned(kRootExtraCid);
+      WriteUnsigned(0);  // shallowSize
+      WriteUnsigned(kNoData);
+      visitor.DoCount();
+      isolate_group()->VisitSharedPointers(&visitor);
+      visitor.CountExtraRefs(num_isolates);
+      visitor.DoWrite();
+      isolate_group()->VisitSharedPointers(&visitor);
+      for (intptr_t i = 0; i < num_isolates; i++) {
+        visitor.WriteExtraRef(i + 2);  // 0 = sentinel, 1 = root, 2+ = isolates
+      }
+    }
+    isolate_group()->ForEachIsolate(
+        [&](Isolate* isolate) {
+          WriteUnsigned(kIsolateExtraCid);
+          WriteUnsigned(0);  // shallowSize
+          WriteUnsigned(kNameData);
+          WriteUtf8(
+              OS::SCreate(thread()->zone(), "%" Pd64, isolate->main_port()));
+          visitor.DoCount();
+          isolate->VisitObjectPointers(&visitor,
+                                       ValidationPolicy::kDontValidateFrames);
+          visitor.DoWrite();
+          isolate->VisitObjectPointers(&visitor,
+                                       ValidationPolicy::kDontValidateFrames);
+        },
+        /*at_safepoint=*/true);
 
     // Heap objects.
     visitor.set_discount_sizes(true);
@@ -1277,6 +1397,8 @@
 
     // Handle root object.
     WriteUnsigned(0);
+    isolate_group()->ForEachIsolate([&](Isolate* isolate) { WriteUnsigned(0); },
+                                    /*at_safepoint=*/true);
 
     // Handle visit rest of the objects.
     iteration.IterateVMIsolateObjects(&visitor);
diff --git a/runtime/vm/raw_object_fields.cc b/runtime/vm/raw_object_fields.cc
index 0c64af6..186f510 100644
--- a/runtime/vm/raw_object_fields.cc
+++ b/runtime/vm/raw_object_fields.cc
@@ -156,15 +156,23 @@
   F(String, length_)                                                           \
   F(Array, type_arguments_)                                                    \
   F(Array, length_)                                                            \
+  F(ImmutableArray, type_arguments_)                                           \
+  F(ImmutableArray, length_)                                                   \
   F(GrowableObjectArray, type_arguments_)                                      \
   F(GrowableObjectArray, length_)                                              \
   F(GrowableObjectArray, data_)                                                \
-  F(LinkedHashBase, type_arguments_)                                           \
-  F(LinkedHashBase, index_)                                                    \
-  F(LinkedHashBase, hash_mask_)                                                \
-  F(LinkedHashBase, data_)                                                     \
-  F(LinkedHashBase, used_data_)                                                \
-  F(LinkedHashBase, deleted_keys_)                                             \
+  F(LinkedHashMap, type_arguments_)                                            \
+  F(LinkedHashMap, index_)                                                     \
+  F(LinkedHashMap, hash_mask_)                                                 \
+  F(LinkedHashMap, data_)                                                      \
+  F(LinkedHashMap, used_data_)                                                 \
+  F(LinkedHashSet, deleted_keys_)                                              \
+  F(LinkedHashSet, type_arguments_)                                            \
+  F(LinkedHashSet, index_)                                                     \
+  F(LinkedHashSet, hash_mask_)                                                 \
+  F(LinkedHashSet, data_)                                                      \
+  F(LinkedHashSet, used_data_)                                                 \
+  F(LinkedHashSet, deleted_keys_)                                              \
   F(TypedData, length_)                                                        \
   F(ExternalTypedData, length_)                                                \
   F(ReceivePort, send_port_)                                                   \
diff --git a/tests/language/regress/regress46867_test.dart b/tests/language/regress/regress46867_test.dart
new file mode 100644
index 0000000..7999cc8
--- /dev/null
+++ b/tests/language/regress/regress46867_test.dart
@@ -0,0 +1,32 @@
+// Copyright (c) 2021, 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.
+
+// Regression test for https://github.com/dart-lang/sdk/issues/46867
+
+import 'package:expect/expect.dart';
+
+class Interface {
+  covariant Object x = 'from Interface';
+}
+
+mixin Mixin implements Interface {}
+
+class BaseClass {
+  static var getterCallCount = 0;
+  static var setterCallCount = 0;
+  Object get x => getterCallCount++;
+  set x(Object value) => setterCallCount++;
+}
+
+class SubClass extends BaseClass with Mixin {}
+
+void main() {
+  Expect.equals(0, BaseClass.getterCallCount);
+  SubClass().x;
+  Expect.equals(1, BaseClass.getterCallCount);
+
+  Expect.equals(0, BaseClass.setterCallCount);
+  SubClass().x = 42;
+  Expect.equals(1, BaseClass.setterCallCount);
+}
diff --git a/tests/language_2/regress/regress46867_test.dart b/tests/language_2/regress/regress46867_test.dart
new file mode 100644
index 0000000..750b2234
--- /dev/null
+++ b/tests/language_2/regress/regress46867_test.dart
@@ -0,0 +1,34 @@
+// Copyright (c) 2021, 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
+
+// Regression test for https://github.com/dart-lang/sdk/issues/46867
+
+import 'package:expect/expect.dart';
+
+class Interface {
+  covariant Object x = 'from Interface';
+}
+
+mixin Mixin implements Interface {}
+
+class BaseClass {
+  static var getterCallCount = 0;
+  static var setterCallCount = 0;
+  Object get x => getterCallCount++;
+  set x(Object value) => setterCallCount++;
+}
+
+class SubClass extends BaseClass with Mixin {}
+
+void main() {
+  Expect.equals(0, BaseClass.getterCallCount);
+  SubClass().x;
+  Expect.equals(1, BaseClass.getterCallCount);
+
+  Expect.equals(0, BaseClass.setterCallCount);
+  SubClass().x = 42;
+  Expect.equals(1, BaseClass.setterCallCount);
+}
diff --git a/tools/VERSION b/tools/VERSION
index f5bacd2..6459dd6 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 15
 PATCH 0
-PRERELEASE 23
+PRERELEASE 24
 PRERELEASE_PATCH 0
\ No newline at end of file