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