Skip to content

Commit de7e3b7

Browse files
jensjohaCommit Queue
authored and
Commit Queue
committed
[CFE] Better token leak tester
Instead of hard-coding a single Token name, find all subtypes of Token and use that when searching for leaks. We allow one instance of SyntheticToken because that's used in `dummyToken`. We fix the printing of retaining paths for fields. Change-Id: Ic4d629ca08cae2307d4a550fb8b274f978c49b36 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/333823 Reviewed-by: Johnni Winther <[email protected]> Commit-Queue: Jens Johansen <[email protected]>
1 parent 43db6c6 commit de7e3b7

File tree

3 files changed

+197
-50
lines changed

3 files changed

+197
-50
lines changed
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'dart:io';
6+
import 'package:front_end/src/api_prototype/compiler_options.dart';
7+
import 'package:front_end/src/api_prototype/incremental_kernel_generator.dart';
8+
import 'package:kernel/ast.dart';
9+
10+
import 'incremental_suite.dart' as helper;
11+
import 'utils/io_utils.dart' show computeRepoDirUri;
12+
13+
final Uri repoDir = computeRepoDirUri();
14+
15+
void main() async {
16+
print(await getAllTokens());
17+
}
18+
19+
Future<Set<Class>> getAllTokens() async {
20+
Uri scannerDir = repoDir.resolve("pkg/_fe_analyzer_shared/lib/src/scanner/");
21+
return await findIn(Directory.fromUri(scannerDir), "Token", "/token.dart");
22+
}
23+
24+
/// Compiles either a File or a Directory and finds all subtypes of (including)
25+
/// the specified [className] in a file containing [classFilename].
26+
Future<Set<Class>> findIn(
27+
FileSystemEntity where, String className, String classFilename) async {
28+
List<Uri> files = [];
29+
if (where is File) {
30+
files.add(where.uri);
31+
} else if (where is Directory) {
32+
for (FileSystemEntity subEntity in where.listSync(recursive: true)) {
33+
if (subEntity is File) {
34+
files.add(subEntity.uri);
35+
}
36+
}
37+
}
38+
39+
Class? foundTarget;
40+
Map<Class, Set<Class>> subclassMap = {};
41+
Component component = await compileOutline(files);
42+
for (Library lib in component.libraries) {
43+
for (Class c in lib.classes) {
44+
if (c.name == className && c.fileUri.toString().contains(classFilename)) {
45+
if (foundTarget != null) throw "Found both $foundTarget and $c";
46+
foundTarget = c;
47+
}
48+
for (Supertype s in c.supers) {
49+
(subclassMap[s.classNode] ??= {}).add(c);
50+
}
51+
}
52+
}
53+
if (foundTarget == null) throw "Didn't find '$className' in '$classFilename'";
54+
Set<Class> result = {foundTarget};
55+
List<Class> worklist = [foundTarget];
56+
while (worklist.isNotEmpty) {
57+
Class c = worklist.removeLast();
58+
for (Class child in subclassMap[c] ?? const []) {
59+
if (result.add(child)) {
60+
worklist.add(child);
61+
}
62+
}
63+
}
64+
return result;
65+
}
66+
67+
Future<Component> compileOutline(List<Uri> input) async {
68+
CompilerOptions options = helper.getOptions();
69+
options.omitPlatform = true;
70+
// Give only one input so it automatically finds the packages file.
71+
helper.TestIncrementalCompiler compiler =
72+
new helper.TestIncrementalCompiler(options, input.first, null, true);
73+
IncrementalCompilerResult compilerResult =
74+
await compiler.computeDelta(entryPoints: input);
75+
return compilerResult.component;
76+
}

pkg/front_end/test/token_leak_dart2js_test.dart renamed to pkg/front_end/test/token_leak_dart2js_git_test.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
44

5-
import 'token_leak_test.dart' as test;
5+
import 'token_leak_git_test.dart' as test;
66

