[CFE] Allow experimental invalidation to work on DillLibraryBuilders

This CL makes experimental invalidation work on DillLibraryBuilders,
and solves all found issues (e.g. old references (aka leaks)) with it.

Note: This CL introduces a few writes that seems weird (e.g. setting
variables that's about to be out-of-scope to null). This is done to
prevent "leaks", or probably more likely, prevent a "false positive"
leak detection and it currently gives a "clean bill of health" from
the leak detector.

Change-Id: I5b01df6e9ede710a5b624a8a4c21015214140318
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/134288
Commit-Queue: Jens Johansen <jensj@google.com>
Reviewed-by: Johnni Winther <johnniwinther@google.com>
diff --git a/pkg/front_end/lib/src/fasta/dill/dill_library_builder.dart b/pkg/front_end/lib/src/fasta/dill/dill_library_builder.dart
index 562615e..818480a 100644
--- a/pkg/front_end/lib/src/fasta/dill/dill_library_builder.dart
+++ b/pkg/front_end/lib/src/fasta/dill/dill_library_builder.dart
@@ -32,6 +32,7 @@
 import '../builder/class_builder.dart';
 import '../builder/dynamic_type_builder.dart';
 import '../builder/extension_builder.dart';
+import '../builder/modifier_builder.dart';
 import '../builder/never_type_builder.dart';
 import '../builder/invalid_type_declaration_builder.dart';
 import '../builder/library_builder.dart';
@@ -89,7 +90,7 @@
   /// [../kernel/kernel_library_builder.dart].
   Map<String, String> unserializableExports;
 
-  // TODO(jensj): These 4 booleans could potentially be merged into a single
+  // TODO(jensj): These 5 booleans could potentially be merged into a single
   // state field.
   bool isReadyToBuild = false;
   bool isReadyToFinalizeExports = false;
@@ -294,53 +295,75 @@
       exportScopeBuilder.addMember(name, declaration);
     });
 
