mirror of
https://github.com/dart-lang/sdk
synced 2024-09-15 23:49:47 +00:00
[cfe] Add test for type arguments on map().toList()
This updates the analysis helpers to add a test for the `map().toList()` problem addressed in https://dart-review.googlesource.com/c/sdk/+/241605 Included in the CL is the addition of the 'analyze.dart' tools, which can be used for advanced code searches. This was used to find the occurences of `map().toList()`. Change-Id: I6e9da282e37fde4534ed5e308260f092779d0750 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/241744 Reviewed-by: Chloe Stefantsova <cstefantsova@google.com> Commit-Queue: Johnni Winther <johnniwinther@google.com>
This commit is contained in:
parent
f26c0e2056
commit
6bf48988c8
|
@ -1952,7 +1952,7 @@ class IncrementalCompiler implements IncrementalKernelGenerator {
|
|||
FunctionNode parameters = new FunctionNode(null,
|
||||
typeParameters: typeDefinitions,
|
||||
positionalParameters: definitions.keys
|
||||
.map((name) =>
|
||||
.map<VariableDeclaration>((name) =>
|
||||
new VariableDeclarationImpl(name, 0, type: definitions[name])
|
||||
..fileOffset = cls?.fileOffset ??
|
||||
extension?.fileOffset ??
|
||||
|
|
315
pkg/front_end/lib/src/testing/analysis_helper.dart
Normal file
315
pkg/front_end/lib/src/testing/analysis_helper.dart
Normal file
|
@ -0,0 +1,315 @@
|
|||
// 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/api_prototype/compiler_options.dart';
|
||||
import 'package:front_end/src/api_prototype/kernel_generator.dart';
|
||||
import 'package:front_end/src/api_prototype/terminal_color_support.dart';
|
||||
import 'package:front_end/src/compute_platform_binaries_location.dart';
|
||||
import 'package:front_end/src/fasta/command_line_reporting.dart';
|
||||
import 'package:front_end/src/fasta/fasta_codes.dart';
|
||||
import 'package:front_end/src/fasta/kernel/redirecting_factory_body.dart';
|
||||
import 'package:front_end/src/kernel_generator_impl.dart';
|
||||
import 'package:kernel/ast.dart';
|
||||
import 'package:kernel/class_hierarchy.dart';
|
||||
import 'package:kernel/core_types.dart';
|
||||
import 'package:kernel/type_environment.dart';
|
||||
|
||||
typedef PerformAnalysisFunction = void Function(
|
||||
DiagnosticMessageHandler onDiagnostic, Component component);
|
||||
typedef UriFilter = bool Function(Uri uri);
|
||||
|
||||
Future<void> runAnalysis(
|
||||
List<Uri> entryPoints, PerformAnalysisFunction performAnalysis) async {
|
||||
CompilerOptions options = new CompilerOptions();
|
||||
options.sdkRoot = computePlatformBinariesLocation(forceBuildDir: true);
|
||||
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 && isRedirectingFactory(node)) {
|
||||
// Don't visit redirecting factories.
|
||||
return;
|
||||
}
|
||||
staticTypeContext = new StaticTypeContext(node, typeEnvironment);
|
||||
super.visitProcedure(node);
|
||||
staticTypeContext = null;
|
||||
}
|
||||
|
||||
@override
|
||||
void visitField(Field node) {
|
||||
if (isRedirectingFactoryField(node)) {
|
||||
// Skip synthetic .dill members.
|
||||
return;
|
||||
}
|
||||
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(
|
||||
format(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/bin/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;
|
||||
}
|
|
@ -645,6 +645,7 @@ interior
|
|||
interleaved
|
||||
intermediate
|
||||
internet
|
||||
interop
|
||||
interpolations
|
||||
interrupted
|
||||
intersects
|
||||
|
|
|
@ -2,27 +2,81 @@
|
|||
// 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 'analysis_helper.dart';
|
||||
import 'package:front_end/src/testing/analysis_helper.dart';
|
||||
import 'verifying_analysis.dart';
|
||||
|
||||
/// Filter function used to only analysis 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;
|
||||
}
|
||||
import 'package:_fe_analyzer_shared/src/messages/diagnostic_message.dart';
|
||||
import 'package:kernel/ast.dart';
|
||||
|
||||
Future<void> main(List<String> args) async {
|
||||
await run(Uri.base.resolve('pkg/front_end/tool/_fasta/compile.dart'),
|
||||
'pkg/front_end/test/static_types/cfe_allowed.json',
|
||||
await run(
|
||||
cfeOnlyEntryPoints, 'pkg/front_end/test/static_types/cfe_allowed.json',
|
||||
analyzedUrisFilter: cfeOnly,
|
||||
verbose: args.contains('-v'),
|
||||
generate: args.contains('-g'));
|
||||
}
|
||||
|
||||
Future<void> run(List<Uri> entryPoints, String allowedListPath,
|
||||
{bool verbose = false,
|
||||
bool generate = false,
|
||||
bool Function(Uri uri)? analyzedUrisFilter}) async {
|
||||
await runAnalysis(entryPoints,
|
||||
(DiagnosticMessageHandler onDiagnostic, Component component) {
|
||||
new DynamicVisitor(
|
||||
onDiagnostic, component, allowedListPath, analyzedUrisFilter)
|
||||
.run(verbose: verbose, generate: generate);
|
||||
});
|
||||
}
|
||||
|
||||
class DynamicVisitor extends VerifyingAnalysis {
|
||||
// TODO(johnniwinther): Enable this when it is less noisy.
|
||||
static const bool checkReturnTypes = false;
|
||||
|
||||
DynamicVisitor(DiagnosticMessageHandler onDiagnostic, Component component,
|
||||
String? allowedListPath, UriFilter? analyzedUrisFilter)
|
||||
: super(onDiagnostic, component, allowedListPath, analyzedUrisFilter);
|
||||
|
||||
@override
|
||||
void visitDynamicGet(DynamicGet node) {
|
||||
registerError(node, "Dynamic access of '${node.name}'.");
|
||||
super.visitDynamicGet(node);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitDynamicSet(DynamicSet node) {
|
||||
registerError(node, "Dynamic update to '${node.name}'.");
|
||||
super.visitDynamicSet(node);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitDynamicInvocation(DynamicInvocation node) {
|
||||
registerError(node, "Dynamic invocation of '${node.name}'.");
|
||||
super.visitDynamicInvocation(node);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitFunctionDeclaration(FunctionDeclaration node) {
|
||||
if (checkReturnTypes && node.function.returnType is DynamicType) {
|
||||
registerError(node, "Dynamic return type");
|
||||
}
|
||||
super.visitFunctionDeclaration(node);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitFunctionExpression(FunctionExpression node) {
|
||||
if (checkReturnTypes && node.function.returnType is DynamicType) {
|
||||
registerError(node, "Dynamic return type");
|
||||
}
|
||||
super.visitFunctionExpression(node);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitProcedure(Procedure node) {
|
||||
if (checkReturnTypes &&
|
||||
node.function.returnType is DynamicType &&
|
||||
node.name.text != 'noSuchMethod') {
|
||||
registerError(node, "Dynamic return type on $node");
|
||||
}
|
||||
super.visitProcedure(node);
|
||||
}
|
||||
}
|
||||
|
|
1
pkg/front_end/test/static_types/type_arguments.json
Normal file
1
pkg/front_end/test/static_types/type_arguments.json
Normal file
|
@ -0,0 +1 @@
|
|||
{}
|
94
pkg/front_end/test/static_types/type_arguments_test.dart
Normal file
94
pkg/front_end/test/static_types/type_arguments_test.dart
Normal file
|
@ -0,0 +1,94 @@
|
|||
// 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:front_end/src/testing/analysis_helper.dart';
|
||||
import 'verifying_analysis.dart';
|
||||
|
||||
import 'package:_fe_analyzer_shared/src/messages/diagnostic_message.dart';
|
||||
import 'package:kernel/ast.dart';
|
||||
|
||||
Future<void> main(List<String> args) async {
|
||||
await run(cfeAndBackendsEntryPoints,
|
||||
'pkg/front_end/test/static_types/type_arguments.json',
|
||||
analyzedUrisFilter: cfeAndBackends,
|
||||
verbose: args.contains('-v'),
|
||||
generate: args.contains('-g'));
|
||||
}
|
||||
|
||||
Future<void> run(List<Uri> entryPoints, String allowedListPath,
|
||||
{bool verbose = false,
|
||||
bool generate = false,
|
||||
bool Function(Uri uri)? analyzedUrisFilter}) async {
|
||||
await runAnalysis(entryPoints,
|
||||
(DiagnosticMessageHandler onDiagnostic, Component component) {
|
||||
new TypeArgumentsVisitor(
|
||||
onDiagnostic, component, allowedListPath, analyzedUrisFilter)
|
||||
.run(verbose: verbose, generate: generate);
|
||||
});
|
||||
}
|
||||
|
||||
class TypeArgumentsVisitor extends VerifyingAnalysis {
|
||||
TypeArgumentsVisitor(
|
||||
DiagnosticMessageHandler onDiagnostic,
|
||||
Component component,
|
||||
String? allowedListPath,
|
||||
UriFilter? analyzedUrisFilter)
|
||||
: super(onDiagnostic, component, allowedListPath, analyzedUrisFilter);
|
||||
|
||||
@override
|
||||
void visitInstanceInvocation(InstanceInvocation node) {
|
||||
if (node.name.text == 'toList') {
|
||||
TreeNode receiver = node.receiver;
|
||||
if (receiver is InstanceInvocation &&
|
||||
receiver.name.text == 'map' &&
|
||||
receiver.arguments.types.length == 1) {
|
||||
String astUri = 'package:kernel/ast.dart';
|
||||
InterfaceType expressionType =
|
||||
interface.createInterfaceType('Expression', uri: astUri);
|
||||
InterfaceType statementType =
|
||||
interface.createInterfaceType('Statement', uri: astUri);
|
||||
InterfaceType assertStatementType =
|
||||
interface.createInterfaceType('AssertStatement', uri: astUri);
|
||||
InterfaceType variableDeclarationType =
|
||||
interface.createInterfaceType('VariableDeclaration', uri: astUri);
|
||||
DartType typeArgument = receiver.arguments.types.single;
|
||||
if (interface.isSubtypeOf(typeArgument, expressionType) &&
|
||||
typeArgument != expressionType) {
|
||||
registerError(
|
||||
node,
|
||||
"map().toList() with type argument "
|
||||
"${typeArgument} instead of ${expressionType}");
|
||||
}
|
||||
if (interface.isSubtypeOf(typeArgument, statementType)) {
|
||||
if (interface.isSubtypeOf(typeArgument, assertStatementType)) {
|
||||
// [AssertStatement] is used as an exclusive member of
|
||||
// `InstanceCreation.asserts`.
|
||||
if (typeArgument != assertStatementType) {
|
||||
registerError(
|
||||
node,
|
||||
"map().toList() with type argument "
|
||||
"${typeArgument} instead of ${assertStatementType}");
|
||||
}
|
||||
} else if (interface.isSubtypeOf(
|
||||
typeArgument, variableDeclarationType)) {
|
||||
// [VariableDeclaration] is used as an exclusive member of, for
|
||||
// instance, `FunctionNode.positionalParameters`.
|
||||
if (typeArgument != variableDeclarationType) {
|
||||
registerError(
|
||||
node,
|
||||
"map().toList() with type argument "
|
||||
"${typeArgument} instead of ${variableDeclarationType}");
|
||||
}
|
||||
} else if (typeArgument != statementType) {
|
||||
registerError(
|
||||
node,
|
||||
"map().toList() with type argument "
|
||||
"${typeArgument} instead of ${statementType}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
super.visitInstanceInvocation(node);
|
||||
}
|
||||
}
|
|
@ -7,93 +7,21 @@ import 'dart:io';
|
|||
|
||||
import 'package:_fe_analyzer_shared/src/messages/diagnostic_message.dart';
|
||||
import 'package:expect/expect.dart';
|
||||
import 'package:front_end/src/api_prototype/compiler_options.dart';
|
||||
import 'package:front_end/src/api_prototype/kernel_generator.dart';
|
||||
import 'package:front_end/src/api_prototype/terminal_color_support.dart';
|
||||
import 'package:front_end/src/compute_platform_binaries_location.dart';
|
||||
import 'package:front_end/src/fasta/command_line_reporting.dart';
|
||||
import 'package:front_end/src/fasta/fasta_codes.dart';
|
||||
import 'package:front_end/src/fasta/kernel/redirecting_factory_body.dart';
|
||||
import 'package:front_end/src/kernel_generator_impl.dart';
|
||||
import 'package:front_end/src/testing/analysis_helper.dart';
|
||||
import 'package:kernel/ast.dart';
|
||||
import 'package:kernel/class_hierarchy.dart';
|
||||
import 'package:kernel/core_types.dart';
|
||||
import 'package:kernel/type_environment.dart';
|
||||
|
||||
Future<void> run(Uri entryPoint, String allowedListPath,
|
||||
{bool verbose = false,
|
||||
bool generate = false,
|
||||
bool Function(Uri uri)? analyzedUrisFilter}) async {
|
||||
CompilerOptions options = new CompilerOptions();
|
||||
options.sdkRoot = computePlatformBinariesLocation(forceBuildDir: true);
|
||||
|
||||
options.onDiagnostic = (DiagnosticMessage message) {
|
||||
printDiagnosticMessage(message, print);
|
||||
};
|
||||
InternalCompilerResult compilerResult = await kernelForProgramInternal(
|
||||
entryPoint, options, retainDataForTesting: true, requireMain: false)
|
||||
as InternalCompilerResult;
|
||||
|
||||
new DynamicVisitor(options.onDiagnostic!, compilerResult.component!,
|
||||
allowedListPath, analyzedUrisFilter)
|
||||
.run(verbose: verbose, generate: generate);
|
||||
}
|
||||
|
||||
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 && isRedirectingFactory(node)) {
|
||||
// Don't visit redirecting factories.
|
||||
return;
|
||||
}
|
||||
staticTypeContext = new StaticTypeContext(node, typeEnvironment);
|
||||
super.visitProcedure(node);
|
||||
staticTypeContext = null;
|
||||
}
|
||||
|
||||
@override
|
||||
void visitField(Field node) {
|
||||
if (isRedirectingFactoryField(node)) {
|
||||
// Skip synthetic .dill members.
|
||||
return;
|
||||
}
|
||||
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 DynamicVisitor extends StaticTypeVisitorBase {
|
||||
// TODO(johnniwinther): Enable this when it is less noisy.
|
||||
static const bool checkReturnTypes = false;
|
||||
|
||||
final DiagnosticMessageHandler onDiagnostic;
|
||||
final Component component;
|
||||
/// [AnalysisVisitor] that supports tracking error/problem occurrences in an
|
||||
/// allowed list file.
|
||||
class VerifyingAnalysis extends AnalysisVisitor {
|
||||
final String? _allowedListPath;
|
||||
final bool Function(Uri uri)? analyzedUrisFilter;
|
||||
|
||||
Map _expectedJson = {};
|
||||
Map<String, Map<String, List<FormattedMessage>>> _actualMessages = {};
|
||||
|
||||
DynamicVisitor(this.onDiagnostic, this.component, this._allowedListPath,
|
||||
this.analyzedUrisFilter)
|
||||
: super(
|
||||
component, new ClassHierarchy(component, new CoreTypes(component)));
|
||||
VerifyingAnalysis(DiagnosticMessageHandler onDiagnostic, Component component,
|
||||
this._allowedListPath, UriFilter? analyzedUrisFilter)
|
||||
: super(onDiagnostic, component, analyzedUrisFilter);
|
||||
|
||||
void run({bool verbose = false, bool generate = false}) {
|
||||
if (!generate && _allowedListPath != null) {
|
||||
|
@ -109,7 +37,7 @@ class DynamicVisitor extends StaticTypeVisitorBase {
|
|||
component.accept(this);
|
||||
if (generate && _allowedListPath != null) {
|
||||
Map<String, Map<String, int>> actualJson = {};
|
||||
_actualMessages.forEach(
|
||||
forEachMessage(
|
||||
(String uri, Map<String, List<FormattedMessage>> actualMessagesMap) {
|
||||
Map<String, int> map = {};
|
||||
actualMessagesMap
|
||||
|
@ -127,7 +55,7 @@ class DynamicVisitor extends StaticTypeVisitorBase {
|
|||
int errorCount = 0;
|
||||
_expectedJson.forEach((uri, expectedMessages) {
|
||||
Map<String, List<FormattedMessage>>? actualMessagesMap =
|
||||
_actualMessages[uri];
|
||||
getMessagesForUri(uri);
|
||||
if (actualMessagesMap == null) {
|
||||
print("Error: Allowed-listing of uri '$uri' isn't used. "
|
||||
"Remove it from the allowed-list.");
|
||||
|
@ -168,7 +96,7 @@ class DynamicVisitor extends StaticTypeVisitorBase {
|
|||
});
|
||||
}
|
||||
});
|
||||
_actualMessages.forEach(
|
||||
forEachMessage(
|
||||
(String uri, Map<String, List<FormattedMessage>> actualMessagesMap) {
|
||||
if (!_expectedJson.containsKey(uri)) {
|
||||
actualMessagesMap
|
||||
|
@ -185,7 +113,7 @@ class DynamicVisitor extends StaticTypeVisitorBase {
|
|||
print("""
|
||||
|
||||
********************************************************************************
|
||||
* Unexpected dynamic invocations found by test:
|
||||
* Unexpected code patterns found by test:
|
||||
*
|
||||
* ${relativizeUri(Platform.script)}
|
||||
*
|
||||
|
@ -199,7 +127,7 @@ class DynamicVisitor extends StaticTypeVisitorBase {
|
|||
exit(-1);
|
||||
}
|
||||
if (verbose) {
|
||||
_actualMessages.forEach(
|
||||
forEachMessage(
|
||||
(String uri, Map<String, List<FormattedMessage>> actualMessagesMap) {
|
||||
actualMessagesMap
|
||||
.forEach((String message, List<FormattedMessage> actualMessages) {
|
||||
|
@ -234,7 +162,7 @@ class DynamicVisitor extends StaticTypeVisitorBase {
|
|||
});
|
||||
} else {
|
||||
int total = 0;
|
||||
_actualMessages.forEach(
|
||||
forEachMessage(
|
||||
(String uri, Map<String, List<FormattedMessage>> actualMessagesMap) {
|
||||
int count = 0;
|
||||
actualMessagesMap
|
||||
|
@ -251,83 +179,7 @@ class DynamicVisitor extends StaticTypeVisitorBase {
|
|||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void visitLibrary(Library node) {
|
||||
if (analyzedUrisFilter != null) {
|
||||
if (analyzedUrisFilter!(node.importUri)) {
|
||||
super.visitLibrary(node);
|
||||
}
|
||||
} else {
|
||||
super.visitLibrary(node);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void visitDynamicGet(DynamicGet node) {
|
||||
registerError(node, "Dynamic access of '${node.name}'.");
|
||||
super.visitDynamicGet(node);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitDynamicSet(DynamicSet node) {
|
||||
registerError(node, "Dynamic update to '${node.name}'.");
|
||||
super.visitDynamicSet(node);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitDynamicInvocation(DynamicInvocation node) {
|
||||
registerError(node, "Dynamic invocation of '${node.name}'.");
|
||||
super.visitDynamicInvocation(node);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitFunctionDeclaration(FunctionDeclaration node) {
|
||||
if (checkReturnTypes && node.function.returnType is DynamicType) {
|
||||
registerError(node, "Dynamic return type");
|
||||
}
|
||||
super.visitFunctionDeclaration(node);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitFunctionExpression(FunctionExpression node) {
|
||||
if (checkReturnTypes && node.function.returnType is DynamicType) {
|
||||
registerError(node, "Dynamic return type");
|
||||
}
|
||||
super.visitFunctionExpression(node);
|
||||
}
|
||||
|
||||
@override
|
||||
void visitProcedure(Procedure node) {
|
||||
if (checkReturnTypes &&
|
||||
node.function.returnType is DynamicType &&
|
||||
node.name.text != 'noSuchMethod') {
|
||||
registerError(node, "Dynamic return type on $node");
|
||||
}
|
||||
super.visitProcedure(node);
|
||||
}
|
||||
|
||||
void registerError(TreeNode node, String message) {
|
||||
Location location = node.location!;
|
||||
Uri uri = location.file;
|
||||
String uriString = relativizeUri(uri)!;
|
||||
Map<String, List<FormattedMessage>> actualMap = _actualMessages.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(
|
||||
format(locatedMessage, Severity.warning,
|
||||
location: location, uriToSource: component.uriToSource),
|
||||
location.line,
|
||||
location.column,
|
||||
Severity.warning,
|
||||
[]);
|
||||
actualMap
|
||||
.putIfAbsent(message, () => <FormattedMessage>[])
|
||||
.add(diagnosticMessage);
|
||||
registerMessage(node, message);
|
||||
}
|
||||
}
|
33
pkg/front_end/tool/analyze.dart
Normal file
33
pkg/front_end/tool/analyze.dart
Normal file
|
@ -0,0 +1,33 @@
|
|||
// 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:front_end/src/testing/analysis_helper.dart';
|
||||
import 'package:kernel/kernel.dart';
|
||||
|
||||
Future<void> main(List<String> arguments) async {
|
||||
await runAnalysis(
|
||||
cfeAndBackendsEntryPoints,
|
||||
performGeneralAnalysis(cfeAndBackends,
|
||||
(TreeNode node, AnalysisInterface interface) {
|
||||
// Use 'analyze.dart' to perform advanced analysis/code search by
|
||||
// replacing the "example analysis" with a custom analysis.
|
||||
|
||||
// Example analysis:
|
||||
if (node is InstanceInvocation && node.name.text == 'toList') {
|
||||
TreeNode receiver = node.receiver;
|
||||
if (receiver is InstanceInvocation &&
|
||||
receiver.name.text == 'map' &&
|
||||
receiver.arguments.types.length == 1) {
|
||||
InterfaceType expressionType = interface.createInterfaceType(
|
||||
'Expression',
|
||||
uri: 'package:kernel/ast.dart');
|
||||
DartType typeArgument = receiver.arguments.types.single;
|
||||
if (interface.isSubtypeOf(typeArgument, expressionType) &&
|
||||
typeArgument != expressionType) {
|
||||
interface.reportMessage(node, "map().toList()");
|
||||
}
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
Loading…
Reference in a new issue