| // Copyright (c) 2018, 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:collection' show SplayTreeMap; |
| import 'dart:convert' as json; |
| import 'dart:io'; |
| |
| import 'package:args/args.dart'; |
| import 'package:compiler/src/common.dart'; |
| import 'package:compiler/src/compiler.dart'; |
| import 'package:compiler/src/ir/util.dart'; |
| import 'package:compiler/src/util/memory_compiler.dart'; |
| import 'package:compiler/src/phase/load_kernel.dart' as load_kernel; |
| import 'package:expect/async_helper.dart'; |
| import 'package:expect/expect.dart'; |
| import 'package:front_end/src/api_unstable/dart2js.dart' show relativizeUri; |
| import 'package:kernel/ast.dart' as ir; |
| import 'package:kernel/class_hierarchy.dart' as ir; |
| import 'package:kernel/core_types.dart' as ir; |
| import 'package:kernel/type_environment.dart' as ir; |
| |
| import '../helpers/args_helper.dart'; |
| |
| main(List<String> args) { |
| ArgParser argParser = createArgParser(); |
| ArgResults argResults = argParser.parse(args); |
| |
| Uri? entryPoint = getEntryPoint(argResults); |
| if (entryPoint == null) { |
| throw ArgumentError("Missing entry point."); |
| } |
| Uri? librariesSpecificationUri = getLibrariesSpec(argResults); |
| Uri? packageConfig = getPackages(argResults); |
| List<String> options = getOptions(argResults); |
| run( |
| entryPoint, |
| null, |
| analyzedUrisFilter: (Uri uri) => !uri.isScheme('dart'), |
| librariesSpecificationUri: librariesSpecificationUri, |
| packageConfig: packageConfig, |
| options: options, |
| ); |
| } |
| |
| run( |
| Uri entryPoint, |
| String? allowedListPath, { |
| Map<String, String> memorySourceFiles = const {}, |
| Uri? librariesSpecificationUri, |
| Uri? packageConfig, |
| bool verbose = false, |
| bool generate = false, |
| List<String> options = const <String>[], |
| bool analyzedUrisFilter(Uri uri)?, |
| }) { |
| asyncTest(() async { |
| Compiler compiler = await compilerFor( |
| memorySourceFiles: memorySourceFiles, |
| librariesSpecificationUri: librariesSpecificationUri, |
| packageConfig: packageConfig, |
| entryPoint: entryPoint, |
| options: options, |
| ); |
| load_kernel.Output result = (await load_kernel.run( |
| load_kernel.Input( |
| compiler.options, |
| compiler.provider, |
| compiler.reporter, |
| compiler.initializedCompilerState, |
| false, |
| ), |
| ))!; |
| compiler.frontendStrategy.registerLoadedLibraries( |
| result.component, |
| result.libraries!, |
| ); |
| final coreTypes = ir.CoreTypes(result.component); |
| final classHierarchy = ir.ClassHierarchy(result.component, coreTypes); |
| final typeEnvironment = ir.TypeEnvironment(coreTypes, classHierarchy); |
| DynamicVisitor( |
| compiler.reporter, |
| result.component, |
| allowedListPath, |
| analyzedUrisFilter, |
| coreTypes, |
| typeEnvironment, |
| ).run(verbose: verbose, generate: generate); |
| }); |
| } |
| |
| class DynamicVisitor extends ir.RecursiveVisitor { |
| final DiagnosticReporter reporter; |
| final ir.Component component; |
| final String? _allowedListPath; |
| final bool Function(Uri uri)? analyzedUrisFilter; |
| late ir.StaticTypeContext staticTypeContext; |
| final ir.TypeEnvironment typeEnvironment; |
| final ir.CoreTypes coreTypes; |
| |
| Map _expectedJson = {}; |
| final Map<String, Map<String, List<DiagnosticMessage>>> _actualMessages = {}; |
| |
| DynamicVisitor( |
| this.reporter, |
| this.component, |
| this._allowedListPath, |
| this.analyzedUrisFilter, |
| this.coreTypes, |
| this.typeEnvironment, |
| ); |
| |
| void run({bool verbose = false, bool generate = false}) { |
| if (!generate && _allowedListPath != null) { |
| File file = File(_allowedListPath); |
| if (file.existsSync()) { |
| try { |
| _expectedJson = json.jsonDecode(file.readAsStringSync()); |
| } catch (e) { |
| Expect.fail('Error reading allowed list from $_allowedListPath: $e'); |
| } |
| } |
| } |
| component.accept(this); |
| if (generate && _allowedListPath != null) { |
| Map<String, Map<String, int>> actualJson = SplayTreeMap(); |
| _actualMessages.forEach(( |
| String uri, |
| Map<String, List<DiagnosticMessage>> actualMessagesMap, |
| ) { |
| Map<String, int> map = SplayTreeMap(); |
| actualMessagesMap.forEach(( |
| String message, |
| List<DiagnosticMessage> actualMessages, |
| ) { |
| map[message] = actualMessages.length; |
| }); |
| actualJson[uri] = map; |
| }); |
| |
| File(_allowedListPath).writeAsStringSync( |
| json.JsonEncoder.withIndent(' ').convert(actualJson), |
| ); |
| return; |
| } |
| |
| int errorCount = 0; |
| _expectedJson.forEach((uri, expectedMessages) { |
| Map<String, List<DiagnosticMessage>>? actualMessagesMap = |
| _actualMessages[uri]; |
| if (actualMessagesMap == null) { |
| print( |
| "Error: Allowed-listing of uri '$uri' isn't used. " |
| "Remove it from the allowed-list.", |
| ); |
| errorCount++; |
| } else { |
| expectedMessages.forEach((expectedMessage, expectedCount) { |
| List<DiagnosticMessage>? actualMessages = |
| actualMessagesMap[expectedMessage]; |
| if (actualMessages == null) { |
| print( |
| "Error: Allowed-listing of message '$expectedMessage' " |
| "in uri '$uri' isn't used. Remove it from the allowed-list.", |
| ); |
| errorCount++; |
| } else { |
| int actualCount = actualMessages.length; |
| if (actualCount != expectedCount) { |
| print( |
| "Error: Unexpected count of allowed message " |
| "'$expectedMessage' in uri '$uri'. " |
| "Expected $expectedCount, actual $actualCount:", |
| ); |
| print( |
| '----------------------------------------------------------', |
| ); |
| for (DiagnosticMessage message in actualMessages) { |
| reporter.reportError(message); |
| } |
| print( |
| '----------------------------------------------------------', |
| ); |
| errorCount++; |
| } |
| } |
| }); |
| actualMessagesMap.forEach(( |
| String message, |
| List<DiagnosticMessage> actualMessages, |
| ) { |
| if (!expectedMessages.containsKey(message)) { |
| for (DiagnosticMessage message in actualMessages) { |
| reporter.reportError(message); |
| errorCount++; |
| } |
| } |
| }); |
| } |
| }); |
| _actualMessages.forEach(( |
| String uri, |
| Map<String, List<DiagnosticMessage>> actualMessagesMap, |
| ) { |
| if (!_expectedJson.containsKey(uri)) { |
| actualMessagesMap.forEach(( |
| String message, |
| List<DiagnosticMessage> actualMessages, |
| ) { |
| for (DiagnosticMessage message in actualMessages) { |
| reporter.reportError(message); |
| errorCount++; |
| } |
| }); |
| } |
| }); |
| if (errorCount != 0) { |
| print('$errorCount error(s) found.'); |
| print(""" |
| |
| ******************************************************************************** |
| * Unexpected dynamic invocations found by test: |
| * |
| * ${relativizeUri(Uri.base, Platform.script, Platform.isWindows)} |
| * |
| * Please address the reported errors, or, if the errors are as expected, run |
| * |
| * dart ${relativizeUri(Uri.base, Platform.script, Platform.isWindows)} -g |
| * |
| * to update the expectation file. |
| ******************************************************************************** |
| """); |
| exit(-1); |
| } |
| if (verbose) { |
| _actualMessages.forEach(( |
| String uri, |
| Map<String, List<DiagnosticMessage>> actualMessagesMap, |
| ) { |
| actualMessagesMap.forEach(( |
| String message, |
| List<DiagnosticMessage> actualMessages, |
| ) { |
| for (DiagnosticMessage message in actualMessages) { |
| reporter.reportErrorMessage( |
| message.sourceSpan, |
| MessageKind.generic, |
| {'text': '${message.message} (allowed)'}, |
| ); |
| } |
| }); |
| }); |
| } else { |
| int total = 0; |
| _actualMessages.forEach(( |
| String uri, |
| Map<String, List<DiagnosticMessage>> actualMessagesMap, |
| ) { |
| int count = 0; |
| actualMessagesMap.forEach(( |
| String message, |
| List<DiagnosticMessage> actualMessages, |
| ) { |
| count += actualMessages.length; |
| }); |
| |
| print('${count} error(s) allowed in $uri'); |
| total += count; |
| }); |
| if (total > 0) { |
| print('${total} error(s) allowed in total.'); |
| } |
| } |
| } |
| |
| @override |
| void visitLibrary(ir.Library node) { |
| if (analyzedUrisFilter != null) { |
| if (analyzedUrisFilter!(node.importUri)) { |
| return node.visitChildren(this); |
| } |
| } else { |
| return node.visitChildren(this); |
| } |
| } |
| |
| @override |
| void visitProcedure(ir.Procedure node) { |
| if (node.kind == ir.ProcedureKind.Factory && node.isRedirectingFactory) { |
| // Don't visit redirecting factories. |
| return; |
| } |
| super.visitProcedure(node); |
| } |
| |
| @override |
| void defaultMember(ir.Member node) { |
| staticTypeContext = ir.StaticTypeContext(node, typeEnvironment); |
| super.defaultMember(node); |
| } |
| |
| @override |
| void visitDynamicGet(ir.DynamicGet node) { |
| if (node.receiver.getStaticType(staticTypeContext) is ir.DynamicType) { |
| registerError(node, "Dynamic access of '${node.name}'."); |
| } |
| } |
| |
| @override |
| void visitDynamicSet(ir.DynamicSet node) { |
| if (node.receiver.getStaticType(staticTypeContext) is ir.DynamicType) { |
| registerError(node, "Dynamic update to '${node.name}'."); |
| } |
| } |
| |
| @override |
| void visitDynamicInvocation(ir.DynamicInvocation node) { |
| if (node.receiver.getStaticType(staticTypeContext) is ir.DynamicType) { |
| registerError(node, "Dynamic invocation of '${node.name}'."); |
| } |
| } |
| |
| String reportAssertionFailure(ir.TreeNode node, String message) { |
| SourceSpan span = computeSourceSpanFromTreeNode(node); |
| Uri uri = span.uri; |
| if (uri.isScheme('org-dartlang-sdk')) { |
| span = SourceSpan( |
| Uri.base.resolve(uri.path.substring(1)), |
| span.begin, |
| span.end, |
| ); |
| } |
| DiagnosticMessage diagnosticMessage = reporter.createMessage( |
| span, |
| MessageKind.generic, |
| {'text': message}, |
| ); |
| reporter.reportError(diagnosticMessage); |
| return message; |
| } |
| |
| void registerError(ir.TreeNode node, String message) { |
| SourceSpan span = computeSourceSpanFromTreeNode(node); |
| Uri uri = span.uri; |
| String uriString = relativizeUri(Uri.base, uri, Platform.isWindows); |
| Map<String, List<DiagnosticMessage>> actualMap = _actualMessages |
| .putIfAbsent(uriString, () => <String, List<DiagnosticMessage>>{}); |
| if (uri.isScheme('org-dartlang-sdk')) { |
| span = SourceSpan( |
| Uri.base.resolve(uri.path.substring(1)), |
| span.begin, |
| span.end, |
| ); |
| } |
| DiagnosticMessage diagnosticMessage = reporter.createMessage( |
| span, |
| MessageKind.generic, |
| {'text': message}, |
| ); |
| actualMap |
| .putIfAbsent(message, () => <DiagnosticMessage>[]) |
| .add(diagnosticMessage); |
| } |
| } |