| // Copyright (c) 2022, 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 'package:_fe_analyzer_shared/src/messages/diagnostic_message.dart'; | |
| import 'package:front_end/src/codes/diagnostic.dart' as diag; | |
| import 'package:kernel/ast.dart'; | |
| import 'package:kernel/class_hierarchy.dart'; | |
| import 'package:kernel/core_types.dart'; | |
| import 'package:kernel/target/targets.dart'; | |
| import 'package:kernel/type_environment.dart'; | |
| import '../api_prototype/compiler_options.dart'; | |
| import '../api_prototype/kernel_generator.dart'; | |
| import '../api_prototype/terminal_color_support.dart'; | |
| import '../base/command_line_reporting.dart'; | |
| import '../codes/cfe_codes.dart'; | |
| import '../compute_platform_binaries_location.dart'; | |
| import '../kernel_generator_impl.dart'; | |
| typedef PerformAnalysisFunction = void Function( | |
| DiagnosticMessageHandler onDiagnostic, | |
| Component component, | |
| ); | |
| typedef UriFilter = bool Function(Uri uri); | |
| /// Analysis the [entryPoints] using [performAnalysis]. | |
| Future<void> runAnalysis( | |
| List<Uri> entryPoints, | |
| PerformAnalysisFunction performAnalysis, | |
| ) async { | |
| CompilerOptions options = new CompilerOptions(); | |
| options.sdkRoot = computePlatformBinariesLocation(forceBuildDir: true); | |
| await _runAnalysis(options, entryPoints, performAnalysis); | |
| } | |
| /// Analysis the platform libraries for [target] using [performAnalysis]. | |
| Future<void> runPlatformAnalysis( | |
| Target target, | |
| PerformAnalysisFunction performAnalysis, | |
| ) async { | |
| CompilerOptions options = new CompilerOptions(); | |
| options.target = target; | |
| options.environmentDefines = {}; | |
| options.librariesSpecificationUri = Uri.base.resolve( | |
| 'sdk/lib/libraries.json', | |
| ); | |
| Set<Uri> additionalSources = {}; | |
| for (String extraRequiredLibrary in target.extraRequiredLibraries) { | |
| additionalSources.add(Uri.parse(extraRequiredLibrary)); | |
| } | |
| for (String extraRequiredLibrary in target.extraRequiredLibrariesPlatform) { | |
| additionalSources.add(Uri.parse(extraRequiredLibrary)); | |
| } | |
| await _runAnalysis(options, [ | |
| Uri.parse('dart:core'), | |
| ...additionalSources, | |
| ], performAnalysis); | |
| } | |
| Future<void> _runAnalysis( | |
| CompilerOptions options, | |
| Iterable<Uri> entryPoints, | |
| PerformAnalysisFunction performAnalysis, | |
| ) async { | |
| options.packagesFileUri = Uri.base.resolve('.dart_tool/package_config.json'); | |
| options.onDiagnostic = (CfeDiagnosticMessage message) { | |
| printDiagnosticMessage(message, print); | |
| }; | |
| InternalCompilerResult compilerResult = await kernelForProgramInternal( | |
| entryPoints.first, | |
| options, | |
| retainDataForTesting: true, | |
| requireMain: false, | |
| additionalSources: entryPoints.skip(1).toList(), | |
| ) as InternalCompilerResult; | |
| performAnalysis(options.onDiagnostic!, compilerResult.component!); | |
| } | |
| class StaticTypeVisitorBase extends RecursiveVisitor { | |
| final TypeEnvironment typeEnvironment; | |
| StaticTypeContext? staticTypeContext; | |
| new(Component component, ClassHierarchy classHierarchy) | |
| : typeEnvironment = new TypeEnvironment( | |
| new CoreTypes(component), | |
| classHierarchy, | |
| ); | |
| @override | |
| void visitProcedure(Procedure node) { | |
| if (node.kind == ProcedureKind.Factory && node.isRedirectingFactory) { | |
| // Don't visit redirecting factories. | |
| return; | |
| } | |
| staticTypeContext = new StaticTypeContext(node, typeEnvironment); | |
| super.visitProcedure(node); | |
| staticTypeContext = null; | |
| } | |
| @override | |
| void visitField(Field node) { | |
| staticTypeContext = new StaticTypeContext(node, typeEnvironment); | |
| super.visitField(node); | |
| staticTypeContext = null; | |
| } | |
| @override | |
| void visitConstructor(Constructor node) { | |
| staticTypeContext = new StaticTypeContext(node, typeEnvironment); | |
| super.visitConstructor(node); | |
| staticTypeContext = null; | |
| } | |
| } | |
| class AnalysisVisitor extends StaticTypeVisitorBase { | |
| final DiagnosticMessageHandler onDiagnostic; | |
| final Component component; | |
| final UriFilter? uriFilter; | |
| late final AnalysisInterface interface; | |
| Map<String, Map<String, List<FormattedMessage>>> _messages = {}; | |
| new(this.onDiagnostic, this.component, this.uriFilter) | |
| : super( | |
| component, | |
| new ClassHierarchy(component, new CoreTypes(component)), | |
| ) { | |
| interface = new AnalysisInterface(this); | |
| } | |
| @override | |
| void visitLibrary(Library node) { | |
| if (uriFilter != null) { | |
| if (uriFilter!(node.importUri)) { | |
| super.visitLibrary(node); | |
| } | |
| } else { | |
| super.visitLibrary(node); | |
| } | |
| } | |
| void registerMessage(TreeNode node, String message) { | |
| Location location = node.location!; | |
| Uri uri = location.file; | |
| String uriString = relativizeUri(uri)!; | |
| Map<String, List<FormattedMessage>> actualMap = _messages.putIfAbsent( | |
| uriString, | |
| () => <String, List<FormattedMessage>>{}, | |
| ); | |
| if (uri.isScheme('org-dartlang-sdk')) { | |
| location = new Location( | |
| Uri.base.resolve(uri.path.substring(1)), | |
| location.line, | |
| location.column, | |
| ); | |
| } | |
| LocatedMessage locatedMessage = diag.unspecified | |
| .withArguments(message: message) | |
| .withLocation(uri, node.fileOffset, noLength); | |
| FormattedMessage diagnosticMessage = locatedMessage.withFormatting( | |
| formatWithLocationNoSdk( | |
| locatedMessage, | |
| CfeSeverity.warning, | |
| location: location, | |
| uriToSource: component.uriToSource, | |
| ), | |
| location.line, | |
| location.column, | |
| CfeSeverity.warning, | |
| [], | |
| ); | |
| actualMap | |
| .putIfAbsent(message, () => <FormattedMessage>[]) | |
| .add(diagnosticMessage); | |
| } | |
| void forEachMessage( | |
| void Function(String, Map<String, List<FormattedMessage>>) f, | |
| ) { | |
| _messages.forEach(f); | |
| } | |
| Map<String, List<FormattedMessage>>? getMessagesForUri(String uri) { | |
| return _messages[uri]; | |
| } | |
| void printMessages() { | |
| forEachMessage((String uri, Map<String, List<FormattedMessage>> messages) { | |
| messages.forEach((String message, List<FormattedMessage> actualMessages) { | |
| for (FormattedMessage message in actualMessages) { | |
| onDiagnostic(message); | |
| } | |
| }); | |
| }); | |
| } | |
| } | |
| /// Convenience interface for performing analysis. | |
| class AnalysisInterface { | |
| final AnalysisVisitor _visitor; | |
| final ComponentLookup _componentLookup; | |
| new(this._visitor) | |
| : _componentLookup = new ComponentLookup(_visitor.component); | |
| void reportMessage(TreeNode node, String message) { | |
| _visitor.registerMessage(node, message); | |
| } | |
| InterfaceType createInterfaceType( | |
| String className, { | |
| String? uri, | |
| List<DartType>? typeArguments, | |
| }) { | |
| LibraryLookup libraryLookup = _componentLookup.getLibrary( | |
| Uri.parse(uri ?? 'dart:core'), | |
| ); | |
| ClassLookup classLookup = libraryLookup.getClass(className); | |
| Class cls = classLookup.cls; | |
| DartTypeList? finalTypeArguments = typeArguments == null | |
| ? null | |
| : new DartTypeList.from(typeArguments); | |
| return new InterfaceType(cls, Nullability.nonNullable, finalTypeArguments); | |
| } | |
| bool isSubtypeOf(DartType subtype, DartType supertype) { | |
| return _visitor.typeEnvironment.isSubtypeOf(subtype, supertype); | |
| } | |
| } | |
| typedef GeneralAnalysisFunction = void Function( | |
| TreeNode node, | |
| AnalysisInterface interface, | |
| ); | |
| /// Generalized analyzer that uses a single [GeneralAnalysisFunction] on all | |
| /// [TreeNode]s. | |
| class GeneralAnalyzer extends AnalysisVisitor { | |
| final GeneralAnalysisFunction analyzer; | |
| new( | |
| DiagnosticMessageHandler onDiagnostic, | |
| Component component, | |
| bool Function(Uri uri)? analyzedUrisFilter, | |
| this.analyzer, | |
| ) : super(onDiagnostic, component, analyzedUrisFilter); | |
| @override | |
| void defaultTreeNode(TreeNode node) { | |
| analyzer(node, interface); | |
| super.defaultTreeNode(node); | |
| } | |
| } | |
| /// Returns a function that will perform [analysisFunction] on [TreeNode]s | |
| /// in a component, using [uriFilter] to filter which libraries that will be | |
| /// visited. | |
| PerformAnalysisFunction performGeneralAnalysis( | |
| UriFilter? uriFilter, | |
| GeneralAnalysisFunction analysisFunction, | |
| ) { | |
| return (DiagnosticMessageHandler onDiagnostic, Component component) { | |
| GeneralAnalyzer analyzer = new GeneralAnalyzer( | |
| onDiagnostic, | |
| component, | |
| uriFilter, | |
| analysisFunction, | |
| ); | |
| component.accept(analyzer); | |
| analyzer.printMessages(); | |
| }; | |
| } | |
| /// Helper class for looking up libraries in a [Component]. | |
| class ComponentLookup { | |
| final Component _component; | |
| new(this._component); | |
| Map<Uri, LibraryLookup>? _libraries; | |
| LibraryLookup getLibrary(Uri uri) { | |
| LibraryLookup? libraryLookup = (_libraries ??= new Map.fromIterable( | |
| _component.libraries, | |
| key: (library) => library.importUri, | |
| value: (library) => new LibraryLookup(library), | |
| ))[uri]; | |
| if (libraryLookup == null) { | |
| throw "Couldn't find library for '$uri'."; | |
| } | |
| return libraryLookup; | |
| } | |
| } | |
| /// Helper class for looking up classes and members in a [Library]. | |
| // TODO(johnniwinther): Support member lookup. | |
| class LibraryLookup { | |
| final Library library; | |
| new(this.library); | |
| Map<String, ClassLookup>? _classes; | |
| ClassLookup getClass(String name) { | |
| ClassLookup? classLookup = (_classes ??= new Map.fromIterable( | |
| library.classes, | |
| key: (cls) => cls.name, | |
| value: (cls) => new ClassLookup(cls), | |
| ))[name]; | |
| if (classLookup == null) { | |
| throw "Couldn't find class '$name' in ${library.importUri}."; | |
| } | |
| return classLookup; | |
| } | |
| } | |
| /// Helper class for looking up members in a [Class]. | |
| // TODO(johnniwinther): Support member lookup. | |
| class ClassLookup { | |
| final Class cls; | |
| new(this.cls); | |
| } | |
| /// Entry points used for analyzing cfe source code. | |
| // TODO(johnniwinther): Update this to include all files in the cfe, and not | |
| // only those reachable from 'compiler.dart'. | |
| final List<Uri> cfeOnlyEntryPoints = [ | |
| Uri.base.resolve('pkg/front_end/tool/compile.dart'), | |
| ]; | |
| /// Filter function used to only analyze cfe source code. | |
| bool cfeOnly(Uri uri) { | |
| String text = '$uri'; | |
| for (String path in [ | |
| 'package:_fe_analyzer_shared/', | |
| 'package:kernel/', | |
| 'package:front_end/', | |
| ]) { | |
| if (text.startsWith(path)) { | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| /// Entry points used for analyzing cfe and backend source code. | |
| // TODO(johnniwinther): Update this to include all files in cfe and backends, | |
| // and not only those reachable from these entry points. | |
| List<Uri> cfeAndBackendsEntryPoints = [ | |
| Uri.base.resolve('pkg/front_end/tool/compile.dart'), | |
| Uri.base.resolve('pkg/vm/lib/kernel_front_end.dart'), | |
| Uri.base.resolve('pkg/compiler/lib/src/dart2js.dart'), | |
| Uri.base.resolve('pkg/dev_compiler/bin/dartdevc.dart'), | |
| Uri.base.resolve('pkg/frontend_server/bin/frontend_server_starter.dart'), | |
| ]; | |
| /// Filter function used to only analyze cfe and backend source code. | |
| bool cfeAndBackends(Uri uri) { | |
| String text = '$uri'; | |
| for (String path in [ | |
| 'package:_fe_analyzer_shared/', | |
| 'package:kernel/', | |
| 'package:front_end/', | |
| 'package:frontend_server/', | |
| 'package:vm/', | |
| 'package:compiler/', | |
| 'package:dartdevc/', | |
| 'package:_js_interop_checks/', | |
| ]) { | |
| if (text.startsWith(path)) { | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| /// Filter function used to only analyze platform code. | |
| bool platformOnly(Uri uri) => uri.isScheme('dart'); |