Add tests for dynamic access in platform and dart2js code.
This is a step towards making our own code use Dart 2 rather that
Dart 1 semantics.
For instance, the js-runtime libraries often use `dynamic` to avoid
extra type checking in Dart 1 checked mode, which has the consequence
that we have _more_ checking in Dart 2 (or just worse inference
baselines).
The VM is now optimizing for the typed paths and we should therefore
avoid unneeded dynamic accesses in dart2js code itself.
Change-Id: I793ef6c478c4c1b8caa1513990baafc598c462d8
Reviewed-on: https://dart-review.googlesource.com/74380
Commit-Queue: Johnni Winther <johnniwinther@google.com>
Reviewed-by: Stephen Adams <sra@google.com>
diff --git a/tests/compiler/dart2js/analyses/analysis_helper.dart b/tests/compiler/dart2js/analyses/analysis_helper.dart
new file mode 100644
index 0000000..9c6a5d9
--- /dev/null
+++ b/tests/compiler/dart2js/analyses/analysis_helper.dart
@@ -0,0 +1,214 @@
+// 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 'package:async_helper/async_helper.dart';
+import 'package:compiler/src/compiler.dart';
+import 'package:compiler/src/diagnostics/diagnostic_listener.dart';
+import 'package:compiler/src/diagnostics/messages.dart';
+import 'package:compiler/src/diagnostics/source_span.dart';
+import 'package:compiler/src/library_loader.dart';
+import 'package:compiler/src/ir/util.dart';
+import 'package:expect/expect.dart';
+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/memory_compiler.dart';
+
+// TODO(johnniwinther): Update allowed-listing to mention specific properties.
+run(Uri entryPoint,
+ {Map<String, String> memorySourceFiles = const {},
+ Map<String, List<String>> allowedList,
+ bool verbose = false}) {
+ asyncTest(() async {
+ Compiler compiler = await compilerFor(memorySourceFiles: memorySourceFiles);
+ LoadedLibraries loadedLibraries =
+ await compiler.libraryLoader.loadLibraries(entryPoint);
+ new DynamicVisitor(
+ compiler.reporter, loadedLibraries.component, allowedList)
+ .run(verbose: verbose);
+ });
+}
+
+// TODO(johnniwinther): Add improved type promotion to handle negative
+// reasoning.
+// TODO(johnniwinther): Use this visitor in kernel impact computation.
+abstract class StaticTypeVisitor extends ir.Visitor<ir.DartType> {
+ ir.Component get component;
+ ir.TypeEnvironment _typeEnvironment;
+ bool _isStaticTypePrepared = false;
+
+ @override
+ ir.DartType defaultNode(ir.Node node) {
+ node.visitChildren(this);
+ return null;
+ }
+
+ @override
+ ir.DartType defaultExpression(ir.Expression node) {
+ defaultNode(node);
+ return getStaticType(node);
+ }
+
+ ir.DartType getStaticType(ir.Expression node) {
+ if (!_isStaticTypePrepared) {
+ _isStaticTypePrepared = true;
+ try {
+ _typeEnvironment ??= new ir.TypeEnvironment(
+ new ir.CoreTypes(component), new ir.ClassHierarchy(component));
+ } catch (e) {}
+ }
+ if (_typeEnvironment == null) {
+ // The class hierarchy crashes on multiple inheritance. Use `dynamic`
+ // as static type.
+ return const ir.DynamicType();
+ }
+ ir.TreeNode enclosingClass = node;
+ while (enclosingClass != null && enclosingClass is! ir.Class) {
+ enclosingClass = enclosingClass.parent;
+ }
+ try {
+ _typeEnvironment.thisType =
+ enclosingClass is ir.Class ? enclosingClass.thisType : null;
+ return node.getStaticType(_typeEnvironment);
+ } catch (e) {
+ // The static type computation crashes on type errors. Use `dynamic`
+ // as static type.
+ return const ir.DynamicType();
+ }
+ }
+}
+
+// TODO(johnniwinther): Handle dynamic access of Object properties/methods
+// separately.
+class DynamicVisitor extends StaticTypeVisitor {
+ final DiagnosticReporter reporter;
+ final ir.Component component;
+ final Map<String, List<String>> allowedList;
+ int _errorCount = 0;
+ Map<String, Set<String>> _encounteredAllowedListedErrors =
+ <String, Set<String>>{};
+ Map<Uri, List<DiagnosticMessage>> _allowedListedErrors =
+ <Uri, List<DiagnosticMessage>>{};
+
+ DynamicVisitor(this.reporter, this.component, this.allowedList);
+
+ void run({bool verbose = false}) {
+ component.accept(this);
+ bool failed = false;
+ if (_errorCount != 0) {
+ print('$_errorCount error(s) found.');
+ failed = true;
+ }
+ allowedList.forEach((String file, List<String> messageParts) {
+ Set<String> encounteredParts = _encounteredAllowedListedErrors[file];
+ if (encounteredParts == null) {
+ print("Allowed-listing of path '$file' isn't used. "
+ "Remove it from the allowed-list.");
+ failed = true;
+ } else if (messageParts != null) {
+ for (String messagePart in messageParts) {
+ if (!encounteredParts.contains(messagePart)) {
+ print("Allowed-listing of message '$messagePart' in path '$file' "
+ "isn't used. Remove it from the allowed-list.");
+ }
+ failed = true;
+ }
+ }
+ });
+ Expect.isFalse(failed, "Errors occurred.");
+ if (verbose) {
+ _allowedListedErrors.forEach((Uri uri, List<DiagnosticMessage> messages) {
+ for (DiagnosticMessage message in messages) {
+ reporter.reportError(message);
+ }
+ });
+ } else {
+ int total = 0;
+ _allowedListedErrors.forEach((Uri uri, List<DiagnosticMessage> messages) {
+ print('${messages.length} error(s) allowed in $uri');
+ total += messages.length;
+ });
+ if (total > 0) {
+ print('${total} error(s) allowed in total.');
+ }
+ }
+ }
+
+ @override
+ ir.DartType visitPropertyGet(ir.PropertyGet node) {
+ ir.DartType result = super.visitPropertyGet(node);
+ ir.DartType type = node.receiver.accept(this);
+ if (type is ir.DynamicType) {
+ reportError(node, "Dynamic access of '${node.name}'.");
+ }
+ return result;
+ }
+
+ @override
+ ir.DartType visitPropertySet(ir.PropertySet node) {
+ ir.DartType result = super.visitPropertySet(node);
+ ir.DartType type = node.receiver.accept(this);
+ if (type is ir.DynamicType) {
+ reportError(node, "Dynamic update to '${node.name}'.");
+ }
+ return result;
+ }
+
+ @override
+ ir.DartType visitMethodInvocation(ir.MethodInvocation node) {
+ ir.DartType result = super.visitMethodInvocation(node);
+ if (node.name.name == '==' &&
+ node.arguments.positional.single is ir.NullLiteral) {
+ return result;
+ }
+ ir.DartType type = node.receiver.accept(this);
+ if (type is ir.DynamicType) {
+ reportError(node, "Dynamic invocation of '${node.name}'.");
+ }
+ return result;
+ }
+
+ void reportError(ir.Node node, String message) {
+ SourceSpan span = computeSourceSpanFromTreeNode(node);
+ Uri uri = span.uri;
+ if (uri.scheme == 'org-dartlang-sdk') {
+ uri = Uri.base.resolve(uri.path.substring(1));
+ span = new SourceSpan(uri, span.begin, span.end);
+ }
+ bool whiteListed = false;
+ allowedList.forEach((String file, List<String> messageParts) {
+ if (uri.path.endsWith(file)) {
+ if (messageParts == null) {
+ // All errors are whitelisted.
+ whiteListed = true;
+ message += ' (white-listed)';
+ _encounteredAllowedListedErrors.putIfAbsent(
+ file, () => new Set<String>());
+ } else {
+ for (String messagePart in messageParts) {
+ if (message.contains(messagePart)) {
+ _encounteredAllowedListedErrors
+ .putIfAbsent(file, () => new Set<String>())
+ .add(messagePart);
+ message += ' (allowed)';
+ whiteListed = true;
+ }
+ }
+ }
+ }
+ });
+ DiagnosticMessage diagnosticMessage =
+ reporter.createMessage(span, MessageKind.GENERIC, {'text': message});
+ if (whiteListed) {
+ _allowedListedErrors
+ .putIfAbsent(uri, () => <DiagnosticMessage>[])
+ .add(diagnosticMessage);
+ } else {
+ reporter.reportError(diagnosticMessage);
+ _errorCount++;
+ }
+ }
+}
diff --git a/tests/compiler/dart2js/analyze_test.dart b/tests/compiler/dart2js/analyses/analyze_test.dart
similarity index 100%
rename from tests/compiler/dart2js/analyze_test.dart
rename to tests/compiler/dart2js/analyses/analyze_test.dart
diff --git a/tests/compiler/dart2js/analyses/api_dynamic_test.dart b/tests/compiler/dart2js/analyses/api_dynamic_test.dart
new file mode 100644
index 0000000..0737162
--- /dev/null
+++ b/tests/compiler/dart2js/analyses/api_dynamic_test.dart
@@ -0,0 +1,73 @@
+// 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 'package:async_helper/async_helper.dart';
+import 'analysis_helper.dart';
+
+// TODO(johnniwinther): Remove unneeded dynamic accesses.
+const Map<String, List<String>> allowedList = {
+ 'sdk/lib/_http/crypto.dart': null,
+ 'sdk/lib/_http/http_date.dart': null,
+ 'sdk/lib/_http/http_headers.dart': null,
+ 'sdk/lib/_http/http_impl.dart': null,
+ 'sdk/lib/_http/http_parser.dart': null,
+ 'sdk/lib/_http/websocket_impl.dart': null,
+ 'sdk/lib/_internal/js_runtime/lib/async_patch.dart': null,
+ 'sdk/lib/_internal/js_runtime/lib/collection_patch.dart': null,
+ 'sdk/lib/_internal/js_runtime/lib/constant_map.dart': null,
+ 'sdk/lib/_internal/js_runtime/lib/convert_patch.dart': null,
+ 'sdk/lib/_internal/js_runtime/lib/core_patch.dart': null,
+ 'sdk/lib/_internal/js_runtime/lib/interceptors.dart': null,
+ 'sdk/lib/_internal/js_runtime/lib/js_helper.dart': null,
+ 'sdk/lib/_internal/js_runtime/lib/js_number.dart': null,
+ 'sdk/lib/_internal/js_runtime/lib/js_rti.dart': null,
+ 'sdk/lib/_internal/js_runtime/lib/linked_hash_map.dart': null,
+ 'sdk/lib/_internal/js_runtime/lib/native_helper.dart': null,
+ 'sdk/lib/_internal/js_runtime/lib/native_typed_data.dart': null,
+ 'sdk/lib/_internal/js_runtime/lib/regexp_helper.dart': null,
+ 'sdk/lib/_internal/js_runtime/lib/string_helper.dart': null,
+ 'sdk/lib/async/async_error.dart': null,
+ 'sdk/lib/async/future.dart': null,
+ 'sdk/lib/async/stream.dart': null,
+ 'sdk/lib/collection/hash_map.dart': null,
+ 'sdk/lib/collection/iterable.dart': null,
+ 'sdk/lib/collection/splay_tree.dart': null,
+ 'sdk/lib/convert/encoding.dart': null,
+ 'sdk/lib/convert/json.dart': null,
+ 'sdk/lib/convert/string_conversion.dart': null,
+ 'sdk/lib/core/date_time.dart': null,
+ 'sdk/lib/core/duration.dart': null,
+ 'sdk/lib/core/errors.dart': null,
+ 'sdk/lib/core/exceptions.dart': null,
+ 'sdk/lib/core/uri.dart': null,
+ 'sdk/lib/html/dart2js/html_dart2js.dart': null,
+ 'sdk/lib/html/html_common/conversions.dart': null,
+ 'sdk/lib/html/html_common/filtered_element_list.dart': null,
+ 'sdk/lib/html/html_common/lists.dart': null,
+ 'sdk/lib/indexed_db/dart2js/indexed_db_dart2js.dart': null,
+ 'sdk/lib/io/common.dart': null,
+ 'sdk/lib/io/directory_impl.dart': null,
+ 'sdk/lib/io/file_impl.dart': null,
+ 'sdk/lib/io/file_system_entity.dart': null,
+ 'sdk/lib/io/io_resource_info.dart': null,
+ 'sdk/lib/io/link.dart': null,
+ 'sdk/lib/io/platform_impl.dart': null,
+ 'sdk/lib/io/secure_server_socket.dart': null,
+ 'sdk/lib/io/secure_socket.dart': null,
+ 'sdk/lib/io/stdio.dart': null,
+ 'sdk/lib/isolate/isolate.dart': null,
+ 'sdk/lib/js/dart2js/js_dart2js.dart': null,
+ 'sdk/lib/math/point.dart': null,
+ 'sdk/lib/math/rectangle.dart': null,
+ 'sdk/lib/svg/dart2js/svg_dart2js.dart': null,
+};
+
+main(List<String> args) {
+ asyncTest(() async {
+ await run(Uri.parse('memory:main.dart'),
+ memorySourceFiles: {'main.dart': 'main() {}'},
+ allowedList: allowedList,
+ verbose: args.contains('-v'));
+ });
+}
diff --git a/tests/compiler/dart2js/analyses/dart2js_dynamic_test.dart b/tests/compiler/dart2js/analyses/dart2js_dynamic_test.dart
new file mode 100644
index 0000000..82964d8
--- /dev/null
+++ b/tests/compiler/dart2js/analyses/dart2js_dynamic_test.dart
@@ -0,0 +1,121 @@
+// 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 'package:async_helper/async_helper.dart';
+import 'analysis_helper.dart';
+import 'api_dynamic_test.dart' as api;
+
+// TODO(johnniwinther): Remove unneeded dynamic accesses.
+const Map<String, List<String>> allowedList = {
+ 'pkg/compiler/lib/src/closure.dart': null,
+ 'pkg/compiler/lib/src/common/tasks.dart': null,
+ 'pkg/compiler/lib/src/compiler.dart': null,
+ 'pkg/compiler/lib/src/constant_system_dart.dart': null,
+ 'pkg/compiler/lib/src/constants/constructors.dart': null,
+ 'pkg/compiler/lib/src/constants/expressions.dart': null,
+ 'pkg/compiler/lib/src/constants/values.dart': null,
+ 'pkg/compiler/lib/src/dart2js.dart': null,
+ 'pkg/compiler/lib/src/deferred_load.dart': null,
+ 'pkg/compiler/lib/src/diagnostics/messages.dart': null,
+ 'pkg/compiler/lib/src/diagnostics/source_span.dart': null,
+ 'pkg/compiler/lib/src/elements/entities.dart': null,
+ 'pkg/compiler/lib/src/elements/names.dart': null,
+ 'pkg/compiler/lib/src/elements/types.dart': null,
+ 'pkg/compiler/lib/src/hash/sha1.dart': null,
+ 'pkg/compiler/lib/src/helpers/debug_collection.dart': null,
+ 'pkg/compiler/lib/src/helpers/expensive_map.dart': null,
+ 'pkg/compiler/lib/src/helpers/expensive_set.dart': null,
+ 'pkg/compiler/lib/src/helpers/trace.dart': null,
+ 'pkg/compiler/lib/src/helpers/track_map.dart': null,
+ 'pkg/compiler/lib/src/inferrer/inferrer_engine.dart': null,
+ 'pkg/compiler/lib/src/inferrer/locals_handler.dart': null,
+ 'pkg/compiler/lib/src/inferrer/node_tracer.dart': null,
+ 'pkg/compiler/lib/src/inferrer/type_graph_dump.dart': null,
+ 'pkg/compiler/lib/src/inferrer/type_graph_nodes.dart': null,
+ 'pkg/compiler/lib/src/inferrer/typemasks/container_type_mask.dart': null,
+ 'pkg/compiler/lib/src/inferrer/typemasks/dictionary_type_mask.dart': null,
+ 'pkg/compiler/lib/src/inferrer/typemasks/forwarding_type_mask.dart': null,
+ 'pkg/compiler/lib/src/inferrer/typemasks/map_type_mask.dart': null,
+ 'pkg/compiler/lib/src/inferrer/typemasks/type_mask.dart': null,
+ 'pkg/compiler/lib/src/inferrer/typemasks/union_type_mask.dart': null,
+ 'pkg/compiler/lib/src/inferrer/typemasks/value_type_mask.dart': null,
+ 'pkg/compiler/lib/src/io/position_information.dart': null,
+ 'pkg/compiler/lib/src/io/source_information.dart': null,
+ 'pkg/compiler/lib/src/js/js.dart': null,
+ 'pkg/compiler/lib/src/js/rewrite_async.dart': null,
+ 'pkg/compiler/lib/src/js_backend/checked_mode_helpers.dart': null,
+ 'pkg/compiler/lib/src/js_backend/constant_system_javascript.dart': null,
+ 'pkg/compiler/lib/src/js_backend/namer_names.dart': null,
+ 'pkg/compiler/lib/src/js_backend/native_data.dart': null,
+ 'pkg/compiler/lib/src/js_emitter/full_emitter/emitter.dart': null,
+ 'pkg/compiler/lib/src/js_emitter/program_builder/program_builder.dart': null,
+ 'pkg/compiler/lib/src/js_emitter/startup_emitter/fragment_emitter.dart': null,
+ 'pkg/compiler/lib/src/js_model/closure.dart': null,
+ 'pkg/compiler/lib/src/js_model/js_strategy.dart': null,
+ 'pkg/compiler/lib/src/native/behavior.dart': null,
+ 'pkg/compiler/lib/src/native/enqueue.dart': null,
+ 'pkg/compiler/lib/src/native/js.dart': null,
+ 'pkg/compiler/lib/src/source_file_provider.dart': null,
+ 'pkg/compiler/lib/src/ssa/builder_kernel.dart': null,
+ 'pkg/compiler/lib/src/ssa/interceptor_simplifier.dart': null,
+ 'pkg/compiler/lib/src/ssa/nodes.dart': null,
+ 'pkg/compiler/lib/src/ssa/optimize.dart': null,
+ 'pkg/compiler/lib/src/ssa/types.dart': null,
+ 'pkg/compiler/lib/src/ssa/validate.dart': null,
+ 'pkg/compiler/lib/src/ssa/value_range_analyzer.dart': null,
+ 'pkg/compiler/lib/src/ssa/value_set.dart': null,
+ 'pkg/compiler/lib/src/ssa/variable_allocator.dart': null,
+ 'pkg/compiler/lib/src/universe/feature.dart': null,
+ 'pkg/compiler/lib/src/universe/function_set.dart': null,
+ 'pkg/compiler/lib/src/universe/member_usage.dart': null,
+ 'pkg/compiler/lib/src/universe/resolution_world_builder.dart': null,
+ 'pkg/compiler/lib/src/universe/side_effects.dart': null,
+ 'pkg/compiler/lib/src/universe/use.dart': null,
+ 'pkg/compiler/lib/src/universe/world_builder.dart': null,
+ 'pkg/compiler/lib/src/util/enumset.dart': null,
+ 'pkg/compiler/lib/src/util/maplet.dart': null,
+ 'pkg/compiler/lib/src/util/setlet.dart': null,
+ 'pkg/compiler/lib/src/util/util.dart': null,
+ 'pkg/front_end/lib/src/base/libraries_specification.dart': null,
+ 'pkg/front_end/lib/src/fasta/builder/function_type_builder.dart': null,
+ 'pkg/front_end/lib/src/fasta/colors.dart': null,
+ 'pkg/front_end/lib/src/fasta/crash.dart': null,
+ 'pkg/front_end/lib/src/fasta/dill/dill_member_builder.dart': null,
+ 'pkg/front_end/lib/src/fasta/kernel/body_builder.dart': null,
+ 'pkg/front_end/lib/src/fasta/kernel/expression_generator.dart': null,
+ 'pkg/front_end/lib/src/fasta/kernel/kernel_class_builder.dart': null,
+ 'pkg/front_end/lib/src/fasta/kernel/kernel_function_type_alias_builder.dart':
+ null,
+ 'pkg/front_end/lib/src/fasta/kernel/kernel_function_type_builder.dart': null,
+ 'pkg/front_end/lib/src/fasta/kernel/kernel_library_builder.dart': null,
+ 'pkg/front_end/lib/src/fasta/kernel/kernel_procedure_builder.dart': null,
+ 'pkg/front_end/lib/src/fasta/scanner/string_canonicalizer.dart': null,
+ 'pkg/front_end/lib/src/fasta/scanner/token.dart': null,
+ 'pkg/front_end/lib/src/fasta/source/diet_listener.dart': null,
+ 'pkg/front_end/lib/src/fasta/source/outline_builder.dart': null,
+ 'pkg/front_end/lib/src/fasta/util/link.dart': null,
+ 'pkg/front_end/lib/src/fasta/util/link_implementation.dart': null,
+ 'pkg/js_ast/lib/src/builder.dart': null,
+ 'pkg/js_ast/lib/src/template.dart': null,
+ 'pkg/kernel/lib/ast.dart': null,
+ 'pkg/kernel/lib/clone.dart': null,
+ 'pkg/kernel/lib/import_table.dart': null,
+ 'pkg/kernel/lib/kernel.dart': null,
+ 'pkg/kernel/lib/text/ast_to_text.dart': null,
+ 'third_party/pkg/dart2js_info/lib/json_info_codec.dart': null,
+ 'third_party/pkg/dart2js_info/lib/src/measurements.dart': null,
+ 'third_party/pkg/dart2js_info/lib/src/util.dart': null,
+ 'third_party/pkg/source_span/lib/src/file.dart': null,
+ 'third_party/pkg/source_span/lib/src/span_mixin.dart': null,
+};
+
+main(List<String> args) {
+ asyncTest(() async {
+ Map<String, List<String>> allowed = {};
+ allowed.addAll(api.allowedList);
+ allowed.addAll(allowedList);
+ await run(Uri.parse('package:compiler/src/dart2js.dart'),
+ allowedList: allowed, verbose: args.contains('-v'));
+ });
+}
diff --git a/tests/compiler/dart2js/dart2js.status b/tests/compiler/dart2js/dart2js.status
index eb4f134..b39dc5f4f 100644
--- a/tests/compiler/dart2js/dart2js.status
+++ b/tests/compiler/dart2js/dart2js.status
@@ -2,7 +2,7 @@
# 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.
-analyze_test: Slow, Pass
+analyses/analyze_test: Slow, Pass
closure/closure_test: Pass, Slow
codegen/gvn_dynamic_field_get_test: Fail # Issue 18519
codegen/list_tracer_length_test: Fail # Issue 33051