| // Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| import '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, |
| ); |
| |
| await 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 = []; |
| @override |
| int iterationNumber = -1; |
| Completer<List<String>> completer = new Completer<List<String>>(); |
| |
| LeakFinderTest({ |
| required List<helper.Interest> interests, |
| required List<helper.Interest> prettyPrints, |
| required bool throwOnPossibleLeak, |
| }) : super( |
| interests: interests, |
| prettyPrints: prettyPrints, |
| throwOnPossibleLeak: throwOnPossibleLeak); |
| |
| @override |
| void processExited(int exitCode) { |
| print("Process exited!"); |
| leakData.sort(); |
| completer.complete(leakData); |
| } |
| |
| @override |
| void leakDetected(String duplicate, int count, List<String> prettyPrints) { |
| prettyPrints.sort(); |
| leakData.add("$iterationNumber: $count: $prettyPrints"); |
| } |
| |
| @override |
| void noLeakDetected() { |
| leakData.add("$iterationNumber: no leak"); |
| } |
| |
| @override |
| bool shouldDoAnotherIteration(int iterationNumber) { |
| this.iterationNumber = iterationNumber; |
| return iterationNumber <= 6; |
| } |
| } |