[CFE] Fix incremental_compiler_leak_test(er)

In https://dart-review.googlesource.com/c/sdk/+/214020
incremental_compiler_leak_test was renamed to
incremental_compiler_leak_tester temporarily because it started saying
we had possible leaks.

Looking into it now, a number of puzzeling this were found:
* Many SourceLibraryBuilders, SourceClassBuilders etc was alive despite
  having been converted to DillLibraryBuilders (etc). They shoudn't be.
* Several Librarys (from kernels AST) came and went between
  invalidations.

This all was caused by the VM sometimes keeping things around longer
than it should (probably a variation of
https://github.com/dart-lang/sdk/issues/36983).

This CL fixes it by explicitley nulling things out (and similar).
It re-enasbles the test and updates it to explicitley expecting a
certain number of instances for some classes (e.g. it now expect
0 SourceLibraryBuilders).

To be clear I don't think this has caused any "real" leaks, but:
* having things clear up too late is very confusing.
* we will probably use less memory now.

Change-Id: I3a439f23fc7ef26b156c6164a2c37f6352bc57b4
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/229964
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Commit-Queue: Jens Johansen <jensj@google.com>
diff --git a/pkg/front_end/lib/src/fasta/kernel/kernel_target.dart b/pkg/front_end/lib/src/fasta/kernel/kernel_target.dart
index c7c5dfc..9bad9b3 100644
--- a/pkg/front_end/lib/src/fasta/kernel/kernel_target.dart
+++ b/pkg/front_end/lib/src/fasta/kernel/kernel_target.dart
@@ -409,7 +409,7 @@
       if (macroApplications != null) {
         await macroApplications.applyTypeMacros();
       }
-      List<SourceClassBuilder> sourceClassBuilders =
+      List<SourceClassBuilder>? sourceClassBuilders =
           loader.checkSemantics(objectClassBuilder);
       loader.finishTypeVariables(objectClassBuilder, dynamicType);
       loader.createTypeInferenceEngine();
@@ -442,6 +442,11 @@
       loader.checkMainMethods();
       installAllComponentProblems(loader.allComponentProblems);
       loader.allComponentProblems.clear();
+      // For whatever reason sourceClassBuilders is kept alive for some amount
+      // of time, meaning that all source library builders will be kept alive
+      // (for whatever amount of time) even though we convert them to dill
+      // library builders. To avoid it we null it out here.
+      sourceClassBuilders = null;
       return new BuildResult(
           component: component, macroApplications: macroApplications);
     }, () => loader.currentUriForCrashReporting);
@@ -467,7 +472,7 @@
       finishSynthesizedParameters();
       loader.finishDeferredLoadTearoffs();
       loader.finishNoSuchMethodForwarders();
-      List<SourceClassBuilder> sourceClasses = loader.collectSourceClasses();
+      List<SourceClassBuilder>? sourceClasses = loader.collectSourceClasses();
       if (macroApplications != null) {
         await macroApplications.applyDefinitionMacros(
             loader.coreTypes, loader.hierarchy);
@@ -479,6 +484,12 @@
 
       if (verify) this.verify();
       installAllComponentProblems(loader.allComponentProblems);
+
+      // For whatever reason sourceClasses is kept alive for some amount
+      // of time, meaning that all source library builders will be kept alive
+      // (for whatever amount of time) even though we convert them to dill
+      // library builders. To avoid it we null it out here.
+      sourceClasses = null;
       return new BuildResult(
           component: component, macroApplications: macroApplications);
     }, () => loader.currentUriForCrashReporting);
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 08c9e9b..97db119 100644
--- a/pkg/front_end/lib/src/fasta/source/source_loader.dart
+++ b/pkg/front_end/lib/src/fasta/source/source_loader.dart
@@ -642,6 +642,9 @@
       currentUriForCrashReporting = library.importUri;
       await buildBody(library);
     }
+    // Workaround: This will return right away but avoid a "semi leak"
+    // where the latest library is saved in a context somewhere.
+    await buildBody(null);
     currentUriForCrashReporting = null;
     logSummary(templateSourceBodySummary);
   }
@@ -1076,7 +1079,10 @@
   }
 
   /// Builds all the method bodies found in the given [library].
