| // 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: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 = (DiagnosticMessage 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; |
| |
| StaticTypeVisitorBase(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 = {}; |
| |
| AnalysisVisitor(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 = templateUnspecified |
| .withArguments(message) |
| .withLocation(uri, node.fileOffset, noLength); |
| FormattedMessage diagnosticMessage = locatedMessage.withFormatting( |
| formatWithLocationNoSdk(locatedMessage, Severity.warning, |
| location: location, uriToSource: component.uriToSource), |
| location.line, |
| location.column, |
| Severity.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; |
| |
| AnalysisInterface(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; |
| return new InterfaceType( |
| cls, |
| Nullability.nonNullable, |
| typeArguments ?? |
| new List<DartType>.generate( |
| cls.typeParameters.length, (index) => const DynamicType())); |
| } |
| |
| bool isSubtypeOf(DartType subtype, DartType supertype) { |
| return _visitor.typeEnvironment |
| .isSubtypeOf(subtype, supertype, SubtypeCheckMode.withNullabilities); |
| } |
| } |
| |
| 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; |
| |
| GeneralAnalyzer(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; |
| |
| ComponentLookup(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; |
| |
| LibraryLookup(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; |
| |
| ClassLookup(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/_fasta/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/_fasta/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'); |