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