Version 2.14.0-214.0.dev

Merge commit 'a7210551ca6aa438c3fd784f68622e762575d041' into 'dev'
diff --git a/pkg/front_end/test/flutter_gallery_leak_tester.dart b/pkg/front_end/test/flutter_gallery_leak_tester.dart
index b3f57e9..1e25c00 100644
--- a/pkg/front_end/test/flutter_gallery_leak_tester.dart
+++ b/pkg/front_end/test/flutter_gallery_leak_tester.dart
@@ -155,16 +155,22 @@
 
   List<helper.Interest> interests = <helper.Interest>[];
   interests.add(new helper.Interest(
-      Uri.parse("package:kernel/ast.dart"), "Library", ["fileUri"]));
+    Uri.parse("package:kernel/ast.dart"),
+    "Library",
+    ["fileUri"],
+  ));
   helper.VMServiceHeapHelperSpecificExactLeakFinder heapHelper =
       new helper.VMServiceHeapHelperSpecificExactLeakFinder(
-          interests,
-          [
-            new helper.Interest(Uri.parse("package:kernel/ast.dart"), "Library",
-                ["fileUri", "_libraryIdString"]),
-          ],
-          true,
-          false);
+    interests: interests,
+    prettyPrints: [
+      new helper.Interest(
+        Uri.parse("package:kernel/ast.dart"),
+        "Library",
+        ["fileUri", "libraryIdForTesting"],
+      ),
+    ],
+    throwOnPossibleLeak: true,
+  );
 
   print("About to run with "
       "quicker = $quicker; "
@@ -197,7 +203,7 @@
   }
 
   await heapHelper.start(processArgs,
-      stdinReceiver: (s) {
+      stdoutReceiver: (s) {
         if (s.startsWith("+")) {
           files.add(s.substring(1));
         } else if (s.startsWith("-")) {
diff --git a/pkg/front_end/test/incremental_suite.dart b/pkg/front_end/test/incremental_suite.dart
index f340d68..dbb1cf7 100644
--- a/pkg/front_end/test/incremental_suite.dart
+++ b/pkg/front_end/test/incremental_suite.dart
@@ -1064,6 +1064,11 @@
       component = null;
       component2 = null;
       component3 = null;
+      // Dummy tree nodes can (currently) leak though the parent pointer.
+      // To avoid that (here) (for leak testing) we'll null them out.
+      for (TreeNode treeNode in dummyTreeNodes) {
+        treeNode.parent = null;
+      }
 
       if (context.breakBetween) {
         debugger();
diff --git a/pkg/front_end/test/spell_checking_list_code.txt b/pkg/front_end/test/spell_checking_list_code.txt
index 2926fdd..d5c6a8e 100644
--- a/pkg/front_end/test/spell_checking_list_code.txt
+++ b/pkg/front_end/test/spell_checking_list_code.txt
@@ -1156,6 +1156,7 @@
 stdout
 sticky
 stmt
+stopgap
 stopped
 storage
 str
diff --git a/pkg/front_end/test/spell_checking_list_tests.txt b/pkg/front_end/test/spell_checking_list_tests.txt
index 5b999be..58478ccb 100644
--- a/pkg/front_end/test/spell_checking_list_tests.txt
+++ b/pkg/front_end/test/spell_checking_list_tests.txt
@@ -220,6 +220,7 @@
 dictionaries
 dictionary
 differences
+differentiate
 differs
 dijkstra
 dijkstras
@@ -393,6 +394,7 @@
 image
 images
 implementor
+implementors
 imprecision
 in1
 in2
@@ -585,6 +587,7 @@
 phrase
 pink
 placement
+planned
 plug
 pointed
 policy
@@ -796,6 +799,7 @@
 uncover
 uncovers
 underline
+undocumented
 unpacked
 unpatched
 unpaused
@@ -826,6 +830,7 @@
 walt
 warmup
 week
+weekly
 wherever
 whiskers
 wins
diff --git a/pkg/front_end/test/vm_service_for_leak_detection.dart b/pkg/front_end/test/vm_service_for_leak_detection.dart
index 19dbbdb..4329036 100644
--- a/pkg/front_end/test/vm_service_for_leak_detection.dart
+++ b/pkg/front_end/test/vm_service_for_leak_detection.dart
@@ -11,33 +11,38 @@
 main(List<String> args) async {
   List<helper.Interest> interests = <helper.Interest>[];
   interests.add(new helper.Interest(
-      Uri.parse(
-          "package:front_end/src/fasta/source/source_library_builder.dart"),
-      "SourceLibraryBuilder",
-      ["fileUri"]));
+    Uri.parse("package:front_end/src/fasta/source/source_library_builder.dart"),
+    "SourceLibraryBuilder",
+    ["fileUri"],
+  ));
   interests.add(new helper.Interest(
-      Uri.parse(
-          "package:front_end/src/fasta/source/source_extension_builder.dart"),
-      "SourceExtensionBuilder",
-      ["_extension"]));
+    Uri.parse(
+        "package:front_end/src/fasta/source/source_extension_builder.dart"),
+    "SourceExtensionBuilder",
+    ["extension"],
+  ));
   interests.add(new helper.Interest(
-      Uri.parse("package:kernel/ast.dart"), "Library", ["fileUri"]));
+    Uri.parse("package:kernel/ast.dart"),
+    "Library",
+    ["fileUri"],
+  ));
+  interests.add(new helper.Interest(
+    Uri.parse("package:kernel/ast.dart"),
+    "Extension",
+    ["name", "fileUri"],
+  ));
   helper.VMServiceHeapHelperSpecificExactLeakFinder heapHelper =
       new helper.VMServiceHeapHelperSpecificExactLeakFinder(
-          interests,
-          [
-            new helper.Interest(
-                Uri.parse(
-                    "package:front_end/src/fasta/source/source_extension_builder.dart"),
-                "SourceExtensionBuilder",
-                ["_extension"]),
-            new helper.Interest(Uri.parse("package:kernel/ast.dart"),
-                "Extension", ["name", "fileUri"]),
-            new helper.Interest(Uri.parse("package:kernel/ast.dart"), "Library",
-                ["fileUri", "_libraryIdString"]),
-          ],
-          true,
-          true);
+    interests: interests,
+    prettyPrints: [
+      new helper.Interest(
+        Uri.parse("package:kernel/ast.dart"),
+        "Library",
+        ["fileUri", "libraryIdForTesting"],
+      ),
+    ],
+    throwOnPossibleLeak: true,
+  );
 
   if (args.length > 0 && args[0] == "--dart2js") {
     heapHelper.start([
@@ -47,6 +52,12 @@
       "--fast",
       "--experimental",
     ]);
+  } else if (args.length > 0 && args[0] == "--weekly") {
+    heapHelper.start([
+      "--enable-asserts",
+      Platform.script.resolve("incremental_suite.dart").toString(),
+      "-DaddDebugBreaks=true",
+    ]);
   } else {
     heapHelper.start([
       "--enable-asserts",
diff --git a/pkg/front_end/test/vm_service_heap_helper.dart b/pkg/front_end/test/vm_service_heap_helper.dart
index 9b44284..d7bf8d0 100644
--- a/pkg/front_end/test/vm_service_heap_helper.dart
+++ b/pkg/front_end/test/vm_service_heap_helper.dart
@@ -4,23 +4,22 @@
 
 // @dart = 2.9
 
-import "dijkstras_sssp_algorithm.dart";
 import "vm_service_helper.dart" as vmService;
 
 class VMServiceHeapHelperSpecificExactLeakFinder
     extends vmService.LaunchingVMServiceHelper {
+  final Set _interestsClassNames = {};
   final Map<Uri, Map<String, List<String>>> _interests =
       new Map<Uri, Map<String, List<String>>>();
   final Map<Uri, Map<String, List<String>>> _prettyPrints =
       new Map<Uri, Map<String, List<String>>>();
   final bool throwOnPossibleLeak;
-  final bool tryToFindShortestPathToLeaks;
 
-  VMServiceHeapHelperSpecificExactLeakFinder(
-      List<Interest> interests,
-      List<Interest> prettyPrints,
-      this.throwOnPossibleLeak,
-      this.tryToFindShortestPathToLeaks) {
+  VMServiceHeapHelperSpecificExactLeakFinder({
+    List<Interest> interests: const [],
+    List<Interest> prettyPrints: const [],
+    this.throwOnPossibleLeak: false,
+  }) {
     if (interests.isEmpty) throw "Empty list of interests given";
     for (Interest interest in interests) {
       Map<String, List<String>> classToFields = _interests[interest.uri];
@@ -28,6 +27,7 @@
         classToFields = Map<String, List<String>>();
         _interests[interest.uri] = classToFields;
       }
+      _interestsClassNames.add(interest.className);
       List<String> fields = classToFields[interest.className];
       if (fields == null) {
         fields = <String>[];
@@ -72,6 +72,11 @@
   @override
   Future<void> run() async {
     _vm = await serviceClient.getVM();
+    if (_vm.isolates.length == 0) {
+      print("Didn't get any isolates. Will wait 1 second and retry.");
+      await Future.delayed(new Duration(seconds: 1));
+      _vm = await serviceClient.getVM();
+    }
     if (_vm.isolates.length != 1) {
       throw "Expected 1 isolate, got ${_vm.isolates.length}";
     }
@@ -83,332 +88,240 @@
 
     _iterationNumber = 1;
     while (true) {
+      if (!shouldDoAnotherIteration(_iterationNumber)) break;
       await waitUntilPaused(_isolateRef.id);
       print("Iteration: #$_iterationNumber");
-      await forceGC(_isolateRef.id);
 
-      vmService.HeapSnapshotGraph heapSnapshotGraph =
-          await vmService.HeapSnapshotGraph.getSnapshot(
-              serviceClient, _isolateRef);
+      Stopwatch stopwatch = new Stopwatch()..start();
 
-      Set<String> duplicatePrints = {};
-      Map<String, List<vmService.HeapSnapshotObject>> groupedByToString = {};
-      _usingUnconvertedGraph(
-          heapSnapshotGraph, duplicatePrints, groupedByToString);
+      vmService.AllocationProfile allocationProfile =
+          await forceGC(_isolateRef.id);
+      print("Forced GC in ${stopwatch.elapsedMilliseconds} ms");
 
-      if (duplicatePrints.isNotEmpty) {
-        print("======================================");
-        print("WARNING: Duplicated pretty prints of objects.");
-        print("This might be a memory leak!");
-        print("");
-        for (String s in duplicatePrints) {
-          int count = groupedByToString[s].length;
-          print("$s ($count)");
-          for (vmService.HeapSnapshotObject duplicate in groupedByToString[s]) {
-            String prettyPrint = _heapObjectPrettyPrint(
-                duplicate, heapSnapshotGraph, _prettyPrints);
-            print(" => ${prettyPrint}");
+      stopwatch.reset();
+      List<Leak> leaks = [];
+      for (vmService.ClassHeapStats member in allocationProfile.members) {
+        if (_interestsClassNames.contains(member.classRef.name)) {
+          vmService.Class c =
+              await serviceClient.getObject(_isolateRef.id, member.classRef.id);
+          String uriString = c.location?.script?.uri;
+          if (uriString == null) continue;
+          Uri uri = Uri.parse(uriString);
+          Map<String, List<String>> uriInterest = _interests[uri];
+          if (uriInterest == null) continue;
+          List<String> fieldsForClass = uriInterest[c.name];
+          if (fieldsForClass == null) continue;
+
+          List<String> fieldsForClassPrettyPrint = fieldsForClass;
+
+          uriInterest = _prettyPrints[uri];
+          if (uriInterest != null) {
+            if (uriInterest[c.name] != null) {
+              fieldsForClassPrettyPrint = uriInterest[c.name];
+            }
           }
-          print("");
-        }
 
-        if (tryToFindShortestPathToLeaks) {
-          _tryToFindShortestPath(heapSnapshotGraph);
-        }
-
-        if (throwOnPossibleLeak) {
-          throw "Possible leak detected.";
+          leaks.addAll(await _findLeaks(_isolateRef, member.classRef,
+              fieldsForClass, fieldsForClassPrettyPrint));
         }
       }
+      if (leaks.isNotEmpty) {
+        for (Leak leak in leaks) {
+          leakDetected(leak.duplicate, leak.count, leak.prettyPrints);
+        }
+        if (throwOnPossibleLeak) {
+          throw "Leaks found";
+        }
+      } else {
+        noLeakDetected();
+      }
+
+      print("Looked for leaks in ${stopwatch.elapsedMilliseconds} ms");
 
       await serviceClient.resume(_isolateRef.id);
       _iterationNumber++;
     }
   }
 
-  void _tryToFindShortestPath(vmService.HeapSnapshotGraph heapSnapshotGraph) {
-    HeapGraph graph = convertHeapGraph(heapSnapshotGraph);
-    Set<String> duplicatePrints = {};
-    Map<String, List<HeapGraphElement>> groupedByToString = {};
-    _usingConvertedGraph(graph, duplicatePrints, groupedByToString);
-
-    print("======================================");
-
-    for (String duplicateString in duplicatePrints) {
-      print("$duplicateString:");
-      List<HeapGraphElement> Function(HeapGraphElement target) dijkstraTarget =
-          dijkstra(graph.elements.first, graph);
-      for (HeapGraphElement duplicate in groupedByToString[duplicateString]) {
-        print("${duplicate} pointed to from:");
-        print(duplicate.getPrettyPrint(_prettyPrints));
-        List<HeapGraphElement> shortestPath = dijkstraTarget(duplicate);
-        for (int i = 0; i < shortestPath.length - 1; i++) {
-          HeapGraphElement thisOne = shortestPath[i];
-          HeapGraphElement nextOne = shortestPath[i + 1];
-          String indexFieldName;
-          if (thisOne is HeapGraphElementActual) {
-            HeapGraphClass c = thisOne.class_;
-            if (c is HeapGraphClassActual) {
-              for (vmService.HeapSnapshotField field in c.origin.fields) {
-                if (thisOne.references[field.index] == nextOne) {
-                  indexFieldName = field.name;
-                }
-              }
-            }
-          }
-          if (indexFieldName == null) {
-            indexFieldName = "no field found; index "
-                "${thisOne.references.indexOf(nextOne)}";
-          }
-          print("  $thisOne -> $nextOne ($indexFieldName)");
-        }
-        print("---------------------------");
-      }
-    }
-  }
-
-  void _usingConvertedGraph(HeapGraph graph, Set<String> duplicatePrints,
-      Map<String, List<HeapGraphElement>> groupedByToString) {
-    Set<String> seenPrints = {};
-    for (HeapGraphClassActual c in graph.classes) {
-      Map<String, List<String>> interests = _interests[c.libraryUri];
-      if (interests != null && interests.isNotEmpty) {
-        List<String> fieldsToUse = interests[c.name];
-        if (fieldsToUse != null && fieldsToUse.isNotEmpty) {
-          for (HeapGraphElement instance in c.getInstances(graph)) {
-            StringBuffer sb = new StringBuffer();
-            sb.writeln("Instance: ${instance}");
-            if (instance is HeapGraphElementActual) {
-              for (String fieldName in fieldsToUse) {
-                String prettyPrinted =
-                    instance.getField(fieldName).getPrettyPrint(_prettyPrints);
-                sb.writeln("  $fieldName: "
-                    "${prettyPrinted}");
-              }
-            }
-            String sbToString = sb.toString();
-            if (!seenPrints.add(sbToString)) {
-              duplicatePrints.add(sbToString);
-            }
-            groupedByToString[sbToString] ??= [];
-            groupedByToString[sbToString].add(instance);
-          }
-        }
-      }
-    }
-  }
-
-  String _heapObjectToString(
-      vmService.HeapSnapshotObject o, vmService.HeapSnapshotClass class_) {
-    if (o == null) return "Sentinel";
-    if (o.data is vmService.HeapSnapshotObjectNoData) {
-      return "Instance of ${class_.name}";
-    }
-    if (o.data is vmService.HeapSnapshotObjectLengthData) {
-      vmService.HeapSnapshotObjectLengthData data = o.data;
-      return "Instance of ${class_.name} length = ${data.length}";
-    }
-    return "Instance of ${class_.name}; data: '${o.data}'";
-  }
-
-  vmService.HeapSnapshotObject _heapObjectGetField(
-      String name,
-      vmService.HeapSnapshotObject o,
-      vmService.HeapSnapshotClass class_,
-      vmService.HeapSnapshotGraph graph) {
-    for (vmService.HeapSnapshotField field in class_.fields) {
-      if (field.name == name) {
-        int index = o.references[field.index];
-        if (index < 0) {
-          // Sentinel object.
-          return null;
-        }
-        return graph.objects[index];
-      }
-    }
-    return null;
-  }
-
-  String _heapObjectPrettyPrint(
-      vmService.HeapSnapshotObject o,
-      vmService.HeapSnapshotGraph graph,
-      Map<Uri, Map<String, List<String>>> prettyPrints) {
-    if (o.classId <= 0) {
-      return "Class sentinel";
-    }
-    vmService.HeapSnapshotClass class_ = o.klass;
-
-    if (class_.name == "_OneByteString") {
-      return '"${o.data}"';
-    }
-
-    if (class_.name == "_SimpleUri") {
-      vmService.HeapSnapshotObject fieldValueObject =
-          _heapObjectGetField("_uri", o, class_, graph);
-      String prettyPrinted =
-          _heapObjectPrettyPrint(fieldValueObject, graph, prettyPrints);
-      return "_SimpleUri[${prettyPrinted}]";
-    }
-
-    if (class_.name == "_Uri") {
-      vmService.HeapSnapshotObject schemeValueObject =
-          _heapObjectGetField("scheme", o, class_, graph);
-      String schemePrettyPrinted =
-          _heapObjectPrettyPrint(schemeValueObject, graph, prettyPrints);
-
-      vmService.HeapSnapshotObject pathValueObject =
-          _heapObjectGetField("path", o, class_, graph);
-      String pathPrettyPrinted =
-          _heapObjectPrettyPrint(pathValueObject, graph, prettyPrints);
-
-      return "_Uri[${schemePrettyPrinted}:${pathPrettyPrinted}]";
-    }
-
-    Map<String, List<String>> classToFields = prettyPrints[class_.libraryUri];
-    if (classToFields != null) {
-      List<String> fields = classToFields[class_.name];
-      if (fields != null) {
-        return "${class_.name}[" +
-            fields.map((field) {
-              vmService.HeapSnapshotObject fieldValueObject =
-                  _heapObjectGetField(field, o, class_, graph);
-              String prettyPrinted = fieldValueObject == null
-                  ? null
-                  : _heapObjectPrettyPrint(
-                      fieldValueObject, graph, prettyPrints);
-              return "$field: ${prettyPrinted}";
-            }).join(", ") +
-            "]";
-      }
-    }
-    return _heapObjectToString(o, class_);
-  }
-
-  void _usingUnconvertedGraph(
-      vmService.HeapSnapshotGraph graph,
-      Set<String> duplicatePrints,
-      Map<String, List<vmService.HeapSnapshotObject>> groupedByToString) {
-    Set<String> seenPrints = {};
-    List<bool> ignoredClasses =
-        new List<bool>.filled(graph.classes.length, false);
-    for (int i = 0; i < graph.objects.length; i++) {
-      vmService.HeapSnapshotObject o = graph.objects[i];
-      if (o.classId <= 0) {
-        // Sentinel.
-        continue;
-      }
-      if (ignoredClasses[o.classId - 1]) {
-        // Class is not interesting.
-        continue;
-      }
-      vmService.HeapSnapshotClass c = o.klass;
-      Map<String, List<String>> interests = _interests[c.libraryUri];
-      if (interests == null || interests.isEmpty) {
-        // Not an object we care about.
-        ignoredClasses[o.classId - 1] = true;
-        continue;
-      }
-
-      List<String> fieldsToUse = interests[c.name];
-      if (fieldsToUse == null || fieldsToUse.isEmpty) {
-        // Not an object we care about.
-        ignoredClasses[o.classId - 1] = true;
-        continue;
-      }
-
-      StringBuffer sb = new StringBuffer();
-      sb.writeln("Instance: ${_heapObjectToString(o, c)}");
-      for (String fieldName in fieldsToUse) {
-        vmService.HeapSnapshotObject fieldValueObject =
-            _heapObjectGetField(fieldName, o, c, graph);
-        String prettyPrinted =
-            _heapObjectPrettyPrint(fieldValueObject, graph, _prettyPrints);
-        sb.writeln("  $fieldName: ${prettyPrinted}");
-      }
-      String sbToString = sb.toString();
-      if (!seenPrints.add(sbToString)) {
-        duplicatePrints.add(sbToString);
-      }
-      groupedByToString[sbToString] ??= [];
-      groupedByToString[sbToString].add(o);
-    }
-  }
-
-  List<HeapGraphElement> Function(HeapGraphElement target) dijkstra(
-      HeapGraphElement source, HeapGraph heapGraph) {
-    Map<HeapGraphElement, int> elementNum = {};
-    Map<HeapGraphElement, GraphNode<HeapGraphElement>> elements = {};
-    elements[heapGraph.elementSentinel] =
-        new GraphNode<HeapGraphElement>(heapGraph.elementSentinel);
-    elementNum[heapGraph.elementSentinel] = elements.length;
-    for (HeapGraphElementActual element in heapGraph.elements) {
-      elements[element] = new GraphNode<HeapGraphElement>(element);
-      elementNum[element] = elements.length;
-    }
-
-    for (HeapGraphElementActual element in heapGraph.elements) {
-      GraphNode<HeapGraphElement> node = elements[element];
-      for (HeapGraphElement out in element.references) {
-        node.addOutgoing(elements[out]);
-      }
-    }
-
-    DijkstrasAlgorithm<HeapGraphElement> result =
-        new DijkstrasAlgorithm<HeapGraphElement>(
-      elements.values,
-      elements[source],
-      (HeapGraphElement a, HeapGraphElement b) {
-        if (identical(a, b)) {
-          throw "Comparing two identical ones was unexpected";
-        }
-        return elementNum[a] - elementNum[b];
-      },
-      (HeapGraphElement a, HeapGraphElement b) {
-        if (identical(a, b)) return 0;
-
-        // Prefer going via actual field.
-        if (a is HeapGraphElementActual) {
-          HeapGraphClass c = a.class_;
-          if (c is HeapGraphClassActual) {
-            for (vmService.HeapSnapshotField field in c.origin.fields) {
-              if (a.references[field.index] == b) {
-                // Via actual field!
-                return 1;
-              }
-            }
-          }
-        }
-
-        // Prefer not to go directly from HeapGraphClassSentinel to Procedure.
-        if (a is HeapGraphElementActual && b is HeapGraphElementActual) {
-          HeapGraphElementActual aa = a;
-          HeapGraphElementActual bb = b;
-          if (aa.class_ is HeapGraphClassSentinel &&
-              bb.class_ is HeapGraphClassActual) {
-            HeapGraphClassActual c = bb.class_;
-            if (c.name == "Procedure") {
-              return 1000;
-            }
-          }
-        }
-
-        // Prefer not to go via sentinel and via "Context".
-        if (b is HeapGraphElementSentinel) return 100;
-        HeapGraphElementActual bb = b;
-        if (bb.class_ is HeapGraphClassSentinel) return 100;
-        HeapGraphClassActual c = bb.class_;
-        if (c.name == "Context") {
-          if (c.libraryUri.toString().isEmpty) return 100;
-        }
-
-        // Not via actual field.
-        return 10;
+  Future<List<Leak>> _findLeaks(
+      vmService.IsolateRef isolateRef,
+      vmService.ClassRef classRef,
+      List<String> fieldsForClass,
+      List<String> fieldsForClassPrettyPrint) async {
+    // Use undocumented (/ private?) method to get all instances of this class.
+    vmService.InstanceRef instancesAsList = await serviceClient.callMethod(
+      "_getInstancesAsArray",
+      isolateId: isolateRef.id,
+      args: {
+        "objectId": classRef.id,
+        "includeSubclasses": false,
+        "includeImplementors": false,
       },
     );
 
-    return (HeapGraphElement target) {
-      return result.getPathFromTarget(elements[source], elements[target]);
-    };
+    // Create dart code that `toString`s a class instance according to
+    // the fields given as wanting printed. Both for finding duplicates (1) and
+    // for pretty printing entries (for instance to be able to differentiate
+    // them) (2).
+
+    // 1:
+    String fieldsToStringCode = classRef.name +
+        "[" +
+        fieldsForClass
+            .map((value) => "$value: \"\${element.$value}\"")
+            .join(", ") +
+        "]";
+    // 2:
+    String fieldsToStringPrettyPrintCode = classRef.name +
+        "[" +
+        fieldsForClassPrettyPrint
+            .map((value) => "$value: \"\${element.$value}\"")
+            .join(", ") +
+        "]";
+
+    // Expression evaluation to find duplicates: Put all entries into a map
+    // indexed by the `toString` code created above, mapping to list of that
+    // data.
+    vmService.InstanceRef mappedData = await serviceClient.evaluate(
+      isolateRef.id,
+      instancesAsList.id,
+      """
+          this
+              .fold({}, (dynamic index, dynamic element) {
+                String key = '$fieldsToStringCode';
+                var list = index[key] ??= [];
+                list.add(element);
+                return index;
+              })
+        """,
+    );
+    // Expression calculation to find if any of the lists created as values
+    // above contains more than one entry (i.e. there's a duplicate).
+    vmService.InstanceRef duplicatesLengthRef = await serviceClient.evaluate(
+      isolateRef.id,
+      mappedData.id,
+      """
+          this
+              .values
+              .where((dynamic element) => (element.length > 1) as bool)
+              .length
+        """,
+    );
+    vmService.Instance duplicatesLength =
+        await serviceClient.getObject(isolateRef.id, duplicatesLengthRef.id);
+    int duplicates = int.tryParse(duplicatesLength.valueAsString);
+    if (duplicates != 0) {
+      // There are duplicates. Expression calculation to encode the duplication
+      // data (both the string that caused it to be a duplicate and the pretty
+      // prints) as a string (to be able to easily get a hold of it here).
+      // It filters out the duplicates and then encodes it with a simple scheme
+      // of length-prefixed strings (and with everything separated by colons),
+      // e.g. encode the string "string" as "6:string" (length 6, string),
+      // and the list ["foo", "bar"] as "2:3:foo:3:bar" (2 entries, length 3,
+      // foo, length 3, bar).
+      vmService.ObjRef duplicatesDataRef = await serviceClient.evaluate(
+        isolateRef.id,
+        mappedData.id,
+        """
+          this
+              .entries
+              .where((element) => (element.value as List).length > 1)
+              .map((dynamic e) {
+            var keyPart = "\${e.key.length}:\${e.key}";
+            List value = e.value as List;
+            var valuePart1 = "\${value.length}";
+            var valuePart2 = value
+                .map((element) => '$fieldsToStringPrettyPrintCode')
+                .map((element) => "\${element.length}:\$element")
+                .join(":");
+            return "\${keyPart}:\${valuePart1}:\${valuePart2}";
+          }).join(":")
+          """,
+      );
+      if (duplicatesDataRef is! vmService.InstanceRef) {
+        if (duplicatesDataRef is vmService.ErrorRef) {
+          vmService.Error error = await serviceClient.getObject(
+              isolateRef.id, duplicatesDataRef.id);
+          throw "Leak found, but trying to evaluate pretty printing "
+              "didn't go as planned.\n"
+              "Got error with message "
+              "'${error.message}'";
+        } else {
+          throw "Leak found, but trying to evaluate pretty printing "
+              "didn't go as planned.\n"
+              "Got type '${duplicatesDataRef.runtimeType}':"
+              "$duplicatesDataRef";
+        }
+      }
+
+      vmService.Instance duplicatesData =
+          await serviceClient.getObject(isolateRef.id, duplicatesDataRef.id);
+      String encodedData = duplicatesData.valueAsString;
+      try {
+        return parseEncodedLeakString(encodedData);
+      } catch (e) {
+        print("Failure on decoding '$encodedData'");
+        rethrow;
+      }
+    } else {
+      // No leaks.
+      return [];
+    }
+  }
+
+  static List<Leak> parseEncodedLeakString(String leakString) {
+    int index = 0;
+    int parseInt() {
+      int endPartIndex = leakString.indexOf(":", index);
+      String part = leakString.substring(index, endPartIndex);
+      int value = int.parse(part);
+      index = endPartIndex + 1;
+      return value;
+    }
+
+    String parseString() {
+      int value = parseInt();
+      String string = leakString.substring(index, index + value);
+      index = index + value + 1;
+      return string;
+    }
+
+    List<Leak> result = [];
+    while (index < leakString.length) {
+      String duplicate = parseString();
+      int count = parseInt();
+
+      List<String> prettyPrints = [];
+      for (int i = 0; i < count; i++) {
+        String data = parseString();
+        prettyPrints.add(data);
+      }
+      result.add(new Leak(duplicate, count, prettyPrints));
+    }
+    return result;
+  }
+
+  int _latestLeakIteration = -1;
+
+  void leakDetected(String duplicate, int count, List<String> prettyPrints) {
+    if (_iterationNumber != _latestLeakIteration) {
+      print("======================================");
+      print("WARNING: Duplicated pretty prints of objects.");
+      print("This might be a memory leak!");
+      print("");
+    }
+    _latestLeakIteration = _iterationNumber;
+    print("$duplicate ($count)");
+    for (String prettyPrint in prettyPrints) {
+      print(" => ${prettyPrint}");
+    }
+    print("");
+  }
+
+  void noLeakDetected() {}
+
+  bool shouldDoAnotherIteration(int iterationNumber) {
+    return true;
   }
 }
 
@@ -420,166 +333,10 @@
   Interest(this.uri, this.className, this.fieldNames);
 }
 
-HeapGraph convertHeapGraph(vmService.HeapSnapshotGraph graph) {
-  HeapGraphClassSentinel classSentinel = new HeapGraphClassSentinel();
-  List<HeapGraphClassActual> classes =
-      new List<HeapGraphClassActual>.filled(graph.classes.length, null);
-  for (int i = 0; i < graph.classes.length; i++) {
-    vmService.HeapSnapshotClass c = graph.classes[i];
-    classes[i] = new HeapGraphClassActual(c);
-  }
+class Leak {
+  final String duplicate;
+  final int count;
+  final List<String> prettyPrints;
 
-  HeapGraphElementSentinel elementSentinel = new HeapGraphElementSentinel();
-  List<HeapGraphElementActual> elements =
-      new List<HeapGraphElementActual>.filled(graph.objects.length, null);
-  for (int i = 0; i < graph.objects.length; i++) {
-    vmService.HeapSnapshotObject o = graph.objects[i];
-    elements[i] = new HeapGraphElementActual(o);
-  }
-
-  for (int i = 0; i < graph.objects.length; i++) {
-    vmService.HeapSnapshotObject o = graph.objects[i];
-    HeapGraphElementActual converted = elements[i];
-    if (o.classId == 0) {
-      converted.class_ = classSentinel;
-    } else {
-      converted.class_ = classes[o.classId - 1];
-    }
-    converted.referencesFiller = () {
-      for (int refId in o.references) {
-        HeapGraphElement ref;
-        if (refId == 0) {
-          ref = elementSentinel;
-        } else {
-          ref = elements[refId - 1];
-        }
-        converted.references.add(ref);
-      }
-    };
-  }
-  return new HeapGraph(classSentinel, classes, elementSentinel, elements);
-}
-
-class HeapGraph {
-  final HeapGraphClassSentinel classSentinel;
-  final List<HeapGraphClassActual> classes;
-  final HeapGraphElementSentinel elementSentinel;
-  final List<HeapGraphElementActual> elements;
-
-  HeapGraph(
-      this.classSentinel, this.classes, this.elementSentinel, this.elements);
-}
-
-abstract class HeapGraphElement {
-  /// Outbound references, i.e. this element points to elements in this list.
-  List<HeapGraphElement> _references;
-  void Function() referencesFiller;
-  List<HeapGraphElement> get references {
-    if (_references == null && referencesFiller != null) {
-      _references = <HeapGraphElement>[];
-      referencesFiller();
-    }
-    return _references;
-  }
-
-  String getPrettyPrint(Map<Uri, Map<String, List<String>>> prettyPrints) {
-    if (this is HeapGraphElementActual) {
-      HeapGraphElementActual me = this;
-      if (me.class_.toString() == "_OneByteString") {
-        return '"${me.origin.data}"';
-      }
-      if (me.class_.toString() == "_SimpleUri") {
-        return "_SimpleUri["
-            "${me.getField("_uri").getPrettyPrint(prettyPrints)}]";
-      }
-      if (me.class_.toString() == "_Uri") {
-        return "_Uri[${me.getField("scheme").getPrettyPrint(prettyPrints)}:"
-            "${me.getField("path").getPrettyPrint(prettyPrints)}]";
-      }
-      if (me.class_ is HeapGraphClassActual) {
-        HeapGraphClassActual c = me.class_;
-        Map<String, List<String>> classToFields = prettyPrints[c.libraryUri];
-        if (classToFields != null) {
-          List<String> fields = classToFields[c.name];
-          if (fields != null) {
-            return "${c.name}[" +
-                fields.map((field) {
-                  return "$field: "
-                      "${me.getField(field)?.getPrettyPrint(prettyPrints)}";
-                }).join(", ") +
-                "]";
-          }
-        }
-      }
-    }
-    return toString();
-  }
-}
-
-class HeapGraphElementSentinel extends HeapGraphElement {
-  String toString() => "HeapGraphElementSentinel";
-}
-
-class HeapGraphElementActual extends HeapGraphElement {
-  final vmService.HeapSnapshotObject origin;
-  HeapGraphClass class_;
-
-  HeapGraphElementActual(this.origin);
-
-  HeapGraphElement getField(String name) {
-    if (class_ is HeapGraphClassActual) {
-      HeapGraphClassActual c = class_;
-      for (vmService.HeapSnapshotField field in c.origin.fields) {
-        if (field.name == name) {
-          return references[field.index];
-        }
-      }
-    }
-    return null;
-  }
-
-  String toString() {
-    if (origin.data is vmService.HeapSnapshotObjectNoData) {
-      return "Instance of $class_";
-    }
-    if (origin.data is vmService.HeapSnapshotObjectLengthData) {
-      vmService.HeapSnapshotObjectLengthData data = origin.data;
-      return "Instance of $class_ length = ${data.length}";
-    }
-    return "Instance of $class_; data: '${origin.data}'";
-  }
-}
-
-abstract class HeapGraphClass {
-  List<HeapGraphElement> _instances;
-  List<HeapGraphElement> getInstances(HeapGraph graph) {
-    if (_instances == null) {
-      _instances = <HeapGraphElement>[];
-      for (int i = 0; i < graph.elements.length; i++) {
-        HeapGraphElementActual converted = graph.elements[i];
-        if (converted.class_ == this) {
-          _instances.add(converted);
-        }
-      }
-    }
-    return _instances;
-  }
-}
-
-class HeapGraphClassSentinel extends HeapGraphClass {
-  String toString() => "HeapGraphClassSentinel";
-}
-
-class HeapGraphClassActual extends HeapGraphClass {
-  final vmService.HeapSnapshotClass origin;
-
-  HeapGraphClassActual(this.origin) {
-    assert(origin != null);
-  }
-
-  String get name => origin.name;
-
-  Uri get libraryUri => origin.libraryUri;
-
-  String toString() => name;
+  Leak(this.duplicate, this.count, this.prettyPrints);
 }
diff --git a/pkg/front_end/test/vm_service_heap_helper_test.dart b/pkg/front_end/test/vm_service_heap_helper_test.dart
new file mode 100644
index 0000000..d72a28d
--- /dev/null
+++ b/pkg/front_end/test/vm_service_heap_helper_test.dart
@@ -0,0 +1,173 @@
+// 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
+
+import 'dart:async';
+import 'dart:developer';
+import 'dart:io';
+
+import 'package:front_end/src/api_unstable/util.dart';
+
+import "vm_service_heap_helper.dart" as helper;
+
+Future<void> main(List<String> args) async {
+  if (args.contains("--leak")) {
+    return doLeak();
+  }
+
+  List<helper.Interest> interests = <helper.Interest>[];
+  interests.add(
+    new helper.Interest(
+      Platform.script,
+      "LeakMe",
+      ["unique"],
+    ),
+  );
+  interests.add(
+    new helper.Interest(
+      Platform.script,
+      "LeakMe2",
+      ["uniquePart1", "uniquePart2"],
+    ),
+  );
+  LeakFinderTest heapHelper = new LeakFinderTest(
+    interests: interests,
+    prettyPrints: [
+      new helper.Interest(
+        Platform.script,
+        "LeakMe",
+        ["unique", "forPrettyPrinting"],
+      ),
+      new helper.Interest(
+        Platform.script,
+        "LeakMe2",
+        ["uniquePart1", "uniquePart2", "forPrettyPrinting"],
+      ),
+    ],
+    throwOnPossibleLeak: false,
+  );
+
+  heapHelper.start(
+    [
+      "--enable-asserts",
+      Platform.script.toString(),
+      "--leak",
+    ],
+    stderrReceiver: (s) {},
+    stdoutReceiver: (s) {},
+  );
+  List<String> expectedData = [
+    '1: no leak',
+    '2: 2: [LeakMe[unique: "a", forPrettyPrinting: "1"], '
+        'LeakMe[unique: "a", forPrettyPrinting: "3"]]',
+    '3: 2: [LeakMe[unique: "a", forPrettyPrinting: "1"], '
+        'LeakMe[unique: "a", forPrettyPrinting: "3"]]',
+    '3: 2: [LeakMe[unique: "b", forPrettyPrinting: "2"], '
+        'LeakMe[unique: "b", forPrettyPrinting: "4"]]',
+    '4: no leak',
+    '5: no leak',
+    '6: 2: ['
+        'LeakMe2[uniquePart1: "a", uniquePart2: "a", forPrettyPrinting: "1"], '
+        'LeakMe2[uniquePart1: "a", uniquePart2: "a", forPrettyPrinting: "4"]'
+        ']',
+  ];
+  List<String> leakData = await heapHelper.completer.future;
+  if (!equalLists(expectedData, leakData)) {
+    throw "Expected and actual not equal:\n\n"
+        "- ${expectedData.join("\n- ")}\n\n"
+        "vs\n\n"
+        "- ${leakData.join("\n- ")}";
+  }
+
+  print("Done!");
+}
+
+void doLeak() {
+  {
+    LeakMe a = new LeakMe("a", "1");
+    LeakMe b = new LeakMe("b", "2");
+    // Expect no leaks.
+    debugger();
+    LeakMe a2 = new LeakMe("a", "3");
+    // Expect one leak: We find leaks for class `LeakMe` based on the first
+    // field (`unique`). Now we have two objects with the same data ("a") for
+    // this field: `a` and `a2`. That's a leak as we've defined it.
+    debugger();
+    LeakMe b2 = new LeakMe("b", "4");
+    // Expect two leaks: We find leaks for class `LeakMe` based on the first
+    // field (`unique`). Now we have two objects with data "a" (`a` and `a2`)
+    // and two objects with data "b" (`b` and `b2`).
+    debugger();
+    print("$a, $b, $a2, $b2");
+  }
+  {
+    LeakMe2 a = new LeakMe2("a", "a", "1");
+    LeakMe2 b = new LeakMe2("b", "b", "2");
+    // Expect no leaks.
+    debugger();
+    LeakMe2 a2 = new LeakMe2("a", "foo", "3");
+    // Expect no leak.
+    debugger();
+    LeakMe2 a3 = new LeakMe2("a", "a", "4");
+    // Expect one leak: We find leaks for class `LeakMe2` based on the first
+    // field AND the second field (`uniquePart1` and `uniquePart2`). Now we have
+    // two objects with the same data in both fields ("a" and "a" for part 1 and
+    // part 2) namely the objects saved in variables `a` and `a3`.
+    // Notice how the object in variable `a2` did not introduce a leak even
+    // though part 1 match ("a") as part 2 doesn't ("a" vs "foo").
+    debugger();
+    print("$a, $b, $a2, $a3");
+  }
+}
+
+class LeakMe {
+  final String unique;
+  final String forPrettyPrinting;
+
+  LeakMe(this.unique, this.forPrettyPrinting);
+}
+
+class LeakMe2 {
+  final String uniquePart1;
+  final String uniquePart2;
+  final String forPrettyPrinting;
+
+  LeakMe2(this.uniquePart1, this.uniquePart2, this.forPrettyPrinting);
+}
+
+class LeakFinderTest extends helper.VMServiceHeapHelperSpecificExactLeakFinder {
+  List<String> leakData = [];
+  int iterationNumber = -1;
+  Completer<List<String>> completer = new Completer<List<String>>();
+
+  LeakFinderTest({
+    List<helper.Interest> interests,
+    List<helper.Interest> prettyPrints,
+    bool throwOnPossibleLeak,
+  }) : super(
+            interests: interests,
+            prettyPrints: prettyPrints,
+            throwOnPossibleLeak: throwOnPossibleLeak);
+
+  void processExited(int exitCode) {
+    print("Process exited!");
+    leakData.sort();
+    completer.complete(leakData);
+  }
+
+  void leakDetected(String duplicate, int count, List<String> prettyPrints) {
+    prettyPrints.sort();
+    leakData.add("$iterationNumber: $count: $prettyPrints");
+  }
+
+  void noLeakDetected() {
+    leakData.add("$iterationNumber: no leak");
+  }
+
+  bool shouldDoAnotherIteration(int iterationNumber) {
+    this.iterationNumber = iterationNumber;
+    return iterationNumber <= 6;
+  }
+}
diff --git a/pkg/front_end/test/vm_service_helper.dart b/pkg/front_end/test/vm_service_helper.dart
index 8906a43..f5adc3f 100644
--- a/pkg/front_end/test/vm_service_helper.dart
+++ b/pkg/front_end/test/vm_service_helper.dart
@@ -156,7 +156,7 @@
   bool _started = false;
 
   void start(List<String> scriptAndArgs,
-      {void stdinReceiver(String line),
+      {void stdoutReceiver(String line),
       void stderrReceiver(String line)}) async {
     if (_started) throw "Already started";
     _started = true;
@@ -180,8 +180,8 @@
           throw e;
         });
       }
-      if (stdinReceiver != null) {
-        stdinReceiver(line);
+      if (stdoutReceiver != null) {
+        stdoutReceiver(line);
       } else {
         stdout.writeln("> $line");
       }
diff --git a/pkg/front_end/test/weekly_tester.dart b/pkg/front_end/test/weekly_tester.dart
index ba30452..51a670a 100644
--- a/pkg/front_end/test/weekly_tester.dart
+++ b/pkg/front_end/test/weekly_tester.dart
@@ -28,8 +28,13 @@
     } else {
       // The tools/bots/flutter/compile_flutter.sh script passes `--path`
       // --- we'll just pass everything along.
-      startedProcesses
-          .add(await run([leakTester.toString(), ...args], "leak test"));
+      startedProcesses.add(await run(
+        [
+          leakTester.toString(),
+          ...args,
+        ],
+        "leak test",
+      ));
     }
   }
   {
@@ -55,8 +60,13 @@
         // The tools/bots/flutter/compile_flutter.sh script passes `--path`
         // --- we'll just pass everything along.
         startedProcesses.add(await run(
-            [leakTester.toString(), ...args, "--alternativeInvalidation"],
-            "leak test alternative invalidation"));
+          [
+            leakTester.toString(),
+            ...args,
+            "--alternativeInvalidation",
+          ],
+          "leak test alternative invalidation",
+        ));
       }();
     }
   }
@@ -68,8 +78,13 @@
       exitCode = 1;
       print("Couldn't find $weakSuite");
     } else {
-      startedProcesses.add(
-          await run([weakSuite.toString(), "-DsemiFuzz=true"], "weak suite"));
+      startedProcesses.add(await run(
+        [
+          weakSuite.toString(),
+          "-DsemiFuzz=true",
+        ],
+        "weak suite",
+      ));
     }
   }
 
@@ -81,7 +96,27 @@
       print("Couldn't find $strongSuite");
     } else {
       startedProcesses.add(await run(
-          [strongSuite.toString(), "-DsemiFuzz=true"], "strong suite"));
+        [
+          strongSuite.toString(),
+          "-DsemiFuzz=true",
+        ],
+        "strong suite",
+      ));
+    }
+  }
+
+  {
+    // Leak tests of incremental suite tests.
+    Uri incrementalLeakTest =
+        Platform.script.resolve("vm_service_for_leak_detection.dart");
+    if (!new File.fromUri(incrementalLeakTest).existsSync()) {
+      exitCode = 1;
+      print("Couldn't find $incrementalLeakTest");
+    } else {
+      startedProcesses.add(await run([
+        incrementalLeakTest.toString(),
+        "--weekly",
+      ], "incremental leak test"));
     }
   }
 