77
Future<void> main() async {
88
await test.main(['--no-sdk', 'pkg/compiler/lib/src/dart2js.dart']);

pkg/front_end/test/token_leak_test.dart renamed to pkg/front_end/test/token_leak_git_test.dart

Lines changed: 120 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,21 @@ import 'package:front_end/src/fasta/dill/dill_target.dart';
1010
import 'package:front_end/src/fasta/kernel/kernel_target.dart';
1111
import 'package:front_end/src/fasta/kernel/macro/macro.dart';
1212
import 'package:front_end/src/fasta/uri_translator.dart';
13-
import 'package:kernel/canonical_name.dart';
13+
import 'package:kernel/ast.dart' show CanonicalName, Class;
1414
import 'package:vm_service/vm_service.dart' as vmService;
1515
import "package:vm_service/vm_service_io.dart" as vmServiceIo;
1616

1717
import 'compiler_test_helper.dart';
18-
import 'vm_service_helper.dart';
18+
19+
import 'find_all_subclasses_tool.dart';
1920

2021
Future<void> main(List<String> args) async {
22+
Set<Class> allTokenClasses = await getAllTokens();
23+
Map<String, List<Uri>> classesInUris = {};
24+
for (Class c in allTokenClasses) {
25+
(classesInUris[c.name] ??= []).add(c.fileUri);
26+
}
27+
2128
args = args.toList();
2229
bool compileSdk = !args.remove('--no-sdk');
2330
developer.ServiceProtocolInfo serviceProtocolInfo =
@@ -36,7 +43,8 @@ Future<void> main(List<String> args) async {
3643
String path = serverUri.path;
3744
if (!path.endsWith('/')) path += '/';
3845
String wsUriString = 'ws://${serverUri.authority}${path}ws';
39-
VmService serviceClient = await vmServiceIo.vmServiceConnectUri(wsUriString);
46+
vmService.VmService serviceClient =
47+
await vmServiceIo.vmServiceConnectUri(wsUriString);
4048

4149
await compile(
4250
inputs: args.isNotEmpty
@@ -52,7 +60,7 @@ Future<void> main(List<String> args) async {
5260
UriTranslator uriTranslator,
5361
BodyBuilderCreator bodyBuilderCreator) {
5462
return new KernelTargetTester(fileSystem, includeComments, dillTarget,
55-
uriTranslator, bodyBuilderCreator, serviceClient);
63+
uriTranslator, bodyBuilderCreator, serviceClient, classesInUris);
5664
});
5765

5866
await serviceClient.dispose();
@@ -64,20 +72,18 @@ Future<void> main(List<String> args) async {
6472
}
6573

6674
class KernelTargetTester extends KernelTargetTest {
67-
final VmService serviceClient;
68-
69-
// TODO(johnniwinther): Can we programmatically find all subclasses of [Token]
70-
// instead?
71-
static const String className = 'StringTokenImpl';
75+
final vmService.VmService serviceClient;
76+
final Map<String, List<Uri>> classesInUris;
7277

7378
KernelTargetTester(
74-
api.FileSystem fileSystem,
75-
bool includeComments,
76-
DillTarget dillTarget,
77-
UriTranslator uriTranslator,
78-
BodyBuilderCreator bodyBuilderCreator,
79-
this.serviceClient)
80-
: super(fileSystem, includeComments, dillTarget, uriTranslator,
79+
api.FileSystem fileSystem,
80+
bool includeComments,
81+
DillTarget dillTarget,
82+
UriTranslator uriTranslator,
83+
BodyBuilderCreator bodyBuilderCreator,
84+
this.serviceClient,
85+
this.classesInUris,
86+
) : super(fileSystem, includeComments, dillTarget, uriTranslator,
8187
bodyBuilderCreator);
8288

8389
@override
@@ -92,12 +98,11 @@ class KernelTargetTester extends KernelTargetTest {
9298

9399
String isolateId = isolateRef.id!;
94100

95-
int foundInstances =
96-
await findAndPrintRetainingPaths(serviceClient, isolateId, className);
97-
if (foundInstances > 0) {
98-
throw 'Found $foundInstances instances of $className after '
99-
'buildOutlines';
100-
}
101+
throwOnLeaksOrNoFinds(
102+
await findAndPrintRetainingPaths(
103+
serviceClient, isolateId, classesInUris),
104+
"buildOutlines",
105+
classesInUris);
101106
return buildResult;
102107
}
103108

@@ -119,35 +124,96 @@ class KernelTargetTester extends KernelTargetTest {
119124

120125
String isolateId = isolateRef.id!;
121126

122-
int foundInstances =
123-
await findAndPrintRetainingPaths(serviceClient, isolateId, className);
124-
if (foundInstances > 0) {
125-
throw 'Found $foundInstances instances of $className after '
126-
'buildComponent';
127-
}
127+
throwOnLeaksOrNoFinds(
128+
await findAndPrintRetainingPaths(
129+
serviceClient, isolateId, classesInUris),
130+
"buildComponent",
131+
classesInUris);
128132
return buildResult;
129133
}
130134
}
131135

132-
Future<int> findAndPrintRetainingPaths(
133-
vmService.VmService serviceClient, String isolateId, String filter) async {
136+
void throwOnLeaksOrNoFinds(Map<vmService.Class, int> foundInstances,
137+
String afterWhat, Map<String, List<Uri>> classesInUris) {
138+
Map<String, List<Uri>> notFound = {};
139+
for (MapEntry<String, List<Uri>> entry in classesInUris.entries) {
140+
notFound[entry.key] = new List.of(entry.value);
141+
}
142+
StringBuffer? sb;
143+
for (MapEntry<vmService.Class, int> entry in foundInstances.entries) {
144+
List<Uri> notFoundUrisForName = notFound[entry.key.name!]!;
145+
Uri uri = Uri.parse(entry.key.location!.script!.uri!);
146+
for (int i = 0; i < notFoundUrisForName.length; i++) {
147+
if (notFoundUrisForName[i].pathSegments.last == uri.pathSegments.last) {
148+
notFoundUrisForName.removeAt(i);
149+
break;
150+
}
151+
}
152+
if (entry.value > 0) {
153+
// 'SyntheticToken' will have 1 alive because of dummyToken in
154+
// front_end/lib/src/fasta/kernel/utils.dart. Hack around that.
155+
if (entry.key.name == "SyntheticToken" && entry.value == 1) {
156+
continue;
157+
}
158+
sb ??= new StringBuffer();
159+
sb.writeln('Found ${entry.value} instances of ${entry.key} '
160+
'after $afterWhat');
161+
}
162+
}
163+
if (sb != null) {
164+
throw sb.toString();
165+
}
166+
if (foundInstances.isEmpty) {
167+
throw "Didn't find anything.";
168+
}
169+
for (MapEntry<String, List<Uri>> notFoundData in notFound.entries) {
170+
if (notFoundData.value.isNotEmpty) {
171+
print("WARNING: Didn't find ${notFoundData.key}' in "
172+
"${notFoundData.value.join(" and ")}");
173+
}
174+
}
175+
}
176+
177+
Future<Map<vmService.Class, int>> findAndPrintRetainingPaths(
178+
vmService.VmService serviceClient,
179+
String isolateId,
180+
Map<String, List<Uri>> classesInUrisFilter) async {
134181
vmService.AllocationProfile allocationProfile =
135182
await serviceClient.getAllocationProfile(isolateId, gc: true);
136183

137-
int foundInstances = 0;
184+
Map<vmService.Class, int> foundInstances = {};
138185

139186
for (vmService.ClassHeapStats member in allocationProfile.members!) {
140-
if (member.classRef!.name != filter) continue;
187+
String? className = member.classRef!.name;
188+
if (className == null) continue;
189+
List<Uri>? uris = classesInUrisFilter[className];
190+
if (uris == null) continue;
191+
// File uris vs package uris --- for now just compare the filename.
192+
String? classUriString = member.classRef?.location?.script?.uri;
193+
if (classUriString == null) continue;
194+
Uri classUri = Uri.parse(classUriString);
195+
bool foundMatch = false;
196+
for (Uri uri in uris) {
197+
if (classUri.pathSegments.last == uri.pathSegments.last) {
198+
foundMatch = true;
199+
break;
200+
}
201+
}
202+
if (!foundMatch) continue;
141203
vmService.Class c = await serviceClient.getObject(
142204
isolateId, member.classRef!.id!) as vmService.Class;
205+
int? instancesCurrent = member.instancesCurrent;
206+
if (instancesCurrent == null) continue;
207+
foundInstances[c] = instancesCurrent;
208+
if (instancesCurrent == 0) continue;
209+
143210
print("Found ${c.name} (location: ${c.location})");
144211
print("${member.classRef!.name}: "
145212
"(instancesCurrent: ${member.instancesCurrent})");
146213
print("");
147214

148215
vmService.InstanceSet instances =
149216
await serviceClient.getInstances(isolateId, member.classRef!.id!, 100);
150-
foundInstances += instances.instances!.length;
151217
print(" => Got ${instances.instances!.length} instances");
152218
print("");
153219

@@ -160,29 +226,34 @@ Future<int> findAndPrintRetainingPaths(
160226
await serviceClient.getRetainingPath(isolateId, instance.id!, 1000);
161227

162228
print("Retaining path: (length ${retainingPath.length})");
163-
String indent = '';
229+
print(retainingPath.gcRootType);
230+
String indent = ' ';
164231
for (int i = retainingPath.elements!.length - 1; i >= 0; i--) {
165232
vmService.RetainingObject retainingObject =
166233
retainingPath.elements![i];
167234
vmService.ObjRef? value = retainingObject.value;
168-
String field;
169-
if (retainingObject.parentListIndex != null) {
170-
field = '[${retainingObject.parentListIndex}]';
171-
} else if (retainingObject.parentMapKey != null) {
172-
field = '[?]';
173-
} else if (retainingObject.parentField != null) {
174-
field = '.${retainingObject.parentField}';
235+
if (value is vmService.FieldRef) {
236+
print("${indent}field '${value.name}'");
175237
} else {
176-
field = '';
177-
}
178-
String className = '';
179-
if (value is vmService.InstanceRef) {
180-
vmService.ClassRef? classRef = value.classRef;
181-
if (classRef != null && classRef.name != null) {
182-
className = classRef.name!;
238+
String field;
239+
if (retainingObject.parentListIndex != null) {
240+
field = '[${retainingObject.parentListIndex}]';
241+
} else if (retainingObject.parentMapKey != null) {
242+
field = '[?]';
243+
} else if (retainingObject.parentField != null) {
244+
field = '.${retainingObject.parentField}';
245+
} else {
246+
field = '';
247+
}
248+
String className = '';
249+
if (value is vmService.InstanceRef) {
250+
vmService.ClassRef? classRef = value.classRef;
251+
if (classRef != null && classRef.name != null) {
252+
className = 'class ${classRef.name}';
253+
}
183254
}
255+
print("${indent}${className}$field");
184256
}
185-
print("${indent}${className}$field");
186257
indent += ' ';
187258
}
188259

0 commit comments

Comments
 (0)