blob: 97a061233703b4c16e165791d0a8cf6cb09755b3 [file] [log] [blame]
// 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);
}
}