+    Map<Reference, Builder> sourceBuildersMap =
+        loader.currentSourceLoader?.buildersCreatedWithReferences;
     for (Reference reference in library.additionalExports) {
       NamedNode node = reference.node;
-      Uri libraryUri;
-      String name;
-      bool isSetter = false;
-      if (node is Class) {
-        libraryUri = node.enclosingLibrary.importUri;
-        name = node.name;
-      } else if (node is Procedure) {
-        libraryUri = node.enclosingLibrary.importUri;
-        name = node.name.name;
-        isSetter = node.isSetter;
-      } else if (node is Member) {
-        libraryUri = node.enclosingLibrary.importUri;
-        name = node.name.name;
-      } else if (node is Typedef) {
-        libraryUri = node.enclosingLibrary.importUri;
-        name = node.name;
-      } else if (node is Extension) {
-        libraryUri = node.enclosingLibrary.importUri;
-        name = node.name;
-      } else {
-        unhandled("${node.runtimeType}", "finalizeExports", -1, fileUri);
-      }
-      DillLibraryBuilder library = loader.builders[libraryUri];
-      if (library == null) {
-        internalProblem(
-            templateUnspecified.withArguments("No builder for '$libraryUri'."),
-            -1,
-            fileUri);
-      }
       Builder declaration;
-      if (isSetter) {
-        declaration = library.exportScope.lookupLocalMember(name, setter: true);
-        exportScopeBuilder.addSetter(name, declaration);
+      String name;
+      if (sourceBuildersMap?.containsKey(reference) == true) {
+        declaration = sourceBuildersMap[reference];
+        assert(declaration != null);
+        if (declaration is ModifierBuilder) {
+          name = declaration.name;
+        } else {
+          throw new StateError(
+              "Unexpected: $declaration (${declaration.runtimeType}");
+        }
+
+        if (declaration.isSetter) {
+          exportScopeBuilder.addSetter(name, declaration);
+        } else {
+          exportScopeBuilder.addMember(name, declaration);
+        }
       } else {
-        declaration =
-            library.exportScope.lookupLocalMember(name, setter: false);
-        exportScopeBuilder.addMember(name, declaration);
+        Uri libraryUri;
+        bool isSetter = false;
+        if (node is Class) {
+          libraryUri = node.enclosingLibrary.importUri;
+          name = node.name;
+        } else if (node is Procedure) {
+          libraryUri = node.enclosingLibrary.importUri;
+          name = node.name.name;
+          isSetter = node.isSetter;
+        } else if (node is Member) {
+          libraryUri = node.enclosingLibrary.importUri;
+          name = node.name.name;
+        } else if (node is Typedef) {
+          libraryUri = node.enclosingLibrary.importUri;
+          name = node.name;
+        } else if (node is Extension) {
+          libraryUri = node.enclosingLibrary.importUri;
+          name = node.name;
+        } else {
+          unhandled("${node.runtimeType}", "finalizeExports", -1, fileUri);
+        }
+        LibraryBuilder library = loader.builders[libraryUri];
+        if (library == null) {
+          internalProblem(
+              templateUnspecified
+                  .withArguments("No builder for '$libraryUri'."),
+              -1,
+              fileUri);
+        }
+        if (isSetter) {
+          declaration =
+              library.exportScope.lookupLocalMember(name, setter: true);
+          exportScopeBuilder.addSetter(name, declaration);
+        } else {
+          declaration =
+              library.exportScope.lookupLocalMember(name, setter: false);
+          exportScopeBuilder.addMember(name, declaration);
+        }
+        if (declaration == null) {
+          internalProblem(
+              templateUnspecified.withArguments(
+                  "Exported element '$name' not found in '$libraryUri'."),
+              -1,
+              fileUri);
+        }
       }
-      if (declaration == null) {
-        internalProblem(
-            templateUnspecified.withArguments(
-                "Exported element '$name' not found in '$libraryUri'."),
-            -1,
-            fileUri);
-      }
+
       assert(
           (declaration is ClassBuilder && node == declaration.cls) ||
               (declaration is TypeAliasBuilder &&
diff --git a/pkg/front_end/lib/src/fasta/dill/dill_loader.dart b/pkg/front_end/lib/src/fasta/dill/dill_loader.dart
index 3d91dbd..0ce2b66 100644
--- a/pkg/front_end/lib/src/fasta/dill/dill_loader.dart
+++ b/pkg/front_end/lib/src/fasta/dill/dill_loader.dart
@@ -21,6 +21,8 @@
 
 import '../problems.dart' show unhandled;
 
+import '../source/source_loader.dart' show SourceLoader;
+
 import '../target_implementation.dart' show TargetImplementation;
 
 import 'dill_library_builder.dart' show DillLibraryBuilder;
@@ -28,6 +30,8 @@
 import 'dill_target.dart' show DillTarget;
 
 class DillLoader extends Loader {
+  SourceLoader currentSourceLoader;
+
   DillLoader(TargetImplementation target) : super(target);
 
   Template<SummaryTemplate> get outlineSummaryTemplate =>
diff --git a/pkg/front_end/lib/src/fasta/incremental_compiler.dart b/pkg/front_end/lib/src/fasta/incremental_compiler.dart
index 8599429..b58f341 100644
--- a/pkg/front_end/lib/src/fasta/incremental_compiler.dart
+++ b/pkg/front_end/lib/src/fasta/incremental_compiler.dart
@@ -129,7 +129,7 @@
   List<LibraryBuilder> platformBuilders;
   Map<Uri, LibraryBuilder> userBuilders;
   final Uri initializeFromDillUri;
-  final Component componentToInitializeFrom;
+  Component componentToInitializeFrom;
   bool initializedFromDill = false;
   bool initializedIncrementalSerializer = false;
   Uri previousPackagesUri;
@@ -213,13 +213,14 @@
       KernelTarget userCodeOld = userCode;
       setupNewUserCode(c, uriTranslator, hierarchy, reusedLibraries,
           experimentalInvalidation, entryPoints.first);
-      Map<LibraryBuilder, List<SourceLibraryBuilder>> rebuildBodiesMap =
+      Map<LibraryBuilder, List<LibraryBuilder>> rebuildBodiesMap =
           experimentalInvalidationCreateRebuildBodiesBuilders(
               experimentalInvalidation, uriTranslator);
       entryPoints = userCode.setEntryPoints(entryPoints);
       await userCode.loader.buildOutlines();
       experimentalInvalidationPatchUpScopes(
           experimentalInvalidation, rebuildBodiesMap);
+      rebuildBodiesMap = null;
 
       // Checkpoint: Build the actual outline.
       // Note that the [Component] is not the "full" component.
@@ -277,10 +278,14 @@
       if (componentWithDill == null) {
         userCode.loader.builders.clear();
         userCode = userCodeOld;
+        dillLoadedData.loader.currentSourceLoader = userCode.loader;
       } else {
-        previousSourceBuilders = await convertSourceLibraryBuildersToDill();
+        previousSourceBuilders =
+            await convertSourceLibraryBuildersToDill(experimentalInvalidation);
       }
 
+      experimentalInvalidation = null;
+
       // Output result.
       Procedure mainMethod = componentWithDill == null
           ? data.userLoadedUriMain
@@ -295,11 +300,17 @@
   /// Convert every SourceLibraryBuilder to a DillLibraryBuilder.
   /// As we always do this, this will only be the new ones.
   ///
+  /// If doing experimental invalidation that means that some of the old dill
+  /// library builders might have links (via export scopes) to the
+  /// source builders and they will thus be patched up here too.
+  ///
   /// Returns the set of Libraries that now has new (dill) builders.
-  Future<Set<Library>> convertSourceLibraryBuildersToDill() async {
+  Future<Set<Library>> convertSourceLibraryBuildersToDill(
+      ExperimentalInvalidation experimentalInvalidation) async {
     bool changed = false;
     Set<Library> newDillLibraryBuilders = new Set<Library>();
     userBuilders ??= <Uri, LibraryBuilder>{};
+    Map<LibraryBuilder, List<LibraryBuilder>> convertedLibraries;
     for (MapEntry<Uri, LibraryBuilder> entry
         in userCode.loader.builders.entries) {
       if (entry.value is SourceLibraryBuilder) {
@@ -309,14 +320,53 @@
         userCode.loader.builders[entry.key] = dillBuilder;
         userBuilders[entry.key] = dillBuilder;
         newDillLibraryBuilders.add(builder.library);
+        if (userCode.loader.first == builder) {
+          userCode.loader.first = dillBuilder;
+        }
         changed = true;
+        if (experimentalInvalidation != null) {
+          convertedLibraries ??=
+              new Map<LibraryBuilder, List<LibraryBuilder>>();
+          convertedLibraries[builder] = [dillBuilder];
+        }
       }
     }
     if (changed) {
       // We suppress finalization errors because they have already been
       // reported.
       await dillLoadedData.buildOutlines(suppressFinalizationErrors: true);
+
+      if (experimentalInvalidation != null) {
+        /// If doing experimental invalidation that means that some of the old
+        /// dill library builders might have links (via export scopes) to the
+        /// source builders. Patch that up.
+
+        // Maps from old library builder to map of new content.
+        Map<LibraryBuilder, Map<String, Builder>> replacementMap = {};
+
+        // Maps from old library builder to map of new content.
+        Map<LibraryBuilder, Map<String, Builder>> replacementSettersMap = {};
+
+        experimentalInvalidationFillReplacementMaps(
+            convertedLibraries, replacementMap, replacementSettersMap);
+
+        for (LibraryBuilder builder
+            in experimentalInvalidation.originalNotReusedLibraries) {
+          DillLibraryBuilder dillBuilder = builder;
+          if (dillBuilder.isBuilt) {
+            dillBuilder.exportScope
+                .patchUpScope(replacementMap, replacementSettersMap);
+          }
+        }
+        replacementMap = null;
+        replacementSettersMap = null;
+      }
     }
+    userCode.loader.buildersCreatedWithReferences.clear();
+    userCode.loader.builderHierarchy.nodes.clear();
+    userCode.loader.referenceFromIndex = null;
+    convertedLibraries = null;
+    experimentalInvalidation = null;
     if (userBuilders.isEmpty) userBuilders = null;
     return newDillLibraryBuilders;
   }
@@ -415,17 +465,17 @@
   /// Fill in the replacement maps that describe the replacements that need to
   /// happen because of experimental invalidation.
   void experimentalInvalidationFillReplacementMaps(
-      Map<LibraryBuilder, List<SourceLibraryBuilder>> rebuildBodiesMap,
+      Map<LibraryBuilder, List<LibraryBuilder>> rebuildBodiesMap,
       Map<LibraryBuilder, Map<String, Builder>> replacementMap,
       Map<LibraryBuilder, Map<String, Builder>> replacementSettersMap) {
-    for (MapEntry<LibraryBuilder, List<SourceLibraryBuilder>> entry
+    for (MapEntry<LibraryBuilder, List<LibraryBuilder>> entry
         in rebuildBodiesMap.entries) {
       Map<String, Builder> childReplacementMap = {};
       Map<String, Builder> childReplacementSettersMap = {};
-      List<SourceLibraryBuilder> builders = rebuildBodiesMap[entry.key];
+      List<LibraryBuilder> builders = rebuildBodiesMap[entry.key];
       replacementMap[entry.key] = childReplacementMap;
       replacementSettersMap[entry.key] = childReplacementSettersMap;
-      for (SourceLibraryBuilder builder in builders) {
+      for (LibraryBuilder builder in builders) {
         NameIterator iterator = builder.nameIterator;
         while (iterator.moveNext()) {
           Builder childBuilder = iterator.current;
@@ -450,22 +500,22 @@
   /// When doing experimental invalidation, we have some builders that needs to
   /// be rebuild special, namely they have to be [userCode.loader.read] with
   /// references from the original [Library] for things to work.
-  Map<LibraryBuilder, List<SourceLibraryBuilder>>
+  Map<LibraryBuilder, List<LibraryBuilder>>
       experimentalInvalidationCreateRebuildBodiesBuilders(
           ExperimentalInvalidation experimentalInvalidation,
           UriTranslator uriTranslator) {
     // Any builder(s) in [rebuildBodies] should be semi-reused: Create source
     // builders based on the underlying libraries.
     // Maps from old library builder to list of new library builder(s).
-    Map<LibraryBuilder, List<SourceLibraryBuilder>> rebuildBodiesMap =
-        new Map<LibraryBuilder, List<SourceLibraryBuilder>>.identity();
+    Map<LibraryBuilder, List<LibraryBuilder>> rebuildBodiesMap =
+        new Map<LibraryBuilder, List<LibraryBuilder>>.identity();
     if (experimentalInvalidation != null) {
       for (LibraryBuilder library in experimentalInvalidation.rebuildBodies) {
         LibraryBuilder newBuilder = userCode.loader.read(library.importUri, -1,
             accessor: userCode.loader.first,
             fileUri: library.fileUri,
             referencesFrom: library.library);
-        List<SourceLibraryBuilder> builders = [newBuilder];
+        List<LibraryBuilder> builders = [newBuilder];
         rebuildBodiesMap[library] = builders;
         for (LibraryPart part in library.library.parts) {
           // We need to pass the reference to make any class, procedure etc
@@ -492,7 +542,7 @@
   /// didn't do anything special.
   void experimentalInvalidationPatchUpScopes(
       ExperimentalInvalidation experimentalInvalidation,
-      Map<LibraryBuilder, List<SourceLibraryBuilder>> rebuildBodiesMap) {
+      Map<LibraryBuilder, List<LibraryBuilder>> rebuildBodiesMap) {
     if (experimentalInvalidation != null) {
       // Maps from old library builder to map of new content.
       Map<LibraryBuilder, Map<String, Builder>> replacementMap = {};
@@ -557,8 +607,16 @@
               }
             }
           }
+        } else if (builder is DillLibraryBuilder) {
+          DillLibraryBuilder dillBuilder = builder;
+          // There's only something to patch up if it was build already.
+          if (dillBuilder.isBuilt) {
+            dillBuilder.exportScope
+                .patchUpScope(replacementMap, replacementSettersMap);
+          }
         } else {
-          throw "Currently unsupported";
+          throw new StateError(
+              "Unexpected builder: $builder (${builder.runtimeType})");
         }
       }
     }
@@ -581,6 +639,7 @@
         dillLoadedData,
         uriTranslator);
     userCode.loader.hierarchy = hierarchy;
+    dillLoadedData.loader.currentSourceLoader = userCode.loader;
 
     // Re-use the libraries we've deemed re-usable.
     for (LibraryBuilder library in reusedLibraries) {
@@ -754,6 +813,10 @@
     // rebuild the bodies.
     for (int i = 0; i < reusedResult.directlyInvalidated.length; i++) {
       LibraryBuilder builder = reusedResult.directlyInvalidated[i];
+      if (builder.library.problemsAsJson != null) {
+        assert(builder.library.problemsAsJson.isNotEmpty);
+        return null;
+      }
       Iterator<Builder> iterator = builder.iterator;
       while (iterator.moveNext()) {
         Builder childBuilder = iterator.current;
@@ -866,6 +929,7 @@
         // If initializing from a component it has to include the sdk,
         // so we explicitly don't load it here.
         initializeFromComponent(uriTranslator, c, data);
+        componentToInitializeFrom = null;
       } else {
         List<int> summaryBytes = await c.options.loadSdkSummaryBytes();
         bytesLength = prepareSummary(summaryBytes, uriTranslator, c, data);
diff --git a/pkg/front_end/lib/src/fasta/source/source_library_builder.dart b/pkg/front_end/lib/src/fasta/source/source_library_builder.dart
index dc904d8..65e4043 100644
--- a/pkg/front_end/lib/src/fasta/source/source_library_builder.dart
+++ b/pkg/front_end/lib/src/fasta/source/source_library_builder.dart
@@ -39,13 +39,14 @@
         Nullability,
         Procedure,
         ProcedureKind,
+        Reference,
         SetLiteral,
         StaticInvocation,
         StringLiteral,
         Supertype,
-        Typedef,
         TypeParameter,
         TypeParameterType,
+        Typedef,
         VariableDeclaration,
         VoidType;
 
@@ -774,13 +775,18 @@
     }
   }
 
-  Builder addBuilder(String name, Builder declaration, int charOffset) {
+  @override
+  Builder addBuilder(String name, Builder declaration, int charOffset,
+      {Reference reference}) {
     // TODO(ahe): Set the parent correctly here. Could then change the
     // implementation of MemberBuilder.isTopLevel to test explicitly for a
     // LibraryBuilder.
     if (name == null) {
       unhandled("null", "name", charOffset, fileUri);
     }
+    if (reference != null) {
+      loader.buildersCreatedWithReferences[reference] = declaration;
+    }
     if (currentTypeParameterScopeBuilder == libraryDeclaration) {
       if (declaration is MemberBuilder) {
         declaration.parent = this;
@@ -1134,9 +1140,12 @@
         // DietListener can find them.
         for (int i = duplicated.length; i > 0; i--) {
           Builder declaration = duplicated[i - 1];
+          // No reference: There should be no duplicates when using references.
           addBuilder(name, declaration, declaration.charOffset);
         }
       } else {
+        // No reference: The part is in the same loader so the reference
+        // - if needed - was already added.
         addBuilder(name, declaration, declaration.charOffset);
       }
     }
@@ -1553,7 +1562,8 @@
     members.forEach(setParentAndCheckConflicts);
     constructors.forEach(setParentAndCheckConflicts);
     setters.forEach(setParentAndCheckConflicts);
-    addBuilder(className, classBuilder, nameOffset);
+    addBuilder(className, classBuilder, nameOffset,
+        reference: referencesFromClass?.reference);
   }
 
   Map<String, TypeVariableBuilder> checkTypeVariables(
@@ -1668,7 +1678,8 @@
     members.forEach(setParentAndCheckConflicts);
     constructors.forEach(setParentAndCheckConflicts);
     setters.forEach(setParentAndCheckConflicts);
-    addBuilder(extensionName, extensionBuilder, nameOffset);
+    addBuilder(extensionName, extensionBuilder, nameOffset,
+        reference: referenceFrom?.reference);
   }
 
   TypeBuilder applyMixins(TypeBuilder type, int startCharOffset, int charOffset,
@@ -1906,7 +1917,8 @@
         // pkg/analyzer/test/src/summary/resynthesize_kernel_test.dart can't
         // handle that :(
         application.cls.isAnonymousMixin = !isNamedMixinApplication;
-        addBuilder(fullname, application, charOffset);
+        addBuilder(fullname, application, charOffset,
+            reference: referencesFromClass?.reference);
         supertype = addNamedType(fullname, const NullabilityBuilder.omitted(),
             applicationTypeArguments, charOffset);
       }
@@ -2062,7 +2074,8 @@
         getterReferenceFrom,
         setterReferenceFrom);
     fieldBuilder.constInitializerToken = constInitializerToken;
-    addBuilder(name, fieldBuilder, charOffset);
+    addBuilder(name, fieldBuilder, charOffset,
+        reference: referenceFrom?.reference);
     if (type == null && initializerToken != null && fieldBuilder.next == null) {
       // Only the first one (the last one in the linked list of next pointers)
       // are added to the tree, had parent pointers and can infer correctly.
@@ -2114,7 +2127,8 @@
     metadataCollector?.setConstructorNameOffset(
         constructorBuilder.constructor, name);
     checkTypeVariables(typeVariables, constructorBuilder);
-    addBuilder(constructorName, constructorBuilder, charOffset);
+    addBuilder(constructorName, constructorBuilder, charOffset,
+        reference: referenceFrom?.reference);
     if (nativeMethodName != null) {
       addNativeMethod(constructorBuilder);
     }
@@ -2219,7 +2233,8 @@
     metadataCollector?.setDocumentationComment(
         procedureBuilder.procedure, documentationComment);
     checkTypeVariables(typeVariables, procedureBuilder);
-    addBuilder(name, procedureBuilder, charOffset);
+    addBuilder(name, procedureBuilder, charOffset,
+        reference: referenceFrom?.reference);
     if (nativeMethodName != null) {
       addNativeMethod(procedureBuilder);
     }
@@ -2319,7 +2334,8 @@
     currentTypeParameterScopeBuilder = savedDeclaration;
 
     factoryDeclaration.resolveTypes(procedureBuilder.typeVariables, this);
-    addBuilder(procedureName, procedureBuilder, charOffset);
+    addBuilder(procedureName, procedureBuilder, charOffset,
+        reference: referenceFrom?.reference);
     if (nativeMethodName != null) {
       addNativeMethod(procedureBuilder);
     }
@@ -2352,7 +2368,8 @@
         charEndOffset,
         referencesFromClass,
         referencesFromIndexedClass);
-    addBuilder(name, builder, charOffset);
+    addBuilder(name, builder, charOffset,
+        reference: referencesFromClass?.reference);
     metadataCollector?.setDocumentationComment(
         builder.cls, documentationComment);
   }
@@ -2379,7 +2396,8 @@
     // Nested declaration began in `OutlineBuilder.beginFunctionTypeAlias`.
     endNestedDeclaration(TypeParameterScopeKind.typedef, "#typedef")
         .resolveTypes(typeVariables, this);
-    addBuilder(name, typedefBuilder, charOffset);
+    addBuilder(name, typedefBuilder, charOffset,
+        reference: referenceFrom?.reference);
   }
 
   FunctionTypeBuilder addFunctionType(
diff --git a/pkg/front_end/lib/src/fasta/source/source_loader.dart b/pkg/front_end/lib/src/fasta/source/source_loader.dart
index 9a79867..9d3b7d3 100644
--- a/pkg/front_end/lib/src/fasta/source/source_loader.dart
+++ b/pkg/front_end/lib/src/fasta/source/source_loader.dart
@@ -40,6 +40,7 @@
         LibraryDependency,
         Nullability,
         ProcedureKind,
+        Reference,
         Supertype,
         TreeNode;
 
@@ -152,6 +153,11 @@
   ClassHierarchy hierarchy;
   CoreTypes _coreTypes;
 
+  /// For builders created with a reference, this maps from that reference to
+  /// that builder. This is used for looking up source builders when finalizing
+  /// exports in dill builders.
+  Map<Reference, Builder> buildersCreatedWithReferences = {};
+
   /// Used when checking whether a return type of an async function is valid.
   ///
   /// The said return type is valid if it's a subtype of [futureOfBottom].
diff --git a/pkg/front_end/lib/src/fasta/util/textual_outline.dart b/pkg/front_end/lib/src/fasta/util/textual_outline.dart
index a6c7251..7cb4916 100644
--- a/pkg/front_end/lib/src/fasta/util/textual_outline.dart
+++ b/pkg/front_end/lib/src/fasta/util/textual_outline.dart
@@ -9,6 +9,9 @@
 import 'package:_fe_analyzer_shared/src/parser/class_member_parser.dart'
     show ClassMemberParser;
 
+import 'package:_fe_analyzer_shared/src/parser/declaration_kind.dart'
+    show DeclarationKind;
+
 import 'package:_fe_analyzer_shared/src/scanner/scanner.dart'
     show ErrorToken, LanguageVersionToken, Scanner;
 
@@ -47,9 +50,12 @@
     if (token is ErrorToken) {
       return null;
     }
-    if (printed && token.offset > endOfLast) {
+    if (addLinebreak) {
+      sb.write("\n");
+    } else if (printed && token.offset > endOfLast) {
       sb.write(" ");
     }
+    addLinebreak = false;
 
     sb.write(token.lexeme);
     printed = true;
@@ -58,19 +64,19 @@
       if (token.lexeme == ";") {
         addLinebreak = true;
       } else if (token.endGroup != null &&
-          listener.endOffsets.contains(token.endGroup.offset)) {
+          (listener.nonClassEndOffsets.contains(token.endGroup.offset) ||
+              listener.classEndOffsets.contains(token.endGroup.offset))) {
         addLinebreak = true;
-      } else if (listener.endOffsets.contains(token.offset)) {
+      } else if (listener.nonClassEndOffsets.contains(token.offset) ||
+          listener.classEndOffsets.contains(token.offset)) {
         addLinebreak = true;
       }
     }
 
-    if (addLinebreak) sb.write("\n");
-    addLinebreak = false;
     if (token.isEof) break;
 
     if (token.endGroup != null &&
-        listener.endOffsets.contains(token.endGroup.offset)) {
+        listener.nonClassEndOffsets.contains(token.endGroup.offset)) {
       token = token.endGroup;
     } else {
       token = token.next;
@@ -86,17 +92,30 @@
 }
 
 class EndOffsetListener extends DirectiveListener {
-  Set<int> endOffsets = new Set<int>();
+  Set<int> nonClassEndOffsets = new Set<int>();
+  Set<int> classEndOffsets = new Set<int>();
 
   @override
   void endClassMethod(Token getOrSet, Token beginToken, Token beginParam,
       Token beginInitializers, Token endToken) {
-    endOffsets.add(endToken.offset);
+    nonClassEndOffsets.add(endToken.offset);
   }
 
   @override
   void endTopLevelMethod(Token beginToken, Token getOrSet, Token endToken) {
-    endOffsets.add(endToken.offset);
+    nonClassEndOffsets.add(endToken.offset);
+  }
+
+  @override
+  void endClassFactoryMethod(
+      Token beginToken, Token factoryKeyword, Token endToken) {
+    nonClassEndOffsets.add(endToken.offset);
+  }
+
+  @override
+  void endClassOrMixinBody(
+      DeclarationKind kind, int memberCount, Token beginToken, Token endToken) {
+    classEndOffsets.add(endToken.offset);
   }
 
   @override
diff --git a/pkg/front_end/test/incremental_dart2js_tester.dart b/pkg/front_end/test/incremental_dart2js_tester.dart
index 0eb21c7..b8f8b47 100644
--- a/pkg/front_end/test/incremental_dart2js_tester.dart
+++ b/pkg/front_end/test/incremental_dart2js_tester.dart
@@ -31,58 +31,50 @@
     }
   }
 
-  Stopwatch stopwatch = new Stopwatch()..start();
-  Uri input = Platform.script.resolve("../../compiler/bin/dart2js.dart");
-  CompilerOptions options = helper.getOptions(targetName: "VM");
-  helper.TestIncrementalCompiler compiler =
-      new helper.TestIncrementalCompiler(options, input);
-  compiler.useExperimentalInvalidation = useExperimentalInvalidation;
-  Component c = await compiler.computeDelta();
-  print("Compiled dart2js to Component with ${c.libraries.length} libraries "
-      "in ${stopwatch.elapsedMilliseconds} ms.");
-  stopwatch.reset();
+  Dart2jsTester dart2jsTester =
+      new Dart2jsTester(useExperimentalInvalidation, fast, addDebugBreaks);
+  await dart2jsTester.test();
+}
+
+class Dart2jsTester {
+  final bool useExperimentalInvalidation;
+  final bool fast;
+  final bool addDebugBreaks;
+
+  Stopwatch stopwatch = new Stopwatch();
   List<int> firstCompileData;
   Map<Uri, List<int>> libToData;
-  if (fast) {
-    libToData = {};
-    c.libraries.sort((l1, l2) {
-      return "${l1.fileUri}".compareTo("${l2.fileUri}");
-    });
-
-    c.problemsAsJson?.sort();
-
-    c.computeCanonicalNames();
-
-    for (Library library in c.libraries) {
-      library.additionalExports.sort((Reference r1, Reference r2) {
-        return "${r1.canonicalName}".compareTo("${r2.canonicalName}");
-      });
-      library.problemsAsJson?.sort();
-
-      List<int> libSerialized =
-          serializeComponent(c, filter: (l) => l == library);
-      libToData[library.importUri] = libSerialized;
-    }
-  } else {
-    firstCompileData = util.postProcess(c);
-  }
-  print("Serialized in ${stopwatch.elapsedMilliseconds} ms");
-  stopwatch.reset();
-
-  List<Uri> uris = c.uriToSource.values
-      .map((s) => s != null ? s.importUri : null)
-      .where((u) => u != null && u.scheme != "dart")
-      .toSet()
-      .toList();
-
-  c = null;
+  List<Uri> uris;
 
   List<Uri> diffs = new List<Uri>();
   Set<Uri> componentUris = new Set<Uri>();
 
-  Stopwatch localStopwatch = new Stopwatch()..start();
-  for (int i = 0; i < uris.length; i++) {
-    Uri uri = uris[i];
+  Dart2jsTester(
+      this.useExperimentalInvalidation, this.fast, this.addDebugBreaks);
+
+  void test() async {
+    helper.TestIncrementalCompiler compiler = await setup();
+
+    diffs = new List<Uri>();
+    componentUris = new Set<Uri>();
+
+    Stopwatch localStopwatch = new Stopwatch()..start();
+    for (int i = 0; i < uris.length; i++) {
+      Uri uri = uris[i];
+      await step(uri, i, compiler, localStopwatch);
+    }
+
+    print("A total of ${diffs.length} diffs:");
+    for (Uri uri in diffs) {
+      print(" - $uri");
+    }
+
+    print("Done after ${uris.length} recompiles in "
+        "${stopwatch.elapsedMilliseconds} ms");
+  }
+
+  Future step(Uri uri, int i, helper.TestIncrementalCompiler compiler,
+      Stopwatch localStopwatch) async {
     print("Invalidating $uri ($i)");
     compiler.invalidate(uri);
     localStopwatch.reset();
@@ -168,24 +160,65 @@
     print("-----");
   }
 
-  print("A total of ${diffs.length} diffs:");
-  for (Uri uri in diffs) {
-    print(" - $uri");
+  Future<helper.TestIncrementalCompiler> setup() async {
+    stopwatch.reset();
+    stopwatch.start();
+    Uri input = Platform.script.resolve("../../compiler/bin/dart2js.dart");
+    CompilerOptions options = helper.getOptions(targetName: "VM");
+    helper.TestIncrementalCompiler compiler =
+        new helper.TestIncrementalCompiler(options, input);
+    compiler.useExperimentalInvalidation = useExperimentalInvalidation;
+    Component c = await compiler.computeDelta();
+    print("Compiled dart2js to Component with ${c.libraries.length} libraries "
+        "in ${stopwatch.elapsedMilliseconds} ms.");
+    stopwatch.reset();
+    if (fast) {
+      libToData = {};
+      c.libraries.sort((l1, l2) {
+        return "${l1.fileUri}".compareTo("${l2.fileUri}");
+      });
+
+      c.problemsAsJson?.sort();
+
+      c.computeCanonicalNames();
+
+      for (Library library in c.libraries) {
+        library.additionalExports.sort((Reference r1, Reference r2) {
+          return "${r1.canonicalName}".compareTo("${r2.canonicalName}");
+        });
+        library.problemsAsJson?.sort();
+
+        List<int> libSerialized =
+            serializeComponent(c, filter: (l) => l == library);
+        libToData[library.importUri] = libSerialized;
+      }
+    } else {
+      firstCompileData = util.postProcess(c);
+    }
+    print("Serialized in ${stopwatch.elapsedMilliseconds} ms");
+    stopwatch.reset();
+
+    uris = c.uriToSource.values
+        .map((s) => s != null ? s.importUri : null)
+        .where((u) => u != null && u.scheme != "dart")
+        .toSet()
+        .toList();
+
+    c = null;
+
+    return compiler;
   }
 
-  print("Done after ${uris.length} recompiles in "
-      "${stopwatch.elapsedMilliseconds} ms");
-}
-
-bool isEqual(List<int> a, List<int> b) {
-  int length = a.length;
-  if (b.length != length) {
-    return false;
-  }
-  for (int i = 0; i < length; ++i) {
-    if (a[i] != b[i]) {
+  bool isEqual(List<int> a, List<int> b) {
+    int length = a.length;
+    if (b.length != length) {
       return false;
     }
+    for (int i = 0; i < length; ++i) {
+      if (a[i] != b[i]) {
+        return false;
+      }
+    }
+    return true;
   }
-  return true;
 }
diff --git a/pkg/front_end/test/incremental_load_from_dill_suite.dart b/pkg/front_end/test/incremental_load_from_dill_suite.dart
index 79b25aa..97a33b5 100644
--- a/pkg/front_end/test/incremental_load_from_dill_suite.dart
+++ b/pkg/front_end/test/incremental_load_from_dill_suite.dart
@@ -166,7 +166,7 @@
           "forceLateLoweringForTesting",
           "incrementalSerialization"
         ]);
-        await newWorldTest(
+        await new NewWorldTest().newWorldTest(
           data,
           context,
           map["worlds"],
@@ -317,519 +317,542 @@
   return moduleResult;
 }
 
-Future<Null> newWorldTest(
-    TestData data,
-    Context context,
-    List worlds,
-    Map modules,
-    bool omitPlatform,
-    String targetName,
-    bool forceLateLoweringForTesting,
-    bool incrementalSerialization) async {
-  final Uri sdkRoot = computePlatformBinariesLocation(forceBuildDir: true);
-  final Uri base = Uri.parse("org-dartlang-test:///");
-  final Uri sdkSummary = base.resolve("vm_platform_strong.dill");
-  final Uri initializeFrom = base.resolve("initializeFrom.dill");
-  Uri platformUri = sdkRoot.resolve("vm_platform_strong.dill");
-  final List<int> sdkSummaryData =
-      await new File.fromUri(platformUri).readAsBytes();
-
-  List<int> newestWholeComponentData;
+class NewWorldTest {
+  // These are fields in a class to make it easier to track down memory leaks
+  // via the leak detector test.
   Component newestWholeComponent;
-  MemoryFileSystem fs;
-  Map<String, String> sourceFiles;
-  CompilerOptions options;
-  TestIncrementalCompiler compiler;
-  IncrementalSerializer incrementalSerializer;
-
-  Map<String, List<int>> moduleData;
-  Map<String, Component> moduleComponents;
   Component sdk;
-  if (modules != null) {
-    moduleData = await createModules(
-        modules, sdkSummaryData, targetName, forceLateLoweringForTesting);
-    sdk = newestWholeComponent = new Component();
-    new BinaryBuilder(sdkSummaryData, filename: null, disableLazyReading: false)
-        .readComponent(newestWholeComponent);
-  }
+  Component component;
+  Component component2;
+  Component component3;
 
-  int worldNum = 0;
-  for (YamlMap world in worlds) {
-    worldNum++;
-    print("----------------");
-    print("World #$worldNum");
-    print("----------------");
-    List<Component> modulesToUse;
-    if (world["modules"] != null) {
-      moduleComponents ??= new Map<String, Component>();
+  Future<Null> newWorldTest(
+      TestData data,
+      Context context,
+      List worlds,
+      Map modules,
+      bool omitPlatform,
+      String targetName,
+      bool forceLateLoweringForTesting,
+      bool incrementalSerialization) async {
+    final Uri sdkRoot = computePlatformBinariesLocation(forceBuildDir: true);
+    final Uri base = Uri.parse("org-dartlang-test:///");
+    final Uri sdkSummary = base.resolve("vm_platform_strong.dill");
+    final Uri initializeFrom = base.resolve("initializeFrom.dill");
+    Uri platformUri = sdkRoot.resolve("vm_platform_strong.dill");
+    final List<int> sdkSummaryData =
+        await new File.fromUri(platformUri).readAsBytes();
 
-      sdk.adoptChildren();
-      for (Component c in moduleComponents.values) {
-        c.adoptChildren();
-      }
+    List<int> newestWholeComponentData;
+    MemoryFileSystem fs;
+    Map<String, String> sourceFiles;
+    CompilerOptions options;
+    TestIncrementalCompiler compiler;
+    IncrementalSerializer incrementalSerializer;
 
-      modulesToUse = new List<Component>();
-      for (String moduleName in world["modules"]) {
-        Component moduleComponent = moduleComponents[moduleName];
-        if (moduleComponent != null) {
-          modulesToUse.add(moduleComponent);
+    Map<String, List<int>> moduleData;
+    Map<String, Component> moduleComponents;
+
+    if (modules != null) {
+      moduleData = await createModules(
+          modules, sdkSummaryData, targetName, forceLateLoweringForTesting);
+      sdk = newestWholeComponent = new Component();
+      new BinaryBuilder(sdkSummaryData,
+              filename: null, disableLazyReading: false)
+          .readComponent(newestWholeComponent);
+    }
+
+    int worldNum = 0;
+    for (YamlMap world in worlds) {
+      worldNum++;
+      print("----------------");
+      print("World #$worldNum");
+      print("----------------");
+      List<Component> modulesToUse;
+      if (world["modules"] != null) {
+        moduleComponents ??= new Map<String, Component>();
+
+        sdk.adoptChildren();
+        for (Component c in moduleComponents.values) {
+          c.adoptChildren();
+        }
+
+        modulesToUse = new List<Component>();
+        for (String moduleName in world["modules"]) {
+          Component moduleComponent = moduleComponents[moduleName];
+          if (moduleComponent != null) {
+            modulesToUse.add(moduleComponent);
+          }
+        }
+        for (String moduleName in world["modules"]) {
+          Component moduleComponent = moduleComponents[moduleName];
+          if (moduleComponent == null) {
+            moduleComponent = new Component(nameRoot: sdk.root);
+            new BinaryBuilder(moduleData[moduleName],
+                    filename: null,
+                    disableLazyReading: false,
+                    alwaysCreateNewNamedNodes: true)
+                .readComponent(moduleComponent);
+            moduleComponents[moduleName] = moduleComponent;
+            modulesToUse.add(moduleComponent);
+          }
         }
       }
-      for (String moduleName in world["modules"]) {
-        Component moduleComponent = moduleComponents[moduleName];
-        if (moduleComponent == null) {
-          moduleComponent = new Component(nameRoot: sdk.root);
-          new BinaryBuilder(moduleData[moduleName],
-                  filename: null,
-                  disableLazyReading: false,
-                  alwaysCreateNewNamedNodes: true)
-              .readComponent(moduleComponent);
-          moduleComponents[moduleName] = moduleComponent;
-          modulesToUse.add(moduleComponent);
-        }
-      }
-    }
 
-    bool brandNewWorld = true;
-    if (world["worldType"] == "updated") {
-      brandNewWorld = false;
-    }
-    bool noFullComponent = false;
-    if (world["noFullComponent"] == true) {
-      noFullComponent = true;
-    }
+      bool brandNewWorld = true;
+      if (world["worldType"] == "updated") {
+        brandNewWorld = false;
+      }
+      bool noFullComponent = false;
+      if (world["noFullComponent"] == true) {
+        noFullComponent = true;
+      }
 
-    if (brandNewWorld) {
-      fs = new MemoryFileSystem(base);
-    }
-    fs.entityForUri(sdkSummary).writeAsBytesSync(sdkSummaryData);
-    bool expectInitializeFromDill = false;
-    if (newestWholeComponentData != null &&
-        newestWholeComponentData.isNotEmpty) {
-      fs
-          .entityForUri(initializeFrom)
-          .writeAsBytesSync(newestWholeComponentData);
-      expectInitializeFromDill = true;
-    }
-    if (world["expectInitializeFromDill"] != null) {
-      expectInitializeFromDill = world["expectInitializeFromDill"];
-    }
-    if (brandNewWorld) {
-      sourceFiles = new Map<String, String>.from(world["sources"]);
-    } else {
-      sourceFiles.addAll(
-          new Map<String, String>.from(world["sources"] ?? <String, String>{}));
-    }
-    Uri packagesUri;
-    for (String filename in sourceFiles.keys) {
-      String data = sourceFiles[filename] ?? "";
-      Uri uri = base.resolve(filename);
-      if (filename == ".packages") {
-        packagesUri = uri;
+      if (brandNewWorld) {
+        fs = new MemoryFileSystem(base);
       }
-      fs.entityForUri(uri).writeAsStringSync(data);
-    }
-    if (world["dotPackagesFile"] != null) {
-      packagesUri = base.resolve(world["dotPackagesFile"]);
-    }
-
-    if (brandNewWorld) {
-      options = getOptions(
-          targetName: targetName,
-          forceLateLoweringForTesting: forceLateLoweringForTesting);
-      options.fileSystem = fs;
-      options.sdkRoot = null;
-      options.sdkSummary = sdkSummary;
-      options.omitPlatform = omitPlatform != false;
-      if (world["experiments"] != null) {
-        Map<ExperimentalFlag, bool> experimentalFlags = parseExperimentalFlags(
-            parseExperimentalArguments([world["experiments"]]),
-            onError: (e) => throw "Error on parsing experiments flags: $e");
-        options.experimentalFlags = experimentalFlags;
+      fs.entityForUri(sdkSummary).writeAsBytesSync(sdkSummaryData);
+      bool expectInitializeFromDill = false;
+      if (newestWholeComponentData != null &&
+          newestWholeComponentData.isNotEmpty) {
+        fs
+            .entityForUri(initializeFrom)
+            .writeAsBytesSync(newestWholeComponentData);
+        expectInitializeFromDill = true;
       }
-    }
-    if (packagesUri != null) {
-      options.packagesFileUri = packagesUri;
-    }
-    bool gotError = false;
-    final Set<String> formattedErrors = Set<String>();
-    bool gotWarning = false;
-    final Set<String> formattedWarnings = Set<String>();
-
-    options.onDiagnostic = (DiagnosticMessage message) {
-      String stringId = message.ansiFormatted.join("\n");
-      if (message is FormattedMessage) {
-        stringId = message.toJsonString();
-      } else if (message is DiagnosticMessageFromJson) {
-        stringId = message.toJsonString();
+      if (world["expectInitializeFromDill"] != null) {
+        expectInitializeFromDill = world["expectInitializeFromDill"];
       }
-      if (message.severity == Severity.error) {
-        gotError = true;
-        if (!formattedErrors.add(stringId)) {
-          Expect.fail("Got the same message twice: ${stringId}");
-        }
-      } else if (message.severity == Severity.warning) {
-        gotWarning = true;
-        if (!formattedWarnings.add(stringId)) {
-          Expect.fail("Got the same message twice: ${stringId}");
-        }
-      }
-    };
-
-    List<Uri> entries;
-    if (world["entry"] is String) {
-      entries = [base.resolve(world["entry"])];
-    } else {
-      entries = new List<Uri>();
-      List<dynamic> entryList = world["entry"];
-      for (String entry in entryList) {
-        entries.add(base.resolve(entry));
-      }
-    }
-    bool outlineOnly = world["outlineOnly"] == true;
-    bool skipOutlineBodyCheck = world["skipOutlineBodyCheck"] == true;
-    if (brandNewWorld) {
-      if (incrementalSerialization == true) {
-        incrementalSerializer = new IncrementalSerializer();
-      }
-      if (world["fromComponent"] == true) {
-        compiler = new TestIncrementalCompiler.fromComponent(
-            options,
-            entries.first,
-            (modulesToUse != null) ? sdk : newestWholeComponent,
-            outlineOnly,
-            incrementalSerializer);
+      if (brandNewWorld) {
+        sourceFiles = new Map<String, String>.from(world["sources"]);
       } else {
-        compiler = new TestIncrementalCompiler(options, entries.first,
-            initializeFrom, outlineOnly, incrementalSerializer);
+        sourceFiles.addAll(new Map<String, String>.from(
+            world["sources"] ?? <String, String>{}));
+      }
+      Uri packagesUri;
+      for (String filename in sourceFiles.keys) {
+        String data = sourceFiles[filename] ?? "";
+        Uri uri = base.resolve(filename);
+        if (filename == ".packages") {
+          packagesUri = uri;
+        }
+        fs.entityForUri(uri).writeAsStringSync(data);
+      }
+      if (world["dotPackagesFile"] != null) {
+        packagesUri = base.resolve(world["dotPackagesFile"]);
+      }
 
-        if (modulesToUse != null) {
-          throw "You probably shouldn't do this! "
-              "Any modules will have another sdk loaded!";
+      if (brandNewWorld) {
+        options = getOptions(
+            targetName: targetName,
+            forceLateLoweringForTesting: forceLateLoweringForTesting);
+        options.fileSystem = fs;
+        options.sdkRoot = null;
+        options.sdkSummary = sdkSummary;
+        options.omitPlatform = omitPlatform != false;
+        if (world["experiments"] != null) {
+          Map<ExperimentalFlag, bool> experimentalFlags =
+              parseExperimentalFlags(
+                  parseExperimentalArguments([world["experiments"]]),
+                  onError: (e) =>
+                      throw "Error on parsing experiments flags: $e");
+          options.experimentalFlags = experimentalFlags;
         }
       }
-    }
-
-    compiler.useExperimentalInvalidation = false;
-    if (world["useExperimentalInvalidation"] == true) {
-      compiler.useExperimentalInvalidation = true;
-    }
-
-    List<Uri> invalidated = new List<Uri>();
-    if (world["invalidate"] != null) {
-      for (String filename in world["invalidate"]) {
-        Uri uri = base.resolve(filename);
-        invalidated.add(uri);
-        compiler.invalidate(uri);
+      if (packagesUri != null) {
+        options.packagesFileUri = packagesUri;
       }
-    }
+      bool gotError = false;
+      final Set<String> formattedErrors = Set<String>();
+      bool gotWarning = false;
+      final Set<String> formattedWarnings = Set<String>();
 
-    if (modulesToUse != null) {
-      compiler.setModulesToLoadOnNextComputeDelta(modulesToUse);
-      compiler.invalidateAllSources();
-      compiler.trackNeededDillLibraries = true;
-    }
+      options.onDiagnostic = (DiagnosticMessage message) {
+        String stringId = message.ansiFormatted.join("\n");
+        if (message is FormattedMessage) {
+          stringId = message.toJsonString();
+        } else if (message is DiagnosticMessageFromJson) {
+          stringId = message.toJsonString();
+        }
+        if (message.severity == Severity.error) {
+          gotError = true;
+          if (!formattedErrors.add(stringId)) {
+            Expect.fail("Got the same message twice: ${stringId}");
+          }
+        } else if (message.severity == Severity.warning) {
+          gotWarning = true;
+          if (!formattedWarnings.add(stringId)) {
+            Expect.fail("Got the same message twice: ${stringId}");
+          }
+        }
+      };
 
-    Stopwatch stopwatch = new Stopwatch()..start();
-    Component component = await compiler.computeDelta(
-        entryPoints: entries,
-        fullComponent: brandNewWorld ? false : (noFullComponent ? false : true),
-        simulateTransformer: world["simulateTransformer"]);
-    if (outlineOnly && !skipOutlineBodyCheck) {
-      for (Library lib in component.libraries) {
-        for (Class c in lib.classes) {
-          for (Procedure p in c.procedures) {
+      List<Uri> entries;
+      if (world["entry"] is String) {
+        entries = [base.resolve(world["entry"])];
+      } else {
+        entries = new List<Uri>();
+        List<dynamic> entryList = world["entry"];
+        for (String entry in entryList) {
+          entries.add(base.resolve(entry));
+        }
+      }
+      bool outlineOnly = world["outlineOnly"] == true;
+      bool skipOutlineBodyCheck = world["skipOutlineBodyCheck"] == true;
+      if (brandNewWorld) {
+        if (incrementalSerialization == true) {
+          incrementalSerializer = new IncrementalSerializer();
+        }
+        if (world["fromComponent"] == true) {
+          compiler = new TestIncrementalCompiler.fromComponent(
+              options,
+              entries.first,
+              (modulesToUse != null) ? sdk : newestWholeComponent,
+              outlineOnly,
+              incrementalSerializer);
+        } else {
+          compiler = new TestIncrementalCompiler(options, entries.first,
+              initializeFrom, outlineOnly, incrementalSerializer);
+
+          if (modulesToUse != null) {
+            throw "You probably shouldn't do this! "
+                "Any modules will have another sdk loaded!";
+          }
+        }
+      }
+
+      compiler.useExperimentalInvalidation = false;
+      if (world["useExperimentalInvalidation"] == true) {
+        compiler.useExperimentalInvalidation = true;
+      }
+
+      List<Uri> invalidated = new List<Uri>();
+      if (world["invalidate"] != null) {
+        for (String filename in world["invalidate"]) {
+          Uri uri = base.resolve(filename);
+          invalidated.add(uri);
+          compiler.invalidate(uri);
+        }
+      }
+
+      if (modulesToUse != null) {
+        compiler.setModulesToLoadOnNextComputeDelta(modulesToUse);
+        compiler.invalidateAllSources();
+        compiler.trackNeededDillLibraries = true;
+      }
+
+      Stopwatch stopwatch = new Stopwatch()..start();
+      component = await compiler.computeDelta(
+          entryPoints: entries,
+          fullComponent:
+              brandNewWorld ? false : (noFullComponent ? false : true),
+          simulateTransformer: world["simulateTransformer"]);
+      if (outlineOnly && !skipOutlineBodyCheck) {
+        for (Library lib in component.libraries) {
+          for (Class c in lib.classes) {
+            for (Procedure p in c.procedures) {
+              if (p.function.body != null &&
+                  p.function.body is! EmptyStatement) {
+                throw "Got body (${p.function.body.runtimeType})";
+              }
+            }
+          }
+          for (Procedure p in lib.procedures) {
             if (p.function.body != null && p.function.body is! EmptyStatement) {
               throw "Got body (${p.function.body.runtimeType})";
             }
           }
         }
-        for (Procedure p in lib.procedures) {
-          if (p.function.body != null && p.function.body is! EmptyStatement) {
-            throw "Got body (${p.function.body.runtimeType})";
+      }
+      performErrorAndWarningCheck(
+          world, gotError, formattedErrors, gotWarning, formattedWarnings);
+      util.throwOnEmptyMixinBodies(component);
+      await util.throwOnInsufficientUriToSource(component,
+          fileSystem: gotError ? null : fs);
+      print("Compile took ${stopwatch.elapsedMilliseconds} ms");
+
+      checkExpectedContent(world, component);
+      checkNeededDillLibraries(world, compiler.neededDillLibraries, base);
+
+      if (!noFullComponent) {
+        Set<Library> allLibraries = new Set<Library>();
+        for (Library lib in component.libraries) {
+          computeAllReachableLibrariesFor(lib, allLibraries);
+        }
+        if (allLibraries.length != component.libraries.length) {
+          Expect.fail("Expected for the reachable stuff to be equal to "
+              "${component.libraries} but it was $allLibraries");
+        }
+        Set<Library> tooMany = allLibraries.toSet()
+          ..removeAll(component.libraries);
+        if (tooMany.isNotEmpty) {
+          Expect.fail("Expected for the reachable stuff to be equal to "
+              "${component.libraries} but these were there too: $tooMany "
+              "(and others were missing)");
+        }
+      }
+
+      newestWholeComponentData = util.postProcess(component);
+      newestWholeComponent = component;
+      String actualSerialized = componentToStringSdkFiltered(component);
+      print("*****\n\ncomponent:\n"
+          "${actualSerialized}\n\n\n");
+
+      if (world["uriToSourcesDoesntInclude"] != null) {
+        for (String filename in world["uriToSourcesDoesntInclude"]) {
+          Uri uri = base.resolve(filename);
+          if (component.uriToSource[uri] != null) {
+            throw "Expected no uriToSource for $uri but found "
+                "${component.uriToSource[uri]}";
           }
         }
       }
-    }
-    performErrorAndWarningCheck(
-        world, gotError, formattedErrors, gotWarning, formattedWarnings);
-    util.throwOnEmptyMixinBodies(component);
-    await util.throwOnInsufficientUriToSource(component,
-        fileSystem: gotError ? null : fs);
-    print("Compile took ${stopwatch.elapsedMilliseconds} ms");
-
-    checkExpectedContent(world, component);
-    checkNeededDillLibraries(world, compiler.neededDillLibraries, base);
-
-    if (!noFullComponent) {
-      Set<Library> allLibraries = new Set<Library>();
-      for (Library lib in component.libraries) {
-        computeAllReachableLibrariesFor(lib, allLibraries);
-      }
-      if (allLibraries.length != component.libraries.length) {
-        Expect.fail("Expected for the reachable stuff to be equal to "
-            "${component.libraries} but it was $allLibraries");
-      }
-      Set<Library> tooMany = allLibraries.toSet()
-        ..removeAll(component.libraries);
-      if (tooMany.isNotEmpty) {
-        Expect.fail("Expected for the reachable stuff to be equal to "
-            "${component.libraries} but these were there too: $tooMany "
-            "(and others were missing)");
-      }
-    }
-
-    newestWholeComponentData = util.postProcess(component);
-    newestWholeComponent = component;
-    String actualSerialized = componentToStringSdkFiltered(component);
-    print("*****\n\ncomponent:\n"
-        "${actualSerialized}\n\n\n");
-
-    if (world["uriToSourcesDoesntInclude"] != null) {
-      for (String filename in world["uriToSourcesDoesntInclude"]) {
-        Uri uri = base.resolve(filename);
-        if (component.uriToSource[uri] != null) {
-          throw "Expected no uriToSource for $uri but found "
-              "${component.uriToSource[uri]}";
+      if (world["uriToSourcesOnlyIncludes"] != null) {
+        Set<Uri> allowed = {};
+        for (String filename in world["uriToSourcesOnlyIncludes"]) {
+          Uri uri = base.resolve(filename);
+          allowed.add(uri);
+        }
+        for (Uri uri in component.uriToSource.keys) {
+          // null is always there, so allow it implicitly.
+          // Dart scheme uris too.
+          if (uri == null || uri.scheme == "org-dartlang-sdk") continue;
+          if (!allowed.contains(uri)) {
+            throw "Expected no uriToSource for $uri but found "
+                "${component.uriToSource[uri]}";
+          }
         }
       }
-    }
-    if (world["uriToSourcesOnlyIncludes"] != null) {
-      Set<Uri> allowed = {};
-      for (String filename in world["uriToSourcesOnlyIncludes"]) {
-        Uri uri = base.resolve(filename);
-        allowed.add(uri);
-      }
-      for (Uri uri in component.uriToSource.keys) {
-        // null is always there, so allow it implicitly.
-        // Dart scheme uris too.
-        if (uri == null || uri.scheme == "org-dartlang-sdk") continue;
-        if (!allowed.contains(uri)) {
-          throw "Expected no uriToSource for $uri but found "
-              "${component.uriToSource[uri]}";
-        }
-      }
-    }
 
-    checkExpectFile(data, worldNum, context, actualSerialized);
-    checkClassHierarchy(compiler, component, data, worldNum, context);
+      checkExpectFile(data, worldNum, context, actualSerialized);
+      checkClassHierarchy(compiler, component, data, worldNum, context);
 
-    int nonSyntheticLibraries = countNonSyntheticLibraries(component);
-    int nonSyntheticPlatformLibraries =
-        countNonSyntheticPlatformLibraries(component);
-    int syntheticLibraries = countSyntheticLibraries(component);
-    if (world["expectsPlatform"] == true) {
-      if (nonSyntheticPlatformLibraries < 5) {
-        throw "Expected to have at least 5 platform libraries "
-            "(actually, the entire sdk), "
-            "but got $nonSyntheticPlatformLibraries.";
-      }
-    } else {
-      if (nonSyntheticPlatformLibraries != 0) {
-        throw "Expected to have 0 platform libraries "
-            "but got $nonSyntheticPlatformLibraries.";
-      }
-    }
-    if (world["expectedLibraryCount"] != null) {
-      if (nonSyntheticLibraries - nonSyntheticPlatformLibraries !=
-          world["expectedLibraryCount"]) {
-        throw "Expected ${world["expectedLibraryCount"]} non-synthetic "
-            "libraries, got "
-            "${nonSyntheticLibraries - nonSyntheticPlatformLibraries} "
-            "(not counting platform libraries)";
-      }
-    }
-    if (world["expectedSyntheticLibraryCount"] != null) {
-      if (syntheticLibraries != world["expectedSyntheticLibraryCount"]) {
-        throw "Expected ${world["expectedSyntheticLibraryCount"]} synthetic "
-            "libraries, got ${syntheticLibraries}";
-      }
-    }
-
-    if (world["expectsRebuildBodiesOnly"] != null) {
-      bool didRebuildBodiesOnly = compiler.rebuildBodiesCount > 0;
-      Expect.equals(world["expectsRebuildBodiesOnly"], didRebuildBodiesOnly,
-          "Whether we expected to rebuild bodies only.");
-    }
-
-    if (!noFullComponent) {
-      List<Library> entryLib = component.libraries
-          .where((Library lib) =>
-              entries.contains(lib.importUri) || entries.contains(lib.fileUri))
-          .toList();
-      if (entryLib.length != entries.length) {
-        throw "Expected the entries to become libraries. "
-            "Got ${entryLib.length} libraries for the expected "
-            "${entries.length} entries.";
-      }
-    }
-    if (compiler.initializedFromDill != expectInitializeFromDill) {
-      throw "Expected that initializedFromDill would be "
-          "$expectInitializeFromDill but was ${compiler.initializedFromDill}";
-    }
-
-    if (incrementalSerialization == true && compiler.initializedFromDill) {
-      Expect.isTrue(compiler.initializedIncrementalSerializer);
-    } else {
-      Expect.isFalse(compiler.initializedIncrementalSerializer);
-    }
-
-    if (world["checkInvalidatedFiles"] != false) {
-      Set<Uri> filteredInvalidated =
-          compiler.getFilteredInvalidatedImportUrisForTesting(invalidated);
-      if (world["invalidate"] != null) {
-        Expect.equals(
-            world["invalidate"].length, filteredInvalidated?.length ?? 0);
-        List expectedInvalidatedUri = world["expectedInvalidatedUri"];
-        if (expectedInvalidatedUri != null) {
-          Expect.setEquals(expectedInvalidatedUri.map((s) => base.resolve(s)),
-              filteredInvalidated);
+      int nonSyntheticLibraries = countNonSyntheticLibraries(component);
+      int nonSyntheticPlatformLibraries =
+          countNonSyntheticPlatformLibraries(component);
+      int syntheticLibraries = countSyntheticLibraries(component);
+      if (world["expectsPlatform"] == true) {
+        if (nonSyntheticPlatformLibraries < 5) {
+          throw "Expected to have at least 5 platform libraries "
+              "(actually, the entire sdk), "
+              "but got $nonSyntheticPlatformLibraries.";
         }
       } else {
-        Expect.isNull(filteredInvalidated);
-        Expect.isNull(world["expectedInvalidatedUri"]);
-      }
-    }
-    List<int> incrementalSerializationBytes = checkIncrementalSerialization(
-        incrementalSerialization, component, incrementalSerializer, world);
-
-    Set<String> prevFormattedErrors = formattedErrors.toSet();
-    Set<String> prevFormattedWarnings = formattedWarnings.toSet();
-
-    clearPrevErrorsEtc() {
-      gotError = false;
-      formattedErrors.clear();
-      gotWarning = false;
-      formattedWarnings.clear();
-    }
-
-    if (!noFullComponent) {
-      clearPrevErrorsEtc();
-      Component component2 = await compiler.computeDelta(
-          entryPoints: entries,
-          fullComponent: true,
-          simulateTransformer: world["simulateTransformer"]);
-      performErrorAndWarningCheck(
-          world, gotError, formattedErrors, gotWarning, formattedWarnings);
-      List<int> thisWholeComponent = util.postProcess(component2);
-      print("*****\n\ncomponent2:\n"
-          "${componentToStringSdkFiltered(component2)}\n\n\n");
-      checkIsEqual(newestWholeComponentData, thisWholeComponent);
-      checkErrorsAndWarnings(prevFormattedErrors, formattedErrors,
-          prevFormattedWarnings, formattedWarnings);
-
-      List<int> incrementalSerializationBytes2 = checkIncrementalSerialization(
-          incrementalSerialization, component2, incrementalSerializer, world);
-
-      if ((incrementalSerializationBytes == null &&
-              incrementalSerializationBytes2 != null) ||
-          (incrementalSerializationBytes != null &&
-              incrementalSerializationBytes2 == null)) {
-        throw "Incremental serialization gave results in one instance, "
-            "but not another.";
-      }
-
-      if (incrementalSerializationBytes != null) {
-        checkIsEqual(
-            incrementalSerializationBytes, incrementalSerializationBytes2);
-      }
-    }
-
-    if (world["expressionCompilation"] != null) {
-      List compilations;
-      if (world["expressionCompilation"] is List) {
-        compilations = world["expressionCompilation"];
-      } else {
-        compilations = [world["expressionCompilation"]];
-      }
-      for (Map compilation in compilations) {
-        clearPrevErrorsEtc();
-        bool expectErrors = compilation["errors"] ?? false;
-        bool expectWarnings = compilation["warnings"] ?? false;
-        Uri uri = base.resolve(compilation["uri"]);
-        String expression = compilation["expression"];
-        await compiler.compileExpression(expression, {}, [], "debugExpr", uri);
-        if (gotError && !expectErrors) {
-          throw "Got error(s) on expression compilation: ${formattedErrors}.";
-        } else if (!gotError && expectErrors) {
-          throw "Didn't get any errors.";
-        }
-        if (gotWarning && !expectWarnings) {
-          throw "Got warning(s) on expression compilation: "
-              "${formattedWarnings}.";
-        } else if (!gotWarning && expectWarnings) {
-          throw "Didn't get any warnings.";
+        if (nonSyntheticPlatformLibraries != 0) {
+          throw "Expected to have 0 platform libraries "
+              "but got $nonSyntheticPlatformLibraries.";
         }
       }
-    }
-
-    if (!noFullComponent && incrementalSerialization == true) {
-      // Do compile from scratch and compare.
-      clearPrevErrorsEtc();
-      TestIncrementalCompiler compilerFromScratch;
-
-      IncrementalSerializer incrementalSerializer2;
-      if (incrementalSerialization == true) {
-        incrementalSerializer2 = new IncrementalSerializer();
+      if (world["expectedLibraryCount"] != null) {
+        if (nonSyntheticLibraries - nonSyntheticPlatformLibraries !=
+            world["expectedLibraryCount"]) {
+          throw "Expected ${world["expectedLibraryCount"]} non-synthetic "
+              "libraries, got "
+              "${nonSyntheticLibraries - nonSyntheticPlatformLibraries} "
+              "(not counting platform libraries)";
+        }
+      }
+      if (world["expectedSyntheticLibraryCount"] != null) {
+        if (syntheticLibraries != world["expectedSyntheticLibraryCount"]) {
+          throw "Expected ${world["expectedSyntheticLibraryCount"]} synthetic "
+              "libraries, got ${syntheticLibraries}";
+        }
       }
 
-      if (world["fromComponent"] == true || modulesToUse != null) {
-        compilerFromScratch = new TestIncrementalCompiler.fromComponent(
-            options, entries.first, sdk, outlineOnly, incrementalSerializer2);
+      if (world["expectsRebuildBodiesOnly"] != null) {
+        bool didRebuildBodiesOnly = compiler.rebuildBodiesCount > 0;
+        Expect.equals(world["expectsRebuildBodiesOnly"], didRebuildBodiesOnly,
+            "Whether we expected to rebuild bodies only.");
+      }
+
+      if (!noFullComponent) {
+        List<Library> entryLib = component.libraries
+            .where((Library lib) =>
+                entries.contains(lib.importUri) ||
+                entries.contains(lib.fileUri))
+            .toList();
+        if (entryLib.length != entries.length) {
+          throw "Expected the entries to become libraries. "
+              "Got ${entryLib.length} libraries for the expected "
+              "${entries.length} entries.";
+        }
+      }
+      if (compiler.initializedFromDill != expectInitializeFromDill) {
+        throw "Expected that initializedFromDill would be "
+            "$expectInitializeFromDill but was ${compiler.initializedFromDill}";
+      }
+
+      if (incrementalSerialization == true && compiler.initializedFromDill) {
+        Expect.isTrue(compiler.initializedIncrementalSerializer);
       } else {
-        compilerFromScratch = new TestIncrementalCompiler(
-            options, entries.first, null, outlineOnly, incrementalSerializer2);
+        Expect.isFalse(compiler.initializedIncrementalSerializer);
       }
 
-      if (modulesToUse != null) {
-        compilerFromScratch.setModulesToLoadOnNextComputeDelta(modulesToUse);
-        compilerFromScratch.invalidateAllSources();
-        compilerFromScratch.trackNeededDillLibraries = true;
-      }
-
-      Stopwatch stopwatch = new Stopwatch()..start();
-      Component component3 = await compilerFromScratch.computeDelta(
-          entryPoints: entries,
-          simulateTransformer: world["simulateTransformer"]);
-      compilerFromScratch = null;
-      performErrorAndWarningCheck(
-          world, gotError, formattedErrors, gotWarning, formattedWarnings);
-      util.throwOnEmptyMixinBodies(component3);
-      await util.throwOnInsufficientUriToSource(component3);
-      print("Compile took ${stopwatch.elapsedMilliseconds} ms");
-
-      util.postProcess(component3);
-      print("*****\n\ncomponent3:\n"
-          "${componentToStringSdkFiltered(component3)}\n\n\n");
-      checkErrorsAndWarnings(prevFormattedErrors, formattedErrors,
-          prevFormattedWarnings, formattedWarnings);
-
-      List<int> incrementalSerializationBytes3 = checkIncrementalSerialization(
-          incrementalSerialization, component3, incrementalSerializer2, world);
-
-      if ((incrementalSerializationBytes == null &&
-              incrementalSerializationBytes3 != null) ||
-          (incrementalSerializationBytes != null &&
-              incrementalSerializationBytes3 == null)) {
-        throw "Incremental serialization gave results in one instance, "
-            "but not another.";
-      }
-
-      if (incrementalSerializationBytes != null) {
-        if (world["brandNewIncrementalSerializationAllowDifferent"] == true) {
-          // Don't check for equality when we allow it to be different
-          // (e.g. when the old one contains more, and the new one doesn't).
+      if (world["checkInvalidatedFiles"] != false) {
+        Set<Uri> filteredInvalidated =
+            compiler.getFilteredInvalidatedImportUrisForTesting(invalidated);
+        if (world["invalidate"] != null) {
+          Expect.equals(
+              world["invalidate"].length, filteredInvalidated?.length ?? 0);
+          List expectedInvalidatedUri = world["expectedInvalidatedUri"];
+          if (expectedInvalidatedUri != null) {
+            Expect.setEquals(expectedInvalidatedUri.map((s) => base.resolve(s)),
+                filteredInvalidated);
+          }
         } else {
-          checkIsEqual(
-              incrementalSerializationBytes, incrementalSerializationBytes3);
+          Expect.isNull(filteredInvalidated);
+          Expect.isNull(world["expectedInvalidatedUri"]);
         }
-        newestWholeComponentData = incrementalSerializationBytes;
       }
-    }
+      List<int> incrementalSerializationBytes = checkIncrementalSerialization(
+          incrementalSerialization, component, incrementalSerializer, world);
 
-    if (context.breakBetween) {
-      debugger();
-      print("Continuing after debug break");
+      Set<String> prevFormattedErrors = formattedErrors.toSet();
+      Set<String> prevFormattedWarnings = formattedWarnings.toSet();
+
+      clearPrevErrorsEtc() {
+        gotError = false;
+        formattedErrors.clear();
+        gotWarning = false;
+        formattedWarnings.clear();
+      }
+
+      if (!noFullComponent) {
+        clearPrevErrorsEtc();
+        component2 = await compiler.computeDelta(
+            entryPoints: entries,
+            fullComponent: true,
+            simulateTransformer: world["simulateTransformer"]);
+        performErrorAndWarningCheck(
+            world, gotError, formattedErrors, gotWarning, formattedWarnings);
+        List<int> thisWholeComponent = util.postProcess(component2);
+        print("*****\n\ncomponent2:\n"
+            "${componentToStringSdkFiltered(component2)}\n\n\n");
+        checkIsEqual(newestWholeComponentData, thisWholeComponent);
+        checkErrorsAndWarnings(prevFormattedErrors, formattedErrors,
+            prevFormattedWarnings, formattedWarnings);
+        newestWholeComponent = component2;
+
+        List<int> incrementalSerializationBytes2 =
+            checkIncrementalSerialization(incrementalSerialization, component2,
+                incrementalSerializer, world);
+
+        if ((incrementalSerializationBytes == null &&
+                incrementalSerializationBytes2 != null) ||
+            (incrementalSerializationBytes != null &&
+                incrementalSerializationBytes2 == null)) {
+          throw "Incremental serialization gave results in one instance, "
+              "but not another.";
+        }
+
+        if (incrementalSerializationBytes != null) {
+          checkIsEqual(
+              incrementalSerializationBytes, incrementalSerializationBytes2);
+        }
+      }
+
+      if (world["expressionCompilation"] != null) {
+        List compilations;
+        if (world["expressionCompilation"] is List) {
+          compilations = world["expressionCompilation"];
+        } else {
+          compilations = [world["expressionCompilation"]];
+        }
+        for (Map compilation in compilations) {
+          clearPrevErrorsEtc();
+          bool expectErrors = compilation["errors"] ?? false;
+          bool expectWarnings = compilation["warnings"] ?? false;
+          Uri uri = base.resolve(compilation["uri"]);
+          String expression = compilation["expression"];
+          await compiler.compileExpression(
+              expression, {}, [], "debugExpr", uri);
+          if (gotError && !expectErrors) {
+            throw "Got error(s) on expression compilation: ${formattedErrors}.";
+          } else if (!gotError && expectErrors) {
+            throw "Didn't get any errors.";
+          }
+          if (gotWarning && !expectWarnings) {
+            throw "Got warning(s) on expression compilation: "
+                "${formattedWarnings}.";
+          } else if (!gotWarning && expectWarnings) {
+            throw "Didn't get any warnings.";
+          }
+        }
+      }
+
+      if (!noFullComponent && incrementalSerialization == true) {
+        // Do compile from scratch and compare.
+        clearPrevErrorsEtc();
+        TestIncrementalCompiler compilerFromScratch;
+
+        IncrementalSerializer incrementalSerializer2;
+        if (incrementalSerialization == true) {
+          incrementalSerializer2 = new IncrementalSerializer();
+        }
+
+        if (world["fromComponent"] == true || modulesToUse != null) {
+          compilerFromScratch = new TestIncrementalCompiler.fromComponent(
+              options, entries.first, sdk, outlineOnly, incrementalSerializer2);
+        } else {
+          compilerFromScratch = new TestIncrementalCompiler(options,
+              entries.first, null, outlineOnly, incrementalSerializer2);
+        }
+
+        if (modulesToUse != null) {
+          compilerFromScratch.setModulesToLoadOnNextComputeDelta(modulesToUse);
+          compilerFromScratch.invalidateAllSources();
+          compilerFromScratch.trackNeededDillLibraries = true;
+        }
+
+        Stopwatch stopwatch = new Stopwatch()..start();
+        component3 = await compilerFromScratch.computeDelta(
+            entryPoints: entries,
+            simulateTransformer: world["simulateTransformer"]);
+        compilerFromScratch = null;
+        performErrorAndWarningCheck(
+            world, gotError, formattedErrors, gotWarning, formattedWarnings);
+        util.throwOnEmptyMixinBodies(component3);
+        await util.throwOnInsufficientUriToSource(component3);
+        print("Compile took ${stopwatch.elapsedMilliseconds} ms");
+
+        util.postProcess(component3);
+        print("*****\n\ncomponent3:\n"
+            "${componentToStringSdkFiltered(component3)}\n\n\n");
+        checkErrorsAndWarnings(prevFormattedErrors, formattedErrors,
+            prevFormattedWarnings, formattedWarnings);
+
+        List<int> incrementalSerializationBytes3 =
+            checkIncrementalSerialization(incrementalSerialization, component3,
+                incrementalSerializer2, world);
+
+        if ((incrementalSerializationBytes == null &&
+                incrementalSerializationBytes3 != null) ||
+            (incrementalSerializationBytes != null &&
+                incrementalSerializationBytes3 == null)) {
+          throw "Incremental serialization gave results in one instance, "
+              "but not another.";
+        }
+
+        if (incrementalSerializationBytes != null) {
+          if (world["brandNewIncrementalSerializationAllowDifferent"] == true) {
+            // Don't check for equality when we allow it to be different
+            // (e.g. when the old one contains more, and the new one doesn't).
+          } else {
+            checkIsEqual(
+                incrementalSerializationBytes, incrementalSerializationBytes3);
+          }
+          newestWholeComponentData = incrementalSerializationBytes;
+        }
+      }
+
+      component = null;
+      component2 = null;
+      component3 = null;
+
+      if (context.breakBetween) {
+        debugger();
+        print("Continuing after debug break");
+      }
     }
   }
 }
@@ -1171,10 +1194,10 @@
       libContent.add("Class ${c.name}");
     }
     for (Procedure p in lib.procedures) {
-      libContent.add("Procedure ${p.name}");
+      libContent.add("Procedure ${p.name.name}");
     }
     for (Field f in lib.fields) {
-      libContent.add("Field ${f.name}");
+      libContent.add("Field ${f.name.name}");
     }
   }
   return actualContent;
diff --git a/pkg/front_end/test/spell_checking_list_tests.txt b/pkg/front_end/test/spell_checking_list_tests.txt
index ef6d66d..ac51d61 100644
--- a/pkg/front_end/test/spell_checking_list_tests.txt
+++ b/pkg/front_end/test/spell_checking_list_tests.txt
@@ -122,6 +122,7 @@
 depended
 depfile
 desc
+detector
 deviation
 dfast
 dictionaries
@@ -138,6 +139,7 @@
 disconnect
 discovering
 dispatcher
+dispose
 dist
 doctype
 doesnt
@@ -185,6 +187,7 @@
 fisk
 five
 floor
+foos
 forbidden
 forces
 foreign
@@ -381,6 +384,7 @@
 splitting
 sqrt
 sssp
+stats
 std
 stdio
 strip
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 7d3c6ba..040e1d5 100644
--- a/pkg/front_end/test/vm_service_for_leak_detection.dart
+++ b/pkg/front_end/test/vm_service_for_leak_detection.dart
@@ -26,11 +26,23 @@
             ["_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);
+
+  // heapHelper.start([
+  //   "--enable-asserts",
+  //   Platform.script.resolve("incremental_dart2js_tester.dart").toString(),
+  //   "--addDebugBreaks",
+  //   "--fast",
+  //   "--experimental",
+  // ]);
   heapHelper.start([
-    Platform.script.resolve("incremental_dart2js_tester.dart").toString(),
-    "--fast",
-    "--addDebugBreaks",
+    "--enable-asserts",
+    Platform.script.resolve("incremental_load_from_dill_suite.dart").toString(),
+    "-DaddDebugBreaks=true",
+    // "--",
+    // "incremental_load_from_dill/no_outline_change_...",
   ]);
 }
diff --git a/pkg/front_end/test/vm_service_heap_finder.dart b/pkg/front_end/test/vm_service_heap_finder.dart
new file mode 100644
index 0000000..f199b1e
--- /dev/null
+++ b/pkg/front_end/test/vm_service_heap_finder.dart
@@ -0,0 +1,40 @@
+import "dart:io";
+import "vm_service_heap_helper.dart";
+
+class Foo {
+  final String x;
+  final int y;
+
+  Foo(this.x, this.y);
+}
+
+main() async {
+  List<Foo> foos = [];
+  foos.add(new Foo("hello", 42));
+  foos.add(new Foo("world", 43));
+  foos.add(new Foo("!", 44));
+  String connectTo = ask("Connect to");
+  VMServiceHeapHelperBase vm = VMServiceHeapHelperBase();
+  await vm.connect(Uri.parse(connectTo));
+  String isolateId = await vm.getIsolateId();
+  String classToFind = ask("Find what class");
+  await vm.printAllocationProfile(isolateId, filter: classToFind);
+  String fieldToFilter = ask("Filter on what field");
+  Set<String> fieldValues = {};
+  while (true) {
+    String fieldValue = ask("Look for value in field (empty to stop)");
+    if (fieldValue == "") break;
+    fieldValues.add(fieldValue);
+  }
+
+  await vm.filterAndPrintInstances(
+      isolateId, classToFind, fieldToFilter, fieldValues);
+
+  await vm.disconnect();
+  print("Disconnect done!");
+}
+
+String ask(String question) {
+  stdout.write("$question: ");
+  return stdin.readLineSync();
+}
diff --git a/pkg/front_end/test/vm_service_heap_helper.dart b/pkg/front_end/test/vm_service_heap_helper.dart
index b4a80f1..e23bdbd 100644
--- a/pkg/front_end/test/vm_service_heap_helper.dart
+++ b/pkg/front_end/test/vm_service_heap_helper.dart
@@ -1,4 +1,5 @@
 import "dart:convert";
+import "dart:developer";
 import "dart:io";
 
 import "package:vm_service/vm_service.dart" as vmService;
@@ -6,230 +7,20 @@
 
 import "dijkstras_sssp_algorithm.dart";
 
-class VMServiceHeapHelper {
-  Process _process;
+class VMServiceHeapHelperBase {
   vmService.VmService _serviceClient;
-  bool _started = false;
-  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;
 
-  VMServiceHeapHelper(List<Interest> interests, List<Interest> prettyPrints,
-      this.throwOnPossibleLeak) {
-    if (interests.isEmpty) throw "Empty list of interests given";
-    for (Interest interest in interests) {
-      Map<String, List<String>> classToFields = _interests[interest.uri];
-      if (classToFields == null) {
-        classToFields = Map<String, List<String>>();
-        _interests[interest.uri] = classToFields;
-      }
-      List<String> fields = classToFields[interest.className];
-      if (fields == null) {
-        fields = new List<String>();
-        classToFields[interest.className] = fields;
-      }
-      fields.addAll(interest.fieldNames);
-    }
-    for (Interest interest in prettyPrints) {
-      Map<String, List<String>> classToFields = _prettyPrints[interest.uri];
-      if (classToFields == null) {
-        classToFields = Map<String, List<String>>();
-        _prettyPrints[interest.uri] = classToFields;
-      }
-      List<String> fields = classToFields[interest.className];
-      if (fields == null) {
-        fields = new List<String>();
-        classToFields[interest.className] = fields;
-      }
-      fields.addAll(interest.fieldNames);
-    }
-  }
+  VMServiceHeapHelperBase();
 
-  void start(List<String> scriptAndArgs) async {
-    if (_started) throw "Already started";
-    _started = true;
-    _process = await Process.start(
-        Platform.resolvedExecutable,
-        ["--pause_isolates_on_start", "--enable-vm-service=0"]
-          ..addAll(scriptAndArgs));
-    _process.stdout
-        .transform(utf8.decoder)
-        .transform(new LineSplitter())
-        .listen((line) {
-      const kObservatoryListening = 'Observatory listening on ';
-      if (line.startsWith(kObservatoryListening)) {
-        Uri observatoryUri =
-            Uri.parse(line.substring(kObservatoryListening.length));
-        _gotObservatoryUri(observatoryUri);
-      }
-      stdout.writeln("> $line");
-    });
-    _process.stderr
-        .transform(utf8.decoder)
-        .transform(new LineSplitter())
-        .listen((line) {
-      stderr.writeln("> $line");
-    });
-  }
-
-  void _gotObservatoryUri(Uri observatoryUri) async {
+  Future connect(Uri observatoryUri) async {
     String wsUriString =
         'ws://${observatoryUri.authority}${observatoryUri.path}ws';
     _serviceClient = await vmService.vmServiceConnectUri(wsUriString,
         log: const StdOutLog());
-    await _run();
   }
 
-  void _run() async {
-    vmService.VM vm = await _serviceClient.getVM();
-    if (vm.isolates.length != 1) {
-      throw "Expected 1 isolate, got ${vm.isolates.length}";
-    }
-    vmService.IsolateRef isolateRef = vm.isolates.single;
-    await _forceGC(isolateRef.id);
-
-    assert(await _isPausedAtStart(isolateRef.id));
-    await _serviceClient.resume(isolateRef.id);
-
-    int iterationNumber = 1;
-    while (true) {
-      await _waitUntilPaused(isolateRef.id);
-      print("Iteration: #$iterationNumber");
-      iterationNumber++;
-      await _forceGC(isolateRef.id);
-
-      vmService.HeapSnapshotGraph heapSnapshotGraph =
-          await vmService.HeapSnapshotGraph.getSnapshot(
-              _serviceClient, isolateRef);
-      HeapGraph graph = convertHeapGraph(heapSnapshotGraph);
-
-      Set<String> seenPrints = {};
-      Set<String> duplicatePrints = {};
-      Map<String, List<HeapGraphElement>> groupedByToString = {};
-      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.instances) {
-              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);
-            }
-          }
-        }
-      }
-      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)");
-          print("");
-        }
-        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:");
-            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("---------------------------");
-          }
-        }
-
-        if (throwOnPossibleLeak) throw "Possible leak detected.";
-      }
-      await _serviceClient.resume(isolateRef.id);
-    }
-  }
-
-  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 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;
-        }
-        return 1;
-      },
-    );
-
-    return (HeapGraphElement target) {
-      return result.getPathFromTarget(elements[source], elements[target]);
-    };
+  Future disconnect() async {
+    await _serviceClient.dispose();
   }
 
   Future<void> _waitUntilPaused(String isolateId) async {
@@ -310,6 +101,329 @@
       }
     }
   }
+
+  Future<void> printAllocationProfile(String isolateId, {String filter}) async {
+    await _waitUntilIsolateIsRunnable(isolateId);
+    vmService.AllocationProfile allocationProfile =
+        await _serviceClient.getAllocationProfile(isolateId);
+    for (vmService.ClassHeapStats member in allocationProfile.members) {
+      if (filter != null) {
+        if (member.classRef.name != filter) continue;
+      } else {
+        if (member.classRef.name == "") continue;
+        if (member.instancesCurrent == 0) continue;
+      }
+      vmService.Class c =
+          await _serviceClient.getObject(isolateId, member.classRef.id);
+      if (c.location?.script?.uri == null) continue;
+      print("${member.classRef.name}: ${member.instancesCurrent}");
+    }
+  }
+
+  Future<void> filterAndPrintInstances(String isolateId, String filter,
+      String fieldName, Set<String> fieldValues) async {
+    await _waitUntilIsolateIsRunnable(isolateId);
+    vmService.AllocationProfile allocationProfile =
+        await _serviceClient.getAllocationProfile(isolateId);
+    for (vmService.ClassHeapStats member in allocationProfile.members) {
+      if (member.classRef.name != filter) continue;
+      vmService.Class c =
+          await _serviceClient.getObject(isolateId, member.classRef.id);
+      if (c.location?.script?.uri == null) continue;
+      print("${member.classRef.name}: ${member.instancesCurrent}");
+      print(c.location.script.uri);
+
+      vmService.InstanceSet instances = await _serviceClient.getInstances(
+          isolateId, member.classRef.id, 10000);
+      int instanceNum = 0;
+      for (vmService.ObjRef instance in instances.instances) {
+        instanceNum++;
+        var receivedObject =
+            await _serviceClient.getObject(isolateId, instance.id);
+        if (receivedObject is! vmService.Instance) continue;
+        vmService.Instance object = receivedObject;
+        for (vmService.BoundField field in object.fields) {
+          if (field.decl.name == fieldName) {
+            if (field.value is vmService.Sentinel) continue;
+            var receivedValue =
+                await _serviceClient.getObject(isolateId, field.value.id);
+            if (receivedValue is! vmService.Instance) continue;
+            String value = (receivedValue as vmService.Instance).valueAsString;
+            if (!fieldValues.contains(value)) continue;
+            print("${instanceNum}: ${field.decl.name}: "
+                "${value} --- ${instance.id}");
+          }
+        }
+      }
+    }
+    print("Done!");
+  }
+
+  Future<String> getIsolateId() async {
+    vmService.VM vm = await _serviceClient.getVM();
+    if (vm.isolates.length != 1) {
+      throw "Expected 1 isolate, got ${vm.isolates.length}";
+    }
+    vmService.IsolateRef isolateRef = vm.isolates.single;
+    return isolateRef.id;
+  }
+}
+
+class VMServiceHeapHelper extends VMServiceHeapHelperBase {
+  Process _process;
+
+  bool _started = false;
+  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;
+
+  VMServiceHeapHelper(List<Interest> interests, List<Interest> prettyPrints,
+      this.throwOnPossibleLeak) {
+    if (interests.isEmpty) throw "Empty list of interests given";
+    for (Interest interest in interests) {
+      Map<String, List<String>> classToFields = _interests[interest.uri];
+      if (classToFields == null) {
+        classToFields = Map<String, List<String>>();
+        _interests[interest.uri] = classToFields;
+      }
+      List<String> fields = classToFields[interest.className];
+      if (fields == null) {
+        fields = new List<String>();
+        classToFields[interest.className] = fields;
+      }
+      fields.addAll(interest.fieldNames);
+    }
+    for (Interest interest in prettyPrints) {
+      Map<String, List<String>> classToFields = _prettyPrints[interest.uri];
+      if (classToFields == null) {
+        classToFields = Map<String, List<String>>();
+        _prettyPrints[interest.uri] = classToFields;
+      }
+      List<String> fields = classToFields[interest.className];
+      if (fields == null) {
+        fields = new List<String>();
+        classToFields[interest.className] = fields;
+      }
+      fields.addAll(interest.fieldNames);
+    }
+  }
+
+  void start(List<String> scriptAndArgs) async {
+    if (_started) throw "Already started";
+    _started = true;
+    _process = await Process.start(
+        Platform.resolvedExecutable,
+        ["--pause_isolates_on_start", "--enable-vm-service=0"]
+          ..addAll(scriptAndArgs));
+    _process.stdout
+        .transform(utf8.decoder)
+        .transform(new LineSplitter())
+        .listen((line) {
+      const kObservatoryListening = 'Observatory listening on ';
+      if (line.startsWith(kObservatoryListening)) {
+        Uri observatoryUri =
+            Uri.parse(line.substring(kObservatoryListening.length));
+        _setupAndRun(observatoryUri);
+      }
+      stdout.writeln("> $line");
+    });
+    _process.stderr
+        .transform(utf8.decoder)
+        .transform(new LineSplitter())
+        .listen((line) {
+      stderr.writeln("> $line");
+    });
+  }
+
+  void _setupAndRun(Uri observatoryUri) async {
+    await connect(observatoryUri);
+    await _run();
+  }
+
+  void _run() async {
+    vmService.VM vm = await _serviceClient.getVM();
+    if (vm.isolates.length != 1) {
+      throw "Expected 1 isolate, got ${vm.isolates.length}";
+    }
+    vmService.IsolateRef isolateRef = vm.isolates.single;
+    await _forceGC(isolateRef.id);
+
+    assert(await _isPausedAtStart(isolateRef.id));
+    await _serviceClient.resume(isolateRef.id);
+
+    int iterationNumber = 1;
+    while (true) {
+      await _waitUntilPaused(isolateRef.id);
+      print("Iteration: #$iterationNumber");
+      iterationNumber++;
+      await _forceGC(isolateRef.id);
+
+      vmService.HeapSnapshotGraph heapSnapshotGraph =
+          await vmService.HeapSnapshotGraph.getSnapshot(
+              _serviceClient, isolateRef);
+      HeapGraph graph = convertHeapGraph(heapSnapshotGraph);
+
+      Set<String> seenPrints = {};
+      Set<String> duplicatePrints = {};
+      Map<String, List<HeapGraphElement>> groupedByToString = {};
+      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.instances) {
+              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);
+            }
+          }
+        }
+      }
+      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)");
+          print("");
+        }
+        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("---------------------------");
+          }
+        }
+
+        if (throwOnPossibleLeak) {
+          debugger();
+          throw "Possible leak detected.";
+        }
+      }
+      await _serviceClient.resume(isolateRef.id);
+    }
+  }
+
+  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;
+      },
+    );
+
+    return (HeapGraphElement target) {
+      return result.getPathFromTarget(elements[source], elements[target]);
+    };
+  }
 }
 
 class Interest {
@@ -412,7 +526,8 @@
           if (fields != null) {
             return "${c.name}[" +
                 fields.map((field) {
-                  return "$field: ${me.getField(field).getPrettyPrint(prettyPrints)}";
+                  return "$field: "
+                      "${me.getField(field).getPrettyPrint(prettyPrints)}";
                 }).join(", ") +
                 "]";
           }
diff --git a/pkg/front_end/testcases/incremental_initialize_from_dill/no_outline_change_18.yaml b/pkg/front_end/testcases/incremental_initialize_from_dill/no_outline_change_18.yaml
index 5016781..c7db57e 100644
--- a/pkg/front_end/testcases/incremental_initialize_from_dill/no_outline_change_18.yaml
+++ b/pkg/front_end/testcases/incremental_initialize_from_dill/no_outline_change_18.yaml
@@ -41,4 +41,4 @@
           print("exports!")
         }
     expectedLibraryCount: 3
-    expectsRebuildBodiesOnly: true
+    expectsRebuildBodiesOnly: false # For now, libraries with errors cannot have bodies rebuild.
diff --git a/pkg/front_end/testcases/incremental_initialize_from_dill/no_outline_change_25.yaml b/pkg/front_end/testcases/incremental_initialize_from_dill/no_outline_change_25.yaml
index 6ada515..b561e9d 100644
--- a/pkg/front_end/testcases/incremental_initialize_from_dill/no_outline_change_25.yaml
+++ b/pkg/front_end/testcases/incremental_initialize_from_dill/no_outline_change_25.yaml
@@ -48,4 +48,4 @@
           }
         }
     expectedLibraryCount: 1
-    expectsRebuildBodiesOnly: true
+    expectsRebuildBodiesOnly: false # For now, libraries with errors cannot have bodies rebuild.
diff --git a/pkg/front_end/testcases/incremental_initialize_from_dill/no_outline_change_26.yaml b/pkg/front_end/testcases/incremental_initialize_from_dill/no_outline_change_26.yaml
index 5f07ff2..9986dfb 100644
--- a/pkg/front_end/testcases/incremental_initialize_from_dill/no_outline_change_26.yaml
+++ b/pkg/front_end/testcases/incremental_initialize_from_dill/no_outline_change_26.yaml
@@ -40,4 +40,4 @@
           new A1.foo();
         }
     expectedLibraryCount: 1
-    expectsRebuildBodiesOnly: true
+    expectsRebuildBodiesOnly: false # For now, libraries with errors cannot have bodies rebuild.
diff --git a/pkg/front_end/testcases/incremental_initialize_from_dill/no_outline_change_34.yaml b/pkg/front_end/testcases/incremental_initialize_from_dill/no_outline_change_34.yaml
index 1771b25..eaa4d06 100644
--- a/pkg/front_end/testcases/incremental_initialize_from_dill/no_outline_change_34.yaml
+++ b/pkg/front_end/testcases/incremental_initialize_from_dill/no_outline_change_34.yaml
@@ -95,7 +95,6 @@
         }
     expectedLibraryCount: 3
     expectsRebuildBodiesOnly: true
-
   - entry: main.dart
     useExperimentalInvalidation: true
     worldType: updated
diff --git a/pkg/front_end/testcases/incremental_initialize_from_dill/no_outline_change_34.yaml.world.4.expect b/pkg/front_end/testcases/incremental_initialize_from_dill/no_outline_change_34.yaml.world.4.expect
index 52f52b5..366f447 100644
--- a/pkg/front_end/testcases/incremental_initialize_from_dill/no_outline_change_34.yaml.world.4.expect
+++ b/pkg/front_end/testcases/incremental_initialize_from_dill/no_outline_change_34.yaml.world.4.expect
@@ -1,6 +1,8 @@
 main = <No Member>;
 library from "org-dartlang-test:///lib1.dart" as lib1 {
-additionalExports = (main::main,
+additionalExports = (main::Extension|get#method,
+  main::Extension|method,
+  main::main,
   main::Class,
   main::Extension)
 
diff --git a/pkg/front_end/testcases/incremental_initialize_from_dill/no_outline_change_35.yaml b/pkg/front_end/testcases/incremental_initialize_from_dill/no_outline_change_35.yaml
new file mode 100644
index 0000000..d3b6f53
--- /dev/null
+++ b/pkg/front_end/testcases/incremental_initialize_from_dill/no_outline_change_35.yaml
@@ -0,0 +1,81 @@
+# Copyright (c) 2019, 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.md file.
+
+# Compile an application, change a file, but don't change the outline.
+# Test FFI compilation.
+
+type: newworld
+worlds:
+  - entry: main.dart
+    useExperimentalInvalidation: true
+    sources:
+      main.dart: |
+        import 'lib.dart';
+
+        main() {
+          Coordinate coordinate = new Coordinate.allocate(42.0, 42.0, null);
+          print(coordinate.x);
+          print(coordinate.y);
+          print(coordinate.next);
+        }
+      lib.dart: |
+        import 'dart:ffi';
+        class Coordinate extends Struct {
+          @Double()
+          double x;
+
+          @Double()
+          double y;
+
+          Pointer<Coordinate> next;
+
+          factory Coordinate.allocate(double x, double y, Pointer<Coordinate> next) {
+            return null;
+          }
+        }
+    expectedLibraryCount: 2
+  - entry: main.dart
+    useExperimentalInvalidation: true
+    worldType: updated
+    expectInitializeFromDill: false
+    invalidate:
+      - main.dart
+    sources:
+      main.dart: |
+        import 'lib.dart';
+
+        main() {
+          Coordinate coordinate = new Coordinate.allocate(42.0, 42.0, null);
+          print(coordinate.x);
+          print(coordinate.y);
+          print(coordinate.next);
+          print("Done!");
+        }
+    expectedLibraryCount: 2
+    expectsRebuildBodiesOnly: true
+  - entry: main.dart
+    useExperimentalInvalidation: true
+    worldType: updated
+    expectInitializeFromDill: false
+    invalidate:
+      - lib.dart
+    sources:
+      lib.dart: |
+        import 'dart:ffi';
+        class Coordinate extends Struct {
+          @Double()
+          double x;
+
+          @Double()
+          double y;
+
+          Pointer<Coordinate> next;
+
+          factory Coordinate.allocate(double x, double y, Pointer<Coordinate> next) {
+            print("hello");
+            return null;
+          }
+        }
+    expectedLibraryCount: 2
+    expectsRebuildBodiesOnly: true
diff --git a/pkg/front_end/testcases/incremental_initialize_from_dill/no_outline_change_35.yaml.world.1.expect b/pkg/front_end/testcases/incremental_initialize_from_dill/no_outline_change_35.yaml.world.1.expect
new file mode 100644
index 0000000..748c4c0
--- /dev/null
+++ b/pkg/front_end/testcases/incremental_initialize_from_dill/no_outline_change_35.yaml.world.1.expect
@@ -0,0 +1,60 @@
+main = <No Member>;
+library from "org-dartlang-test:///lib.dart" as lib {
+
+  import "dart:ffi";
+
+  @#C3
+  class Coordinate extends dart.ffi::Struct {
+    @#C3
+    static final field dart.core::int* #sizeOf = (#C6).{dart.core::List::[]}(dart.ffi::_abi());
+    @#C3
+    constructor #fromPointer(dynamic #pointer) → dynamic
+      : super dart.ffi::Struct::_fromPointer(#pointer)
+      ;
+    static factory allocate(dart.core::double* x, dart.core::double* y, dart.ffi::Pointer<lib::Coordinate*>* next) → lib::Coordinate* {
+      return null;
+    }
+    get #_ptr_x() → dart.ffi::Pointer<dart.ffi::Double*>*
+      return this.{dart.ffi::Struct::_addressOf}.{dart.ffi::Pointer::cast}<dart.ffi::Double*>();
+    get x() → dart.core::double*
+      return dart.ffi::_loadDouble(this.{lib::Coordinate::#_ptr_x}, #C7);
+    set x(dart.core::double* #v) → void
+      return dart.ffi::_storeDouble(this.{lib::Coordinate::#_ptr_x}, #C7, #v);
+    get #_ptr_y() → dart.ffi::Pointer<dart.ffi::Double*>*
+      return this.{dart.ffi::Struct::_addressOf}.{dart.ffi::Pointer::_offsetBy}((#C9).{dart.core::List::[]}(dart.ffi::_abi())).{dart.ffi::Pointer::cast}<dart.ffi::Double*>();
+    get y() → dart.core::double*
+      return dart.ffi::_loadDouble(this.{lib::Coordinate::#_ptr_y}, #C7);
+    set y(dart.core::double* #v) → void
+      return dart.ffi::_storeDouble(this.{lib::Coordinate::#_ptr_y}, #C7, #v);
+    get #_ptr_next() → dart.ffi::Pointer<dart.ffi::Pointer<lib::Coordinate*>*>*
+      return this.{dart.ffi::Struct::_addressOf}.{dart.ffi::Pointer::_offsetBy}((#C11).{dart.core::List::[]}(dart.ffi::_abi())).{dart.ffi::Pointer::cast}<dart.ffi::Pointer<lib::Coordinate*>*>();
+    get next() → dart.ffi::Pointer<lib::Coordinate*>*
+      return dart.ffi::_loadPointer<dart.ffi::Pointer<lib::Coordinate*>*>(this.{lib::Coordinate::#_ptr_next}, #C7);
+    set next(dart.ffi::Pointer<lib::Coordinate*>* #v) → void
+      return dart.ffi::_storePointer<dart.ffi::Pointer<lib::Coordinate*>*>(this.{lib::Coordinate::#_ptr_next}, #C7, #v);
+  }
+}
+library from "org-dartlang-test:///main.dart" as main {
+
+  import "org-dartlang-test:///lib.dart";
+
+  static method main() → dynamic {
+    lib::Coordinate* coordinate = lib::Coordinate::allocate(42.0, 42.0, null);
+    dart.core::print(coordinate.{lib::Coordinate::x});
+    dart.core::print(coordinate.{lib::Coordinate::y});
+    dart.core::print(coordinate.{lib::Coordinate::next});
+  }
+}
+constants  {
+  #C1 = "vm:entry-point"
+  #C2 = null
+  #C3 = dart.core::pragma {name:#C1, options:#C2}
+  #C4 = 24
+  #C5 = 20
+  #C6 = <dart.core::int*>[#C4, #C5, #C4]
+  #C7 = 0
+  #C8 = 8
+  #C9 = <dart.core::int*>[#C8, #C8, #C8]
+  #C10 = 16
+  #C11 = <dart.core::int*>[#C10, #C10, #C10]
+}
diff --git a/pkg/front_end/testcases/incremental_initialize_from_dill/no_outline_change_35.yaml.world.2.expect b/pkg/front_end/testcases/incremental_initialize_from_dill/no_outline_change_35.yaml.world.2.expect
new file mode 100644
index 0000000..b1805b0
--- /dev/null
+++ b/pkg/front_end/testcases/incremental_initialize_from_dill/no_outline_change_35.yaml.world.2.expect
@@ -0,0 +1,61 @@
+main = <No Member>;
+library from "org-dartlang-test:///lib.dart" as lib {
+
+  import "dart:ffi";
+
+  @#C3
+  class Coordinate extends dart.ffi::Struct {
+    @#C3
+    static final field dart.core::int* #sizeOf = (#C6).{dart.core::List::[]}(dart.ffi::_abi());
+    @#C3
+    constructor #fromPointer(dynamic #pointer) → dynamic
+      : super dart.ffi::Struct::_fromPointer(#pointer)
+      ;
+    static factory allocate(dart.core::double* x, dart.core::double* y, dart.ffi::Pointer<lib::Coordinate*>* next) → lib::Coordinate* {
+      return null;
+    }
+    get #_ptr_x() → dart.ffi::Pointer<dart.ffi::Double*>*
+      return this.{dart.ffi::Struct::_addressOf}.{dart.ffi::Pointer::cast}<dart.ffi::Double*>();
+    get x() → dart.core::double*
+      return dart.ffi::_loadDouble(this.{lib::Coordinate::#_ptr_x}, #C7);
+    set x(dart.core::double* #v) → void
+      return dart.ffi::_storeDouble(this.{lib::Coordinate::#_ptr_x}, #C7, #v);
+    get #_ptr_y() → dart.ffi::Pointer<dart.ffi::Double*>*
+      return this.{dart.ffi::Struct::_addressOf}.{dart.ffi::Pointer::_offsetBy}((#C9).{dart.core::List::[]}(dart.ffi::_abi())).{dart.ffi::Pointer::cast}<dart.ffi::Double*>();
+    get y() → dart.core::double*
+      return dart.ffi::_loadDouble(this.{lib::Coordinate::#_ptr_y}, #C7);
+    set y(dart.core::double* #v) → void
+      return dart.ffi::_storeDouble(this.{lib::Coordinate::#_ptr_y}, #C7, #v);
+    get #_ptr_next() → dart.ffi::Pointer<dart.ffi::Pointer<lib::Coordinate*>*>*
+      return this.{dart.ffi::Struct::_addressOf}.{dart.ffi::Pointer::_offsetBy}((#C11).{dart.core::List::[]}(dart.ffi::_abi())).{dart.ffi::Pointer::cast}<dart.ffi::Pointer<lib::Coordinate*>*>();
+    get next() → dart.ffi::Pointer<lib::Coordinate*>*
+      return dart.ffi::_loadPointer<dart.ffi::Pointer<lib::Coordinate*>*>(this.{lib::Coordinate::#_ptr_next}, #C7);
+    set next(dart.ffi::Pointer<lib::Coordinate*>* #v) → void
+      return dart.ffi::_storePointer<dart.ffi::Pointer<lib::Coordinate*>*>(this.{lib::Coordinate::#_ptr_next}, #C7, #v);
+  }
+}
+library from "org-dartlang-test:///main.dart" as main {
+
+  import "org-dartlang-test:///lib.dart";
+
+  static method main() → dynamic {
+    lib::Coordinate* coordinate = lib::Coordinate::allocate(42.0, 42.0, null);
+    dart.core::print(coordinate.{lib::Coordinate::x});
+    dart.core::print(coordinate.{lib::Coordinate::y});
+    dart.core::print(coordinate.{lib::Coordinate::next});
+    dart.core::print("Done!");
+  }
+}
+constants  {
+  #C1 = "vm:entry-point"
+  #C2 = null
+  #C3 = dart.core::pragma {name:#C1, options:#C2}
+  #C4 = 24
+  #C5 = 20
+  #C6 = <dart.core::int*>[#C4, #C5, #C4]
+  #C7 = 0
+  #C8 = 8
+  #C9 = <dart.core::int*>[#C8, #C8, #C8]
+  #C10 = 16
+  #C11 = <dart.core::int*>[#C10, #C10, #C10]
+}
diff --git a/pkg/front_end/testcases/incremental_initialize_from_dill/no_outline_change_35.yaml.world.3.expect b/pkg/front_end/testcases/incremental_initialize_from_dill/no_outline_change_35.yaml.world.3.expect
new file mode 100644
index 0000000..32cc854
--- /dev/null
+++ b/pkg/front_end/testcases/incremental_initialize_from_dill/no_outline_change_35.yaml.world.3.expect
@@ -0,0 +1,62 @@
+main = <No Member>;
+library from "org-dartlang-test:///lib.dart" as lib {
+
+  import "dart:ffi";
+
+  @#C3
+  class Coordinate extends dart.ffi::Struct {
+    @#C3
+    static final field dart.core::int* #sizeOf = (#C6).{dart.core::List::[]}(dart.ffi::_abi());
+    @#C3
+    constructor #fromPointer(dynamic #pointer) → dynamic
+      : super dart.ffi::Struct::_fromPointer(#pointer)
+      ;
+    static factory allocate(dart.core::double* x, dart.core::double* y, dart.ffi::Pointer<lib::Coordinate*>* next) → lib::Coordinate* {
+      dart.core::print("hello");
+      return null;
+    }
+    get #_ptr_x() → dart.ffi::Pointer<dart.ffi::Double*>*
+      return this.{dart.ffi::Struct::_addressOf}.{dart.ffi::Pointer::cast}<dart.ffi::Double*>();
+    get x() → dart.core::double*
+      return dart.ffi::_loadDouble(this.{lib::Coordinate::#_ptr_x}, #C7);
+    set x(dart.core::double* #v) → void
+      return dart.ffi::_storeDouble(this.{lib::Coordinate::#_ptr_x}, #C7, #v);
+    get #_ptr_y() → dart.ffi::Pointer<dart.ffi::Double*>*
+      return this.{dart.ffi::Struct::_addressOf}.{dart.ffi::Pointer::_offsetBy}((#C9).{dart.core::List::[]}(dart.ffi::_abi())).{dart.ffi::Pointer::cast}<dart.ffi::Double*>();
+    get y() → dart.core::double*
+      return dart.ffi::_loadDouble(this.{lib::Coordinate::#_ptr_y}, #C7);
+    set y(dart.core::double* #v) → void
+      return dart.ffi::_storeDouble(this.{lib::Coordinate::#_ptr_y}, #C7, #v);
+    get #_ptr_next() → dart.ffi::Pointer<dart.ffi::Pointer<lib::Coordinate*>*>*
+      return this.{dart.ffi::Struct::_addressOf}.{dart.ffi::Pointer::_offsetBy}((#C11).{dart.core::List::[]}(dart.ffi::_abi())).{dart.ffi::Pointer::cast}<dart.ffi::Pointer<lib::Coordinate*>*>();
+    get next() → dart.ffi::Pointer<lib::Coordinate*>*
+      return dart.ffi::_loadPointer<dart.ffi::Pointer<lib::Coordinate*>*>(this.{lib::Coordinate::#_ptr_next}, #C7);
+    set next(dart.ffi::Pointer<lib::Coordinate*>* #v) → void
+      return dart.ffi::_storePointer<dart.ffi::Pointer<lib::Coordinate*>*>(this.{lib::Coordinate::#_ptr_next}, #C7, #v);
+  }
+}
+library from "org-dartlang-test:///main.dart" as main {
+
+  import "org-dartlang-test:///lib.dart";
+
+  static method main() → dynamic {
+    lib::Coordinate* coordinate = lib::Coordinate::allocate(42.0, 42.0, null);
+    dart.core::print(coordinate.{lib::Coordinate::x});
+    dart.core::print(coordinate.{lib::Coordinate::y});
+    dart.core::print(coordinate.{lib::Coordinate::next});
+    dart.core::print("Done!");
+  }
+}
+constants  {
+  #C1 = "vm:entry-point"
+  #C2 = null
+  #C3 = dart.core::pragma {name:#C1, options:#C2}
+  #C4 = 24
+  #C5 = 20
+  #C6 = <dart.core::int*>[#C4, #C5, #C4]
+  #C7 = 0
+  #C8 = 8
+  #C9 = <dart.core::int*>[#C8, #C8, #C8]
+  #C10 = 16
+  #C11 = <dart.core::int*>[#C10, #C10, #C10]
+}
diff --git a/pkg/front_end/testcases/incremental_initialize_from_dill/no_outline_change_6.yaml b/pkg/front_end/testcases/incremental_initialize_from_dill/no_outline_change_6.yaml
index 8aa50fc..96943e2 100644
--- a/pkg/front_end/testcases/incremental_initialize_from_dill/no_outline_change_6.yaml
+++ b/pkg/front_end/testcases/incremental_initialize_from_dill/no_outline_change_6.yaml
@@ -61,7 +61,7 @@
 
         enum CompilationStrategy { direct, toKernel, toData, fromData }
     expectedLibraryCount: 2
-    expectsRebuildBodiesOnly: true
+    expectsRebuildBodiesOnly: false # For now, libraries with errors cannot have bodies rebuild.
   - entry: main.dart
     useExperimentalInvalidation: true
     worldType: updated
@@ -88,4 +88,4 @@
 
         enum CompilationStrategy { direct, toKernel, toData, fromData }
     expectedLibraryCount: 2
-    expectsRebuildBodiesOnly: true
+    expectsRebuildBodiesOnly: false # For now, libraries with errors cannot have bodies rebuild.
diff --git a/pkg/front_end/testcases/incremental_initialize_from_dill/status.status b/pkg/front_end/testcases/incremental_initialize_from_dill/status.status
index 4874153..3cae5cc 100644
--- a/pkg/front_end/testcases/incremental_initialize_from_dill/status.status
+++ b/pkg/front_end/testcases/incremental_initialize_from_dill/status.status
@@ -3,19 +3,3 @@
 # BSD-style license that can be found in the LICENSE.md file.
 
 # Status file for the test suite ../test/incremental_load_from_dill_test.dart.
-
-no_outline_change_1: Crash # Doesn't work on DillLibraryBuilders.
-no_outline_change_2: Crash # Doesn't work on DillLibraryBuilders.
-no_outline_change_6: Crash # Doesn't work on DillLibraryBuilders.
-no_outline_change_7: Crash # Doesn't work on DillLibraryBuilders.
-no_outline_change_9: Crash # Doesn't work on DillLibraryBuilders.
-no_outline_change_10: Crash # Doesn't work on DillLibraryBuilders.
-no_outline_change_11: Crash # Doesn't work on DillLibraryBuilders.
-no_outline_change_12: Crash # Doesn't work on DillLibraryBuilders.
-no_outline_change_13: Crash # Doesn't work on DillLibraryBuilders.
-no_outline_change_14: Crash # Doesn't work on DillLibraryBuilders.
-no_outline_change_21: Crash # Doesn't work on DillLibraryBuilders.
-no_outline_change_24: Crash # Doesn't work on DillLibraryBuilders.
-no_outline_change_27: Crash # Doesn't work on DillLibraryBuilders.
-no_outline_change_33: Crash # Doesn't work on DillLibraryBuilders.
-no_outline_change_34: Crash # Doesn't work on DillLibraryBuilders.