-  Future<Null> buildBody(SourceLibraryBuilder library) async {
+  Future<Null> buildBody(SourceLibraryBuilder? library) async {
+    // [library] is only nullable so we can call this a "dummy-time" to get rid
+    // of a semi-leak.
+    if (library == null) return;
     Iterable<SourceLibraryBuilder>? patches = library.patchLibraries;
     if (patches != null) {
       for (SourceLibraryBuilder patchLibrary in patches) {
diff --git a/pkg/front_end/test/incremental_compiler_leak_tester.dart b/pkg/front_end/test/incremental_compiler_leak_test.dart
similarity index 80%
rename from pkg/front_end/test/incremental_compiler_leak_tester.dart
rename to pkg/front_end/test/incremental_compiler_leak_test.dart
index d05460b..581d63f 100644
--- a/pkg/front_end/test/incremental_compiler_leak_tester.dart
+++ b/pkg/front_end/test/incremental_compiler_leak_test.dart
@@ -85,30 +85,48 @@
       // For now ignore anything not in package:kernel or package:front_end.
       if (ignoredClass(classDetails)) continue;
 
+      bool isStrictClass = strictClass(classDetails);
+
+      int expectedStrictClassNumber = -1;
+      if (isStrictClass) {
+        expectedStrictClassNumber = strictClassExpectedNumber(classDetails);
+      }
+
       // If they're all equal there's nothing to talk about.
-      bool same = true;
-      for (int i = 1; i < listOfInstanceCounts.length; i++) {
+      bool sameAndAsExpected = true;
+      for (int i = 0; i < listOfInstanceCounts.length; i++) {
+        if (expectedStrictClassNumber > -1 &&
+            expectedStrictClassNumber != listOfInstanceCounts[i]) {
+          sameAndAsExpected = false;
+          break;
+        }
         if (listOfInstanceCounts[i] != listOfInstanceCounts[0]) {
-          same = false;
+          sameAndAsExpected = false;
           break;
         }
       }
-      if (same) continue;
+      if (sameAndAsExpected) continue;
 
       int midPoint = listOfInstanceCounts.length ~/ 2;
       List<int> firstHalf = listOfInstanceCounts.sublist(0, midPoint);
       List<int> secondHalf = listOfInstanceCounts.sublist(midPoint);
       TTestResult ttestResult = SimpleTTestStat.ttest(secondHalf, firstHalf);
 
-      if (!strictClass(classDetails)) {
+      if (!isStrictClass) {
         if (!ttestResult.significant) continue;
 
         // TODO(jensj): We could possibly also ignore if it's less (i.e. a
         // negative change), or if the change is < 1%, or the change minus the
         // confidence is < 1% etc.
       }
-      print("Differences on ${c.name} (${uriString}): "
-          "$listOfInstanceCounts ($ttestResult)");
+      if (expectedStrictClassNumber > -1) {
+        print("Differences on ${c.name} (${uriString}): "
+            "Expected exactly $expectedStrictClassNumber but found "
+            "$listOfInstanceCounts ($ttestResult)");
+      } else {
+        print("Differences on ${c.name} (${uriString}): "
+            "$listOfInstanceCounts ($ttestResult)");
+      }
       foundLeak = true;
     }
 
@@ -219,20 +237,26 @@
     return true;
   }
 
-  // I have commented out the lazy ones below.
-  Set<String> frontEndStrictClasses = {
+  Map<String, int> frontEndStrictClasses = {
+    // The inner working of dills are created lazily:
     // "DillClassBuilder",
     // "DillExtensionBuilder",
     // "DillExtensionMemberBuilder",
-    "DillLibraryBuilder",
-    "DillLoader",
     // "DillMemberBuilder",
-    "DillTarget",
     // "DillTypeAliasBuilder",
-    "SourceClassBuilder",
-    "SourceExtensionBuilder",
-    "SourceLibraryBuilder",
-    "SourceLoader",
+
+    "DillLibraryBuilder": -1 /* unknown amount */,
+    "DillLoader": 1,
+    "DillTarget": 1,
+
+    // We convert all source builders to dill builders so we expect none to
+    // exist after that.
+    "SourceClassBuilder": 0,
+    "SourceExtensionBuilder": 0,
+    "SourceLibraryBuilder": 0,
+
+    // We still expect exactly 1 source loader though.
+    "SourceLoader": 1,
   };
 
   Set<String> kernelAstStrictClasses = {
@@ -248,13 +272,13 @@
 
   bool strictClass(vmService.Class classDetails) {
     if (!kernelAstStrictClasses.contains(classDetails.name) &&
-        !frontEndStrictClasses.contains(classDetails.name)) return false;
+        !frontEndStrictClasses.containsKey(classDetails.name)) return false;
 
     if (kernelAstStrictClasses.contains(classDetails.name) &&
         classDetails.location?.script?.uri == "package:kernel/ast.dart") {
       return true;
     }
-    if (frontEndStrictClasses.contains(classDetails.name) &&
+    if (frontEndStrictClasses.containsKey(classDetails.name) &&
         classDetails.location?.script?.uri?.startsWith("package:front_end/") ==
             true) {
       return true;
@@ -262,4 +286,20 @@
 
     throw "$classDetails: ${classDetails.name} --- ${classDetails.location}";
   }
+
+  int strictClassExpectedNumber(vmService.Class classDetails) {
+    if (!strictClass(classDetails)) return -1;
+    if (kernelAstStrictClasses.contains(classDetails.name) &&
+        classDetails.location?.script?.uri == "package:kernel/ast.dart") {
+      return -1;
+    }
+    int? result = frontEndStrictClasses[classDetails.name];
+    if (result != null &&
+        classDetails.location?.script?.uri?.startsWith("package:front_end/") ==
+            true) {
+      return result;
+    }
+
+    throw "$classDetails: ${classDetails.name} --- ${classDetails.location}";
+  }
 }