blob: bf19caf96d492a673c50729e10a3325b8c6ea1e9 [file] [log] [blame]
// Copyright (c) 2023, 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:developer" as developer;
import 'package:_fe_analyzer_shared/src/util/filenames.dart';
import 'package:front_end/src/api_prototype/file_system.dart' as api;
import 'package:front_end/src/base/compiler_context.dart';
import 'package:front_end/src/base/uri_translator.dart';
import 'package:front_end/src/dill/dill_target.dart';
import 'package:front_end/src/kernel/kernel_target.dart';
import 'package:front_end/src/kernel/macro/macro.dart';
import 'package:kernel/ast.dart' show CanonicalName, Class;
import 'package:vm_service/vm_service.dart' as vmService;
import "package:vm_service/vm_service_io.dart" as vmServiceIo;
import 'compiler_test_helper.dart';
import 'find_all_subclasses_tool.dart';
Future<void> main(List<String> args) async {
Set<Class> allTokenClasses = await getAllTokens();
Map<String, List<Uri>> classesInUris = {};
for (Class c in allTokenClasses) {
(classesInUris[c.name] ??= []).add(c.fileUri);
}
args = args.toList();
bool compileSdk = !args.remove('--no-sdk');
developer.ServiceProtocolInfo serviceProtocolInfo =
await developer.Service.getInfo();
bool startedServiceProtocol = false;
if (serviceProtocolInfo.serverUri == null) {
startedServiceProtocol = true;
serviceProtocolInfo = await developer.Service.controlWebServer(
enable: true, silenceOutput: true);
}
Uri? serverUri = serviceProtocolInfo.serverUri;
if (serverUri == null) {
throw "Couldn't get service protocol url.";
}
String path = serverUri.path;
if (!path.endsWith('/')) path += '/';
String wsUriString = 'ws://${serverUri.authority}${path}ws';
vmService.VmService serviceClient =
await vmServiceIo.vmServiceConnectUri(wsUriString);
await compile(
inputs: args.isNotEmpty
? args.map(nativeToUri).toList()
: [
Uri.base
.resolve('pkg/front_end/test/token_leak_test_helper.dart'),
],
compileSdk: compileSdk,
kernelTargetCreator: (CompilerContext compilerContext,
api.FileSystem fileSystem,
bool includeComments,
DillTarget dillTarget,
UriTranslator uriTranslator,
BodyBuilderCreator bodyBuilderCreator) {
return new KernelTargetTester(
compilerContext,
fileSystem,
includeComments,
dillTarget,
uriTranslator,
bodyBuilderCreator,
serviceClient,
classesInUris);
});
await serviceClient.dispose();
if (startedServiceProtocol) {
await developer.Service.controlWebServer(
enable: false, silenceOutput: true);
}
}
class KernelTargetTester extends KernelTargetTest {
final vmService.VmService serviceClient;
final Map<String, List<Uri>> classesInUris;
KernelTargetTester(
CompilerContext compilerContext,
api.FileSystem fileSystem,
bool includeComments,
DillTarget dillTarget,
UriTranslator uriTranslator,
BodyBuilderCreator bodyBuilderCreator,
this.serviceClient,
this.classesInUris,
) : super(compilerContext, fileSystem, includeComments, dillTarget,
uriTranslator, bodyBuilderCreator);
@override
Future<BuildResult> buildOutlines({CanonicalName? nameRoot}) async {
BuildResult buildResult = await super.buildOutlines(nameRoot: nameRoot);
print('buildOutlines complete');
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;
String isolateId = isolateRef.id!;
throwOnLeaksOrNoFinds(
await findAndPrintRetainingPaths(
serviceClient, isolateId, classesInUris),
"buildOutlines",
classesInUris);
return buildResult;
}
@override
Future<BuildResult> buildComponent(
{required MacroApplications? macroApplications,
bool verify = false,
bool allowVerificationErrorForTesting = false}) async {
BuildResult buildResult = await super.buildComponent(
macroApplications: macroApplications,
verify: verify,
allowVerificationErrorForTesting: allowVerificationErrorForTesting);
print('buildComponent complete');
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;
String isolateId = isolateRef.id!;
throwOnLeaksOrNoFinds(
await findAndPrintRetainingPaths(
serviceClient, isolateId, classesInUris),
"buildComponent",
classesInUris);
return buildResult;
}
}
void throwOnLeaksOrNoFinds(Map<vmService.Class, int> foundInstances,
String afterWhat, Map<String, List<Uri>> classesInUris) {
Map<String, List<Uri>> notFound = {};
for (MapEntry<String, List<Uri>> entry in classesInUris.entries) {
notFound[entry.key] = new List.of(entry.value);
}
StringBuffer? sb;
for (MapEntry<vmService.Class, int> entry in foundInstances.entries) {
List<Uri> notFoundUrisForName = notFound[entry.key.name!]!;
Uri uri = Uri.parse(entry.key.location!.script!.uri!);
for (int i = 0; i < notFoundUrisForName.length; i++) {
if (notFoundUrisForName[i].pathSegments.last == uri.pathSegments.last) {
notFoundUrisForName.removeAt(i);
break;
}
}
if (entry.value > 0) {
// 'SyntheticToken' will have 1 alive because of dummyToken in
// front_end/lib/src/kernel/utils.dart. Hack around that.
if (entry.key.name == "SyntheticToken" && entry.value == 1) {
continue;
}
sb ??= new StringBuffer();
sb.writeln('Found ${entry.value} instances of ${entry.key} '
'after $afterWhat');
}
}
if (sb != null) {
throw sb.toString();
}
if (foundInstances.isEmpty) {
throw "Didn't find anything.";
}
for (MapEntry<String, List<Uri>> notFoundData in notFound.entries) {
if (notFoundData.value.isNotEmpty) {
print("WARNING: Didn't find ${notFoundData.key}' in "
"${notFoundData.value.join(" and ")}");
}
}
}
Future<Map<vmService.Class, int>> findAndPrintRetainingPaths(
vmService.VmService serviceClient,
String isolateId,
Map<String, List<Uri>> classesInUrisFilter) async {
vmService.AllocationProfile allocationProfile =
await serviceClient.getAllocationProfile(isolateId, gc: true);
Map<vmService.Class, int> foundInstances = {};
for (vmService.ClassHeapStats member in allocationProfile.members!) {
String? className = member.classRef!.name;
if (className == null) continue;
List<Uri>? uris = classesInUrisFilter[className];
if (uris == null) continue;
// File uris vs package uris --- for now just compare the filename.
String? classUriString = member.classRef?.location?.script?.uri;
if (classUriString == null) continue;
Uri classUri = Uri.parse(classUriString);
bool foundMatch = false;
for (Uri uri in uris) {
if (classUri.pathSegments.last == uri.pathSegments.last) {
foundMatch = true;
break;
}
}
if (!foundMatch) continue;
vmService.Class c = await serviceClient.getObject(
isolateId, member.classRef!.id!) as vmService.Class;
int? instancesCurrent = member.instancesCurrent;
if (instancesCurrent == null) continue;
foundInstances[c] = instancesCurrent;
if (instancesCurrent == 0) continue;
print("Found ${c.name} (location: ${c.location})");
print("${member.classRef!.name}: "
"(instancesCurrent: ${member.instancesCurrent})");
print("");
vmService.InstanceSet instances =
await serviceClient.getInstances(isolateId, member.classRef!.id!, 100);
print(" => Got ${instances.instances!.length} instances");
print("");
for (vmService.ObjRef instance in instances.instances!) {
try {
vmService.Obj receivedObject =
await serviceClient.getObject(isolateId, instance.id!);
print("Instance: $receivedObject");
vmService.RetainingPath retainingPath =
await serviceClient.getRetainingPath(isolateId, instance.id!, 1000);
print("Retaining path: (length ${retainingPath.length})");
print(retainingPath.gcRootType);
String indent = ' ';
for (int i = retainingPath.elements!.length - 1; i >= 0; i--) {
vmService.RetainingObject retainingObject =
retainingPath.elements![i];
vmService.ObjRef? value = retainingObject.value;
if (value is vmService.FieldRef) {
print("${indent}field '${value.name}'");
} else {
String field;
if (retainingObject.parentListIndex != null) {
field = '[${retainingObject.parentListIndex}]';
} else if (retainingObject.parentMapKey != null) {
field = '[?]';
} else if (retainingObject.parentField != null) {
field = '.${retainingObject.parentField}';
} else {
field = '';
}
String className = '';
if (value is vmService.InstanceRef) {
vmService.ClassRef? classRef = value.classRef;
if (classRef != null && classRef.name != null) {
className = 'class ${classRef.name}';
}
}
print("${indent}${className}$field");
}
indent += ' ';
}
print("");
} catch (_) {
// Suppress errors.
}
}
}
print("Done!");
return foundInstances;
}