@@ -103,6 +138,7 @@
 List<String> observatoryLines = [];
 
 Future<WrappedProcess> run(List<String> args, String id) async {
+  Stopwatch stopwatch = new Stopwatch()..start();
   Process process = await Process.start(
       Platform.resolvedExecutable, ["--enable-asserts", ...args]);
   process.stderr
@@ -123,6 +159,11 @@
       observatoryLines.add(line);
     }
   });
+  // ignore: unawaited_futures
+  process.exitCode.then((int exitCode) {
+    stopwatch.stop();
+    print("$id finished in ${stopwatch.elapsed.toString()}");
+  });
   return new WrappedProcess(process, id);
 }
 
diff --git a/pkg/frontend_server/test/frontend_server_flutter.dart b/pkg/frontend_server/test/frontend_server_flutter.dart
index efcfe51..78c9a7b 100644
--- a/pkg/frontend_server/test/frontend_server_flutter.dart
+++ b/pkg/frontend_server/test/frontend_server_flutter.dart
@@ -43,17 +43,26 @@
 }
 
 Future compileTests(String flutterDir, String flutterPlatformDir, Logger logger,
-    {String filter}) async {
+    {String filter, int shards: 1, int shard: 0}) async {
   if (flutterDir == null || !(new Directory(flutterDir).existsSync())) {
     throw "Didn't get a valid flutter directory to work with.";
   }
-  Directory flutterDirectory = new Directory(flutterDir);
+  if (shards < 1) {
+    throw "Shards must be >= 1";
+  }
+  if (shard < 0) {
+    throw "Shard must be >= 0";
+  }
+  if (shard >= shards) {
+    throw "Shard must be < shards";
+  }
   // Ensure the path ends in a slash.
-  flutterDirectory = new Directory.fromUri(flutterDirectory.uri);
+  final Directory flutterDirectory =
+      new Directory.fromUri(new Directory(flutterDir).uri);
 
   List<FileSystemEntity> allFlutterFiles =
       flutterDirectory.listSync(recursive: true, followLinks: false);
-  Directory flutterPlatformDirectory;
+  Directory flutterPlatformDirectoryTmp;
 
   if (flutterPlatformDir == null) {
     List<File> platformFiles = new List<File>.from(allFlutterFiles.where((f) =>
@@ -63,16 +72,16 @@
     if (platformFiles.length < 1) {
       throw "Expected to find a flutter platform file but didn't.";
     }
-    flutterPlatformDirectory = platformFiles.first.parent;
+    flutterPlatformDirectoryTmp = platformFiles.first.parent;
   } else {
-    flutterPlatformDirectory = Directory(flutterPlatformDir);
+    flutterPlatformDirectoryTmp = Directory(flutterPlatformDir);
   }
-  if (!flutterPlatformDirectory.existsSync()) {
-    throw "$flutterPlatformDirectory doesn't exist.";
+  if (!flutterPlatformDirectoryTmp.existsSync()) {
+    throw "$flutterPlatformDirectoryTmp doesn't exist.";
   }
   // Ensure the path ends in a slash.
-  flutterPlatformDirectory =
-      new Directory.fromUri(flutterPlatformDirectory.uri);
+  final Directory flutterPlatformDirectory =
+      new Directory.fromUri(flutterPlatformDirectoryTmp.uri);
 
   if (!new File.fromUri(
           flutterPlatformDirectory.uri.resolve("platform_strong.dill"))
@@ -87,9 +96,11 @@
       f.uri.toString().endsWith("/.packages")));
 
   List<String> allCompilationErrors = [];
-  for (File dotPackage in dotPackagesFiles) {
-    Directory systemTempDir = Directory.systemTemp;
-    Directory tempDir;
+  final Directory systemTempDir = Directory.systemTemp;
+  List<_QueueEntry> queue = [];
+  int totalFiles = 0;
+  for (int i = 0; i < dotPackagesFiles.length; i++) {
+    File dotPackage = dotPackagesFiles[i];
     Directory testDir =
         new Directory.fromUri(dotPackage.parent.uri.resolve("test/"));
     if (!testDir.existsSync()) continue;
@@ -98,7 +109,6 @@
       // in a setup that can handle that.
       continue;
     }
-    logger.notice("Go for $testDir");
     List<File> testFiles =
         new List<File>.from(testDir.listSync(recursive: true).where((f) {
       if (!f.path.endsWith("_test.dart")) return false;
@@ -126,27 +136,46 @@
     }
     for (List<File> files in [weak, strong]) {
       if (files.isEmpty) continue;
-      tempDir = systemTempDir.createTempSync('flutter_frontend_test');
-      try {
-        List<String> compilationErrors = await attemptStuff(
-            files,
-            tempDir,
-            flutterPlatformDirectory,
-            dotPackage,
-            testDir,
-            flutterDirectory,
-            logger,
-            filter);
-        if (compilationErrors.isNotEmpty) {
-          logger.notice("Notice that we had ${compilationErrors.length} "
-              "compilation errors for $testDir");
-          allCompilationErrors.addAll(compilationErrors);
-        }
-      } finally {
-        tempDir.delete(recursive: true);
-      }
+      queue.add(new _QueueEntry(files, dotPackage, testDir));
+      totalFiles += files.length;
     }
   }
+
+  // Process queue, taking shards into account.
+  // This involves ignoring some queue entries and cutting others up to
+  // process exactly the files assigned to this shard.
+  int shardChunkSize = (totalFiles + shards - 1) ~/ shards;
+  int chunkStart = shard * shardChunkSize;
+  int chunkEnd = (shard + 1) * shardChunkSize;
+  int processedFiles = 0;
+
+  for (_QueueEntry queueEntry in queue) {
+    if (processedFiles < chunkEnd &&
+        processedFiles + queueEntry.files.length >= chunkStart) {
+      List<File> chunk = [];
+      for (File file in queueEntry.files) {
+        if (processedFiles >= chunkStart && processedFiles < chunkEnd) {
+          chunk.add(file);
+        }
+        processedFiles++;
+      }
+
+      await _processFiles(
+          systemTempDir,
+          chunk,
+          flutterPlatformDirectory,
+          queueEntry.dotPackage,
+          queueEntry.testDir,
+          flutterDirectory,
+          logger,
+          filter,
+          allCompilationErrors);
+    } else {
+      // None of these files are part of the chunk.
+      processedFiles += queueEntry.files.length;
+    }
+  }
+
   if (allCompilationErrors.isNotEmpty) {
     logger.notice(
         "Had a total of ${allCompilationErrors.length} compilation errors:");
@@ -155,6 +184,45 @@
   }
 }
 
+class _QueueEntry {
+  final List<File> files;
+  final File dotPackage;
+  final Directory testDir;
+
+  _QueueEntry(this.files, this.dotPackage, this.testDir);
+}
+
+Future<void> _processFiles(
+    Directory systemTempDir,
+    List<File> files,
+    Directory flutterPlatformDirectory,
+    File dotPackage,
+    Directory testDir,
+    Directory flutterDirectory,
+    Logger logger,
+    String filter,
+    List<String> allCompilationErrors) async {
+  Directory tempDir = systemTempDir.createTempSync('flutter_frontend_test');
+  try {
+    List<String> compilationErrors = await attemptStuff(
+        files,
+        tempDir,
+        flutterPlatformDirectory,
+        dotPackage,
+        testDir,
+        flutterDirectory,
+        logger,
+        filter);
+    if (compilationErrors.isNotEmpty) {
+      logger.notice("Notice that we had ${compilationErrors.length} "
+          "compilation errors for $testDir");
+      allCompilationErrors.addAll(compilationErrors);
+    }
+  } finally {
+    tempDir.delete(recursive: true);
+  }
+}
+
 Future<List<String>> attemptStuff(
     List<File> testFiles,
     Directory tempDir,
diff --git a/pkg/frontend_server/test/frontend_server_flutter_suite.dart b/pkg/frontend_server/test/frontend_server_flutter_suite.dart
index d96ee6e..36ae561 100644
--- a/pkg/frontend_server/test/frontend_server_flutter_suite.dart
+++ b/pkg/frontend_server/test/frontend_server_flutter_suite.dart
@@ -146,6 +146,9 @@
   final String configurationName;
   final String testFilter;
 
+  final int shard;
+  final int shards;
+
   final String flutterDir;
   final String flutterPlatformDir;
 
@@ -157,7 +160,9 @@
       this.configurationName,
       this.testFilter,
       this.flutterDir,
-      this.flutterPlatformDir);
+      this.flutterPlatformDir,
+      this.shard,
+      this.shards);
 }
 
 void runSuite(SuiteConfiguration configuration) async {
@@ -168,6 +173,8 @@
       configuration.flutterPlatformDir,
       logger,
       filter: configuration.testFilter,
+      shard: configuration.shard,
+      shards: configuration.shards,
     );
   } catch (e) {
     logger.logUnexpectedResult("startup");
@@ -188,50 +195,58 @@
     ..listen((logEntry) => logs.add(logEntry));
   String filter = options.testFilter;
 
-  // Start the test suite in a new isolate.
-  ReceivePort exitPort = new ReceivePort();
-  ReceivePort errorPort = new ReceivePort();
-  SuiteConfiguration configuration = new SuiteConfiguration(
-    resultsPort.sendPort,
-    logsPort.sendPort,
-    options.verbose,
-    options.printFailureLog,
-    options.configurationName,
-    filter,
-    options.flutterDir,
-    options.flutterPlatformDir,
-  );
-  Future<bool> future = Future<bool>(() async {
-    Stopwatch stopwatch = Stopwatch()..start();
-    print("Running suite");
-    Isolate isolate = await Isolate.spawn<SuiteConfiguration>(
-        runSuite, configuration,
-        onExit: exitPort.sendPort, onError: errorPort.sendPort);
-    bool gotError = false;
-    StreamSubscription errorSubscription = errorPort.listen((message) {
-      print("Got error: $message!");
-      gotError = true;
-      logs.add("$message");
+  const int shards = 4;
+  List<Future<bool>> futures = [];
+  for (int shard = 0; shard < shards; shard++) {
+    // Start the test suite in a new isolate.
+    ReceivePort exitPort = new ReceivePort();
+    ReceivePort errorPort = new ReceivePort();
+    SuiteConfiguration configuration = new SuiteConfiguration(
+      resultsPort.sendPort,
+      logsPort.sendPort,
+      options.verbose,
+      options.printFailureLog,
+      options.configurationName,
+      filter,
+      options.flutterDir,
+      options.flutterPlatformDir,
+      shard,
+      shards,
+    );
+    Future<bool> future = Future<bool>(() async {
+      Stopwatch stopwatch = Stopwatch()..start();
+      print("Running suite shard $shard of $shards");
+      Isolate isolate = await Isolate.spawn<SuiteConfiguration>(
+          runSuite, configuration,
+          onExit: exitPort.sendPort, onError: errorPort.sendPort);
+      bool gotError = false;
+      StreamSubscription errorSubscription = errorPort.listen((message) {
+        print("Got error: $message!");
+        gotError = true;
+        logs.add("$message");
+      });
+      bool timedOut = false;
+      Timer timer = Timer(timeoutDuration, () {
+        timedOut = true;
+        print("Suite timed out after "
+            "${timeoutDuration.inMilliseconds}ms");
+        isolate.kill(priority: Isolate.immediate);
+      });
+      await exitPort.first;
+      errorSubscription.cancel();
+      timer.cancel();
+      if (!timedOut && !gotError) {
+        int seconds = stopwatch.elapsedMilliseconds ~/ 1000;
+        print("Suite finished (shard #$shard) (took ${seconds} seconds)");
+      }
+      return timedOut || gotError;
     });
-    bool timedOut = false;
-    Timer timer = Timer(timeoutDuration, () {
-      timedOut = true;
-      print("Suite timed out after "
-          "${timeoutDuration.inMilliseconds}ms");
-      isolate.kill(priority: Isolate.immediate);
-    });
-    await exitPort.first;
-    errorSubscription.cancel();
-    timer.cancel();
-    if (!timedOut && !gotError) {
-      int seconds = stopwatch.elapsedMilliseconds ~/ 1000;
-      print("Suite finished (took ${seconds} seconds)");
-    }
-    return timedOut || gotError;
-  });
+    futures.add(future);
+  }
 
   // Wait for isolate to terminate and clean up.
-  bool timeoutOrCrash = await future;
+  Iterable<bool> timeoutsOrCrashes = await Future.wait(futures);
+  bool timeoutOrCrash = timeoutsOrCrashes.any((timeout) => timeout);
   resultsPort.close();
   logsPort.close();
 
diff --git a/pkg/kernel/lib/ast.dart b/pkg/kernel/lib/ast.dart
index 1fb72f2..ef44a1d 100644
--- a/pkg/kernel/lib/ast.dart
+++ b/pkg/kernel/lib/ast.dart
@@ -592,6 +592,7 @@
 
   static int _libraryIdCounter = 0;
   int _libraryId = ++_libraryIdCounter;
+  int get libraryIdForTesting => _libraryId;
 
   @override
   int compareTo(Library other) => _libraryId - other._libraryId;
@@ -14015,6 +14016,39 @@
 /// constructor.
 final Constant dummyConstant = new NullConstant();
 
+/// Of the dummy nodes, some are tree nodes. `TreeNode`s has a parent pointer
+/// and that can be set when the dummy is used. This means that we can leak
+/// through them. This list will (at least as a stopgap) allow us to null-out
+/// the parent pointer when/if needed.
+///
+/// This should manually be kept up to date.
+final List<TreeNode> dummyTreeNodes = [
+  dummyLibrary,
+  dummyLibraryDependency,
+  dummyCombinator,
+  dummyLibraryPart,
+  dummyClass,
+  dummyConstructor,
+  dummyExtension,
+  dummyMember,
+  dummyProcedure,
+  dummyField,
+  dummyRedirectingFactoryConstructor,
+  dummyTypedef,
+  dummyInitializer,
+  dummyFunctionNode,
+  dummyStatement,
+  dummyExpression,
+  dummyNamedExpression,
+  dummyVariableDeclaration,
+  dummyTypeParameter,
+  dummyMapLiteralEntry,
+  dummyArguments,
+  dummyAssertStatement,
+  dummySwitchCase,
+  dummyCatch,
+];
+
 /// Sentinel value used to signal that a node cannot be removed through the
 /// [RemovingTransformer].
 const Null cannotRemoveSentinel = null;
diff --git a/runtime/tests/vm/vm.status b/runtime/tests/vm/vm.status
index ee4e386..653d67a 100644
--- a/runtime/tests/vm/vm.status
+++ b/runtime/tests/vm/vm.status
@@ -330,6 +330,9 @@
 # up with the real Dart stack trace and hence we don't get correct
 # symbol names.
 [ $arch == simarm || $arch == simarm64 || $arch == simarm64c ]
+cc/Dart_SetFfiNativeResolver: SkipByDesign # Test uses dart:ffi which is not supported on simulators.
+cc/Dart_SetFfiNativeResolver_DoesNotResolve: SkipByDesign # Test uses dart:ffi which is not supported on simulators.
+cc/Dart_SetFfiNativeResolver_MissingResolver: SkipByDesign # Test uses dart:ffi which is not supported on simulators.
 cc/LargeMap: SkipByDesign
 cc/Profiler_AllocationSampleTest: SkipByDesign
 cc/Profiler_ArrayAllocation: SkipByDesign
diff --git a/sdk/lib/_internal/vm/bin/file_patch.dart b/sdk/lib/_internal/vm/bin/file_patch.dart
index ec4337c..92a306b 100644
--- a/sdk/lib/_internal/vm/bin/file_patch.dart
+++ b/sdk/lib/_internal/vm/bin/file_patch.dart
@@ -210,9 +210,9 @@
   }
 
   static Stream _listenOnSocket(int socketId, int id, int pathId) {
-    var native = new _NativeSocket.watch(socketId);
-    var socket = new _RawSocket(native);
-    return socket.expand((event) {
+    final nativeSocket = _NativeSocket.watch(socketId);
+    final rawSocket = _RawSocket(nativeSocket);
+    return rawSocket.expand((event) {
       var stops = [];
       var events = [];
       var pair = {};
@@ -300,7 +300,7 @@
         } while (eventCount > 0);
         // Be sure to clear this manually, as the sockets are not read through
         // the _NativeSocket interface.
-        native.available = 0;
+        nativeSocket.available = 0;
         for (var map in pair.values) {
           for (var event in map.values) {
             rewriteMove(event, getIsDir(event));
diff --git a/tools/VERSION b/tools/VERSION
index 57c8cad..6636005 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 14
 PATCH 0
-PRERELEASE 213
+PRERELEASE 214
 PRERELEASE_PATCH 0
\ No newline at end of file