Support noSuchMethod forwarders in dart2js

Closes #32750

Change-Id: Ib2f358b14ae5a079e3df8413013ecfc006f0e69b
Reviewed-on: https://dart-review.googlesource.com/60250
Commit-Queue: Johnni Winther <johnniwinther@google.com>
Reviewed-by: Sigmund Cherem <sigmund@google.com>
diff --git a/pkg/compiler/lib/src/common_elements.dart b/pkg/compiler/lib/src/common_elements.dart
index 738bd388..c54b666 100644
--- a/pkg/compiler/lib/src/common_elements.dart
+++ b/pkg/compiler/lib/src/common_elements.dart
@@ -1091,6 +1091,15 @@
   FunctionEntity get createInvocationMirror =>
       _findHelperFunction('createInvocationMirror');
 
+  bool isCreateInvocationMirrorHelper(MemberEntity member) {
+    return member.isTopLevel &&
+        member.name == '_createInvocationMirror' &&
+        member.library == coreLibrary;
+  }
+
+  FunctionEntity get createUnmangledInvocationMirror =>
+      _findHelperFunction('createUnmangledInvocationMirror');
+
   FunctionEntity get cyclicThrowHelper =>
       _findHelperFunction("throwCyclicInit");
 
diff --git a/pkg/compiler/lib/src/js_backend/backend.dart b/pkg/compiler/lib/src/js_backend/backend.dart
index 6b6934a..a8164c0 100644
--- a/pkg/compiler/lib/src/js_backend/backend.dart
+++ b/pkg/compiler/lib/src/js_backend/backend.dart
@@ -409,7 +409,8 @@
             compiler.backendStrategy.sourceInformationStrategy,
         constantCompilerTask = new JavaScriptConstantTask(compiler) {
     CommonElements commonElements = compiler.frontendStrategy.commonElements;
-    _backendUsageBuilder = new BackendUsageBuilderImpl(commonElements);
+    _backendUsageBuilder =
+        new BackendUsageBuilderImpl(compiler.frontendStrategy);
     _checkedModeHelpers = new CheckedModeHelpers();
     emitter =
         new CodeEmitterTask(compiler, generateSourceMap, useStartupEmitter);
diff --git a/pkg/compiler/lib/src/js_backend/backend_impact.dart b/pkg/compiler/lib/src/js_backend/backend_impact.dart
index 90bc21f..9adf701 100644
--- a/pkg/compiler/lib/src/js_backend/backend_impact.dart
+++ b/pkg/compiler/lib/src/js_backend/backend_impact.dart
@@ -720,9 +720,12 @@
   BackendImpact _noSuchMethodSupport;
 
   BackendImpact get noSuchMethodSupport {
-    return _noSuchMethodSupport ??= new BackendImpact(
-        staticUses: [_commonElements.createInvocationMirror],
-        dynamicUses: [Selectors.noSuchMethod_]);
+    return _noSuchMethodSupport ??= new BackendImpact(staticUses: [
+      _commonElements.createInvocationMirror,
+      _commonElements.createUnmangledInvocationMirror
+    ], dynamicUses: [
+      Selectors.noSuchMethod_
+    ]);
   }
 
   BackendImpact _loadLibrary;
diff --git a/pkg/compiler/lib/src/js_backend/backend_usage.dart b/pkg/compiler/lib/src/js_backend/backend_usage.dart
index cce6611..3b7503c 100644
--- a/pkg/compiler/lib/src/js_backend/backend_usage.dart
+++ b/pkg/compiler/lib/src/js_backend/backend_usage.dart
@@ -6,6 +6,7 @@
 import '../common_elements.dart';
 import '../elements/entities.dart';
 import '../elements/types.dart';
+import '../frontend_strategy.dart';
 import '../universe/feature.dart';
 import '../util/util.dart' show Setlet;
 import 'backend_impact.dart';
@@ -87,7 +88,7 @@
 }
 
 class BackendUsageBuilderImpl implements BackendUsageBuilder {
-  final CommonElements _commonElements;
+  FrontendStrategy _frontendStrategy;
   // TODO(johnniwinther): Remove the need for these.
   Setlet<FunctionEntity> _globalFunctionDependencies;
   Setlet<ClassEntity> _globalClassDependencies;
@@ -118,43 +119,34 @@
   /// `true` if `noSuchMethod` is used.
   bool isNoSuchMethodUsed = false;
 
-  BackendUsageBuilderImpl(this._commonElements);
+  BackendUsageBuilderImpl(this._frontendStrategy);
+
+  CommonElements get _commonElements => _frontendStrategy.commonElements;
 
   @override
   void registerBackendFunctionUse(FunctionEntity element) {
-    assert(_isValidBackendUse(element),
+    assert(_isValidBackendUse(element, element.library),
         failedAt(element, "Backend use of $element is not allowed."));
     _helperFunctionsUsed.add(element);
   }
 
   @override
   void registerBackendClassUse(ClassEntity element) {
-    assert(_isValidBackendUse(element),
+    assert(_isValidBackendUse(element, element.library),
         failedAt(element, "Backend use of $element is not allowed."));
     _helperClassesUsed.add(element);
   }
 
-  bool _isValidBackendUse(Entity element) {
+  bool _isValidBackendUse(Entity element, LibraryEntity library) {
     if (_isValidEntity(element)) return true;
-    // TODO(redemption): Support these checks on kernel based elements:
-    /*if (element is Element) {
-      assert(element.isDeclaration,
-          failedAt(element, "Backend use $element must be the declaration."));
-      if (element.implementationLibrary.isPatch ||
-          // Needed to detect deserialized injected elements, that is
-          // element declared in patch files.
-          (element.library.isPlatformLibrary &&
-              element.sourcePosition.uri.path
-                  .contains('_internal/js_runtime/lib/')) ||
-          element.library == _commonElements.jsHelperLibrary ||
-          element.library == _commonElements.interceptorsLibrary) {
-        // TODO(johnniwinther): We should be more precise about these.
-        return true;
-      } else {
-        return false;
-      }
-    }*/
-    return true;
+    SourceSpan span = _frontendStrategy.spanFromSpannable(element, element);
+    if (library.canonicalUri.scheme == 'dart' &&
+        span.uri.path.contains('_internal/js_runtime/lib/')) {
+      // TODO(johnniwinther): We should be more precise about these.
+      return true;
+    } else {
+      return false;
+    }
   }
 
   bool _isValidEntity(Entity element) {
diff --git a/pkg/compiler/lib/src/js_backend/resolution_listener.dart b/pkg/compiler/lib/src/js_backend/resolution_listener.dart
index 131da5b..c1b2cac 100644
--- a/pkg/compiler/lib/src/js_backend/resolution_listener.dart
+++ b/pkg/compiler/lib/src/js_backend/resolution_listener.dart
@@ -285,6 +285,10 @@
     }
     _backendUsage.registerUsedMember(member);
 
+    if (_commonElements.isCreateInvocationMirrorHelper(member)) {
+      _registerBackendImpact(worldImpact, _impacts.noSuchMethodSupport);
+    }
+
     if (_elementEnvironment.isDeferredLoadLibraryGetter(member)) {
       // TODO(sigurdm): Create a function registerLoadLibraryAccess.
       if (!_isLoadLibraryFunctionResolved) {
diff --git a/pkg/compiler/lib/src/kernel/dart2js_target.dart b/pkg/compiler/lib/src/kernel/dart2js_target.dart
index 462ba79..71a8c15 100644
--- a/pkg/compiler/lib/src/kernel/dart2js_target.dart
+++ b/pkg/compiler/lib/src/kernel/dart2js_target.dart
@@ -4,12 +4,13 @@
 
 library compiler.src.kernel.dart2js_target;
 
-import 'package:kernel/kernel.dart';
+import 'package:kernel/ast.dart' as ir;
 import 'package:kernel/core_types.dart';
 import 'package:kernel/class_hierarchy.dart';
 import 'package:kernel/target/targets.dart';
 
 import '../native/native.dart' show maybeEnableNative;
+import '../universe/selector.dart';
 
 /// A kernel [Target] to configure the Dart Front End for dart2js.
 class Dart2jsTarget extends Target {
@@ -20,6 +21,8 @@
 
   bool get strongMode => flags.strongMode;
 
+  bool get enableNoSuchMethodForwarders => flags.strongMode;
+
   List<String> get extraRequiredLibraries => _requiredLibraries[name];
 
   @override
@@ -40,23 +43,56 @@
 
   @override
   void performModularTransformationsOnLibraries(
-      CoreTypes coreTypes, ClassHierarchy hierarchy, List<Library> libraries,
+      CoreTypes coreTypes, ClassHierarchy hierarchy, List<ir.Library> libraries,
       {void logger(String msg)}) {}
 
   @override
-  void performGlobalTransformations(CoreTypes coreTypes, Component component,
+  void performGlobalTransformations(CoreTypes coreTypes, ir.Component component,
       {void logger(String msg)}) {}
 
   @override
-  Expression instantiateInvocation(CoreTypes coreTypes, Expression receiver,
-      String name, Arguments arguments, int offset, bool isSuper) {
-    // TODO(sigmund): implement;
-    return new InvalidExpression(null);
+  ir.Expression instantiateInvocation(
+      CoreTypes coreTypes,
+      ir.Expression receiver,
+      String name,
+      ir.Arguments arguments,
+      int offset,
+      bool isSuper) {
+    int kind;
+    if (name.startsWith('get:')) {
+      kind = Selector.invocationMirrorGetterKind;
+      name = name.substring(4);
+    } else if (name.startsWith('set:')) {
+      kind = Selector.invocationMirrorSetterKind;
+      name = name.substring(4);
+    } else {
+      kind = Selector.invocationMirrorMethodKind;
+    }
+    return new ir.StaticInvocation(
+        coreTypes.index
+            .getTopLevelMember('dart:core', '_createInvocationMirror'),
+        new ir.Arguments(<ir.Expression>[
+          new ir.StringLiteral(name)..fileOffset = offset,
+          new ir.ListLiteral(
+              arguments.types.map((t) => new ir.TypeLiteral(t)).toList()),
+          new ir.ListLiteral(arguments.positional)..fileOffset = offset,
+          new ir.MapLiteral(new List<ir.MapEntry>.from(
+              arguments.named.map((ir.NamedExpression arg) {
+            return new ir.MapEntry(
+                new ir.StringLiteral(arg.name)..fileOffset = arg.fileOffset,
+                arg.value)
+              ..fileOffset = arg.fileOffset;
+          })), keyType: coreTypes.stringClass.rawType)
+            ..isConst = (arguments.named.length == 0)
+            ..fileOffset = arguments.fileOffset,
+          new ir.IntLiteral(kind)..fileOffset = offset,
+        ]))
+      ..fileOffset = offset;
   }
 
   @override
-  Expression instantiateNoSuchMethodError(CoreTypes coreTypes,
-      Expression receiver, String name, Arguments arguments, int offset,
+  ir.Expression instantiateNoSuchMethodError(CoreTypes coreTypes,
+      ir.Expression receiver, String name, ir.Arguments arguments, int offset,
       {bool isMethod: false,
       bool isGetter: false,
       bool isSetter: false,
@@ -68,7 +104,7 @@
       bool isConstructor: false,
       bool isTopLevel: false}) {
     // TODO(sigmund): implement;
-    return new InvalidExpression(null);
+    return new ir.InvalidExpression(null);
   }
 }
 
diff --git a/pkg/compiler/lib/src/kernel/element_map.dart b/pkg/compiler/lib/src/kernel/element_map.dart
index 99f481e..7be52d1 100644
--- a/pkg/compiler/lib/src/kernel/element_map.dart
+++ b/pkg/compiler/lib/src/kernel/element_map.dart
@@ -93,8 +93,11 @@
   /// Returns the [Name] corresponding to [name].
   Name getName(ir.Name name);
 
-  /// Return `true` if [node] is the `dart:_foreign_helper` library.
-  bool isForeignLibrary(ir.Library node);
+  /// Return `true` if [member] is a "foreign helper", that is, a member whose
+  /// semantics is defined synthetically and not through Dart code.
+  ///
+  /// Most foreign helpers are located in the `dart:_foreign_helper` library.
+  bool isForeignHelper(MemberEntity member);
 
   /// Computes the [native.NativeBehavior] for a call to the [JS] function.
   native.NativeBehavior getNativeBehaviorForJsCall(ir.StaticInvocation node);
diff --git a/pkg/compiler/lib/src/kernel/element_map_mixins.dart b/pkg/compiler/lib/src/kernel/element_map_mixins.dart
index d8c74cc..88c8a8c 100644
--- a/pkg/compiler/lib/src/kernel/element_map_mixins.dart
+++ b/pkg/compiler/lib/src/kernel/element_map_mixins.dart
@@ -104,9 +104,13 @@
     return new Selector.setter(name);
   }
 
-  /// Return `true` if [node] is the `dart:_foreign_helper` library.
-  bool isForeignLibrary(ir.Library node) {
-    return node.importUri == Uris.dart__foreign_helper;
+  /// Return `true` if [member] is a "foreign helper", that is, a member whose
+  /// semantics is defined synthetically and not through Dart code.
+  ///
+  /// Most foreign helpers are located in the `dart:_foreign_helper` library.
+  bool isForeignHelper(MemberEntity member) {
+    return member.library == commonElements.foreignLibrary ||
+        commonElements.isCreateInvocationMirrorHelper(member);
   }
 
   /// Looks up [typeName] for use in the spec-string of a `JS` call.
@@ -387,7 +391,7 @@
 
   /// Compute the kind of foreign helper function called by [node], if any.
   ForeignKind getForeignKind(ir.StaticInvocation node) {
-    if (isForeignLibrary(node.target.enclosingLibrary)) {
+    if (isForeignHelper(getMember(node.target))) {
       switch (node.target.name.name) {
         case JavaScriptBackend.JS:
           return ForeignKind.JS;
diff --git a/pkg/compiler/lib/src/kernel/env.dart b/pkg/compiler/lib/src/kernel/env.dart
index dcef78f..ec0511b 100644
--- a/pkg/compiler/lib/src/kernel/env.dart
+++ b/pkg/compiler/lib/src/kernel/env.dart
@@ -373,7 +373,8 @@
       }
     }
 
-    void addProcedures(ir.Class c, {bool includeStatic}) {
+    void addProcedures(ir.Class c,
+        {bool includeStatic, bool includeNoSuchMethodForwarders}) {
       for (ir.Procedure member in c.procedures) {
         if (member.isForwardingStub && member.isAbstract) {
           // Skip abstract forwarding stubs. These are never emitted but they
@@ -391,6 +392,9 @@
           continue;
         }
         if (!includeStatic && member.isStatic) continue;
+        if (!includeNoSuchMethodForwarders && member.isNoSuchMethodForwarder) {
+          continue;
+        }
         var name = member.name.name;
         assert(!name.contains('#'));
         if (member.kind == ir.ProcedureKind.Factory) {
@@ -424,13 +428,15 @@
     if (cls.mixedInClass != null) {
       elementMap.ensureClassMembers(cls.mixedInClass);
       addFields(cls.mixedInClass.mixin, includeStatic: false);
-      addProcedures(cls.mixedInClass.mixin, includeStatic: false);
+      addProcedures(cls.mixedInClass.mixin,
+          includeStatic: false, includeNoSuchMethodForwarders: false);
       mergeSort(members, compare: orderByFileOffset);
       mixinMemberCount = members.length;
     }
     addFields(cls, includeStatic: true);
     addConstructors(cls);
-    addProcedures(cls, includeStatic: true);
+    addProcedures(cls,
+        includeStatic: true, includeNoSuchMethodForwarders: true);
 
     if (isUnnamedMixinApplication && _constructorMap.isEmpty) {
       // Ensure that constructors are created for the superclass in case it
diff --git a/pkg/compiler/lib/src/ssa/builder_kernel.dart b/pkg/compiler/lib/src/ssa/builder_kernel.dart
index 2ad67c8..fb04063 100644
--- a/pkg/compiler/lib/src/ssa/builder_kernel.dart
+++ b/pkg/compiler/lib/src/ssa/builder_kernel.dart
@@ -19,6 +19,7 @@
 import '../dump_info.dart';
 import '../elements/entities.dart';
 import '../elements/jumps.dart';
+import '../elements/names.dart';
 import '../elements/types.dart';
 import '../io/source_information.dart';
 import '../js/js.dart' as js;
@@ -3280,11 +3281,11 @@
     ir.Procedure target = node.target;
     SourceInformation sourceInformation =
         _sourceInformationBuilder.buildCall(node, node);
-    if (_elementMap.isForeignLibrary(target.enclosingLibrary)) {
-      handleInvokeStaticForeign(node, target);
+    FunctionEntity function = _elementMap.getMember(target);
+    if (_elementMap.isForeignHelper(function)) {
+      handleInvokeStaticForeign(node, function);
       return;
     }
-    FunctionEntity function = _elementMap.getMember(target);
 
     if (options.strongMode &&
         function == _commonElements.extractTypeArguments &&
@@ -3558,8 +3559,8 @@
   }
 
   void handleInvokeStaticForeign(
-      ir.StaticInvocation invocation, ir.Procedure target) {
-    String name = target.name.name;
+      ir.StaticInvocation invocation, MemberEntity member) {
+    String name = member.name;
     if (name == 'JS') {
       handleForeignJs(invocation);
     } else if (name == 'DART_CLOSURE_TO_JS') {
@@ -3584,6 +3585,8 @@
       handleJsInterceptorConstant(invocation);
     } else if (name == 'JS_STRING_CONCAT') {
       handleJsStringConcat(invocation);
+    } else if (name == '_createInvocationMirror') {
+      _handleCreateInvocationMirror(invocation);
     } else {
       reporter.internalError(
           _elementMap.getSpannable(targetElement, invocation),
@@ -3591,6 +3594,114 @@
     }
   }
 
+  void _handleCreateInvocationMirror(ir.StaticInvocation invocation) {
+    ir.StringLiteral nameLiteral = invocation.arguments.positional[0];
+    String name = nameLiteral.value;
+
+    ir.ListLiteral typeArgumentsLiteral = invocation.arguments.positional[1];
+    List<DartType> typeArguments =
+        typeArgumentsLiteral.expressions.map((ir.Expression expression) {
+      ir.TypeLiteral typeLiteral = expression;
+      return _elementMap.getDartType(typeLiteral.type);
+    }).toList();
+
+    ir.ListLiteral positionalArgumentsLiteral =
+        invocation.arguments.positional[2];
+    ir.MapLiteral namedArgumentsLiteral = invocation.arguments.positional[3];
+    ir.IntLiteral kindLiteral = invocation.arguments.positional[4];
+
+    Name memberName = new Name(name, _currentFrame.member.library);
+    Selector selector;
+    switch (kindLiteral.value) {
+      case Selector.invocationMirrorGetterKind:
+        selector = new Selector.getter(memberName);
+        break;
+      case Selector.invocationMirrorSetterKind:
+        selector = new Selector.setter(memberName);
+        break;
+      case Selector.invocationMirrorMethodKind:
+        if (memberName == Names.INDEX_NAME) {
+          selector = new Selector.index();
+        } else if (memberName == Names.INDEX_SET_NAME) {
+          selector = new Selector.indexSet();
+        } else {
+          CallStructure callStructure = new CallStructure(
+              positionalArgumentsLiteral.expressions.length,
+              namedArgumentsLiteral.entries.map<String>((ir.MapEntry entry) {
+                ir.StringLiteral key = entry.key;
+                return key.value;
+              }).toList(),
+              typeArguments.length);
+          if (Selector.isOperatorName(name)) {
+            selector =
+                new Selector(SelectorKind.OPERATOR, memberName, callStructure);
+          } else {
+            selector = new Selector.call(memberName, callStructure);
+          }
+        }
+        break;
+    }
+
+    HConstant nameConstant = graph.addConstant(
+        closedWorld.constantSystem
+            .createSymbol(closedWorld.commonElements, name),
+        closedWorld);
+
+    List<HInstruction> arguments = <HInstruction>[];
+    for (ir.Expression argument in positionalArgumentsLiteral.expressions) {
+      argument.accept(this);
+      arguments.add(pop());
+    }
+    if (namedArgumentsLiteral.entries.isNotEmpty) {
+      Map<String, HInstruction> namedValues = <String, HInstruction>{};
+      for (ir.MapEntry entry in namedArgumentsLiteral.entries) {
+        ir.StringLiteral key = entry.key;
+        String name = key.value;
+        entry.value.accept(this);
+        namedValues[name] = pop();
+      }
+      for (String name in selector.callStructure.getOrderedNamedArguments()) {
+        arguments.add(namedValues[name]);
+      }
+    }
+
+    _addTypeArguments(arguments, typeArguments,
+        _sourceInformationBuilder.buildCall(invocation, invocation));
+
+    HInstruction argumentsInstruction = buildLiteralList(arguments);
+    add(argumentsInstruction);
+
+    List<HInstruction> argumentNames = <HInstruction>[];
+    for (String argumentName in selector.namedArguments) {
+      ConstantValue argumentNameConstant =
+          constantSystem.createString(argumentName);
+      argumentNames.add(graph.addConstant(argumentNameConstant, closedWorld));
+    }
+    HInstruction argumentNamesInstruction = buildLiteralList(argumentNames);
+    add(argumentNamesInstruction);
+
+    HInstruction typeArgumentCount =
+        graph.addConstantInt(typeArguments.length, closedWorld);
+
+    js.Name internalName = namer.invocationName(selector);
+
+    ConstantValue kindConstant =
+        constantSystem.createIntFromInt(selector.invocationMirrorKind);
+
+    _pushStaticInvocation(
+        _commonElements.createUnmangledInvocationMirror,
+        [
+          nameConstant,
+          graph.addConstantStringFromName(internalName, closedWorld),
+          graph.addConstant(kindConstant, closedWorld),
+          argumentsInstruction,
+          argumentNamesInstruction,
+          typeArgumentCount,
+        ],
+        abstractValueDomain.dynamicType,
+        const <DartType>[]);
+  }
+
   bool _unexpectedForeignArguments(ir.StaticInvocation invocation,
       {int minPositional, int maxPositional, int typeArgumentCount = 0}) {
     String pluralizeArguments(int count, [String adjective = '']) {
diff --git a/pkg/compiler/lib/src/ssa/kernel_impact.dart b/pkg/compiler/lib/src/ssa/kernel_impact.dart
index 8e62ae0..b5cd0b2 100644
--- a/pkg/compiler/lib/src/ssa/kernel_impact.dart
+++ b/pkg/compiler/lib/src/ssa/kernel_impact.dart
@@ -142,9 +142,8 @@
     handleSignature(constructor.function, checkReturnType: false);
     visitNodes(constructor.initializers);
     visitNode(constructor.function.body);
-    if (constructor.isExternal &&
-        !elementMap.isForeignLibrary(constructor.enclosingLibrary)) {
-      MemberEntity member = elementMap.getMember(constructor);
+    MemberEntity member = elementMap.getMember(constructor);
+    if (constructor.isExternal && !elementMap.isForeignHelper(member)) {
       bool isJsInterop = elementMap.nativeBasicData.isJsInteropMember(member);
       impactBuilder.registerNativeData(elementMap
           .getNativeBehaviorForMethod(constructor, isJsInterop: isJsInterop));
@@ -198,9 +197,8 @@
     handleSignature(procedure.function);
     visitNode(procedure.function.body);
     handleAsyncMarker(procedure.function);
-    if (procedure.isExternal &&
-        !elementMap.isForeignLibrary(procedure.enclosingLibrary)) {
-      MemberEntity member = elementMap.getMember(procedure);
+    MemberEntity member = elementMap.getMember(procedure);
+    if (procedure.isExternal && !elementMap.isForeignHelper(member)) {
       bool isJsInterop = elementMap.nativeBasicData.isJsInteropMember(member);
       impactBuilder.registerNativeData(elementMap
           .getNativeBehaviorForMethod(procedure, isJsInterop: isJsInterop));
diff --git a/pkg/compiler/lib/src/ssa/nodes.dart b/pkg/compiler/lib/src/ssa/nodes.dart
index 2258231..4451c74 100644
--- a/pkg/compiler/lib/src/ssa/nodes.dart
+++ b/pkg/compiler/lib/src/ssa/nodes.dart
@@ -978,7 +978,7 @@
   HInstruction(this.inputs, this.instructionType)
       : id = idCounter++,
         usedBy = <HInstruction>[] {
-    assert(inputs.every((e) => e != null));
+    assert(inputs.every((e) => e != null), "inputs: $inputs");
   }
 
   int get hashCode => id;
diff --git a/pkg/compiler/lib/src/universe/selector.dart b/pkg/compiler/lib/src/universe/selector.dart
index d4af193..34aaf06 100644
--- a/pkg/compiler/lib/src/universe/selector.dart
+++ b/pkg/compiler/lib/src/universe/selector.dart
@@ -208,15 +208,16 @@
    */
   String get invocationMirrorMemberName => isSetter ? '$name=' : name;
 
+  static const int invocationMirrorMethodKind = 0;
+  static const int invocationMirrorGetterKind = 1;
+  static const int invocationMirrorSetterKind = 2;
+
   int get invocationMirrorKind {
-    const int METHOD = 0;
-    const int GETTER = 1;
-    const int SETTER = 2;
-    int kind = METHOD;
+    int kind = invocationMirrorMethodKind;
     if (isGetter) {
-      kind = GETTER;
+      kind = invocationMirrorGetterKind;
     } else if (isSetter) {
-      kind = SETTER;
+      kind = invocationMirrorSetterKind;
     }
     return kind;
   }
diff --git a/sdk/lib/_internal/js_runtime/lib/core_patch.dart b/sdk/lib/_internal/js_runtime/lib/core_patch.dart
index e590c9c..e27baf5 100644
--- a/sdk/lib/_internal/js_runtime/lib/core_patch.dart
+++ b/sdk/lib/_internal/js_runtime/lib/core_patch.dart
@@ -2947,3 +2947,14 @@
     return _reduce(resultDigits, resultUsed);
   }
 }
+
+/// Creates an invocation object used in noSuchMethod forwarding stubs.
+///
+/// The signature is hardwired to the kernel nodes generated in the
+/// `Dart2jsTarget` and read in the `KernelSsaGraphBuilder`.
+external Invocation _createInvocationMirror(
+    String memberName,
+    List typeArguments,
+    List positionalArguments,
+    Map<String, dynamic> namedArguments,
+    int kind);
diff --git a/sdk/lib/_internal/js_runtime/lib/js_helper.dart b/sdk/lib/_internal/js_runtime/lib/js_helper.dart
index fd9e485..9e2f105 100644
--- a/sdk/lib/_internal/js_runtime/lib/js_helper.dart
+++ b/sdk/lib/_internal/js_runtime/lib/js_helper.dart
@@ -251,9 +251,9 @@
 }
 
 createUnmangledInvocationMirror(
-    Symbol symbol, internalName, kind, arguments, argumentNames) {
+    Symbol symbol, internalName, kind, arguments, argumentNames, types) {
   return new JSInvocationMirror(
-      symbol, internalName, kind, arguments, argumentNames, 0);
+      symbol, internalName, kind, arguments, argumentNames, types);
 }
 
 void throwInvalidReflectionError(String memberName) {
@@ -1258,8 +1258,13 @@
     String selectorName =
         '${JS_GET_NAME(JsGetName.CALL_PREFIX)}\$$argumentCount$names';
 
-    return function.noSuchMethod(createUnmangledInvocationMirror(#call,
-        selectorName, JSInvocationMirror.METHOD, arguments, namedArgumentList));
+    return function.noSuchMethod(createUnmangledInvocationMirror(
+        #call,
+        selectorName,
+        JSInvocationMirror.METHOD,
+        arguments,
+        namedArgumentList,
+        0));
   }
 
   /**
diff --git a/tests/compiler/dart2js/helpers/element_lookup.dart b/tests/compiler/dart2js/helpers/element_lookup.dart
index 6d24abe..4823c94 100644
--- a/tests/compiler/dart2js/helpers/element_lookup.dart
+++ b/tests/compiler/dart2js/helpers/element_lookup.dart
@@ -22,12 +22,14 @@
 }
 
 MemberEntity findClassMember(
-    JClosedWorld closedWorld, String className, String memberName) {
+    JClosedWorld closedWorld, String className, String memberName,
+    {bool required: true}) {
   ElementEnvironment elementEnvironment = closedWorld.elementEnvironment;
   ClassEntity cls = findClass(closedWorld, className);
   assert(cls != null, "Class '$className' not found.");
   MemberEntity member = elementEnvironment.lookupClassMember(cls, memberName);
-  assert(member != null, "Member '$memberName' not found in $cls.");
+  assert(
+      !required || member != null, "Member '$memberName' not found in $cls.");
   return member;
 }
 
diff --git a/tests/compiler/dart2js/inference/data/no_such_method1.dart b/tests/compiler/dart2js/inference/data/no_such_method1.dart
index b4a1774..146ea37 100644
--- a/tests/compiler/dart2js/inference/data/no_such_method1.dart
+++ b/tests/compiler/dart2js/inference/data/no_such_method1.dart
@@ -10,7 +10,8 @@
 
 /*element: B.:[exact=B]*/
 class B extends A {
-  foo();
+  /*strong.element: B.foo:[exact=JSUInt31]*/
+  /*strong.invoke: [subclass=B]*/ foo();
 }
 
 /*element: C.:[exact=C]*/
diff --git a/tests/compiler/dart2js/model/no_such_method_forwarders_test.dart b/tests/compiler/dart2js/model/no_such_method_forwarders_test.dart
new file mode 100644
index 0000000..7519cdd
--- /dev/null
+++ b/tests/compiler/dart2js/model/no_such_method_forwarders_test.dart
@@ -0,0 +1,180 @@
+// 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/commandline_options.dart';
+import 'package:compiler/src/compiler.dart';
+import 'package:compiler/src/elements/entities.dart';
+import 'package:compiler/src/elements/types.dart';
+import 'package:compiler/src/world.dart';
+import 'package:expect/expect.dart';
+import '../helpers/element_lookup.dart';
+import '../memory_compiler.dart';
+
+const String source = '''
+abstract class I<T> {
+  T method();
+}
+
+class A<T> implements I<T> {
+  noSuchMethod(_) => null;
+}
+
+class B<T> extends I<T> {
+  noSuchMethod(_) => null;
+}
+
+abstract class C1<T> implements I<T> {
+}
+
+class C2<T> extends C1<T> {
+  noSuchMethod(_) => null;
+}
+
+abstract class D1<T> implements I<T> {
+}
+
+abstract class D2<T> extends D1<T> {
+  noSuchMethod(_) => null;
+}
+
+class D3<T> extends D2<T> {
+}
+
+class E1<T> {
+  T method() => null;
+}
+
+abstract class E2<T> implements I<T> {
+  noSuchMethod(_) => null;
+}
+
+class E3<T> extends E1<T> with E2<T> {
+}
+
+class F1<T> {
+  T method() => null;
+}
+
+class F2<T> implements I<T> {
+  noSuchMethod(_) => null;
+}
+
+class F3<T> extends F1<T> with F2<T> {
+}
+
+abstract class G1<T> {
+  T method();
+}
+
+abstract class G2<T> implements I<T> {
+  noSuchMethod(_) => null;
+}
+
+class G3<T> extends G1<T> with G2<T> {
+}
+
+abstract class H1<T> {
+  T method();
+}
+
+abstract class H2<T> implements I<T> {
+  noSuchMethod(_) => null;
+}
+
+class H3<T> extends H1<T> with H2<T> {
+  method() => null;
+}
+
+main() {
+  new A();
+  new B();
+  new C2();
+  new D3();
+  new E1();
+  new E3();
+  new F1();
+  new F2();
+  new F3();
+  new G3();
+  new H3();
+}
+''';
+
+main() {
+  asyncTest(() async {
+    CompilationResult result = await runCompiler(
+        memorySourceFiles: {'main.dart': source}, options: [Flags.strongMode]);
+    Expect.isTrue(result.isSuccess);
+    Compiler compiler = result.compiler;
+    JClosedWorld closedWorld = compiler.backendClosedWorldForTesting;
+
+    void check(String className,
+        {bool hasMethod, bool isAbstract: false, String declaringClass}) {
+      MemberEntity member =
+          findClassMember(closedWorld, className, 'method', required: false);
+      if (hasMethod) {
+        Expect.isNotNull(
+            member, "Missing member 'method' in class '$className'.");
+        Expect.equals(isAbstract, member.isAbstract,
+            "Unexpected abstract-ness on method $member.");
+        ClassEntity cls = findClass(closedWorld, declaringClass);
+        if (cls != member.enclosingClass) {
+          print("Unexpected declaring class $member. "
+              "Found ${member.enclosingClass}, expected $cls.");
+        }
+        Expect.equals(
+            cls,
+            member.enclosingClass,
+            "Unexpected declaring class $member. "
+            "Found ${member.enclosingClass}, expected $cls.");
+        DartType type;
+        if (member.isFunction) {
+          type =
+              closedWorld.elementEnvironment.getFunctionType(member).returnType;
+        } else if (member.isGetter) {
+          type =
+              closedWorld.elementEnvironment.getFunctionType(member).returnType;
+        } else if (member.isSetter) {
+          type = closedWorld.elementEnvironment
+              .getFunctionType(member)
+              .parameterTypes
+              .first;
+        }
+        Expect.isTrue(type is TypeVariableType,
+            "Unexpected member type for $member: $type");
+        TypeVariableType typeVariable = type;
+        Expect.equals(
+            cls,
+            typeVariable.element.typeDeclaration,
+            "Unexpected type declaration for $typeVariable for $member. "
+            "Expected $cls, found ${typeVariable.element.typeDeclaration}.");
+      } else {
+        Expect.isNull(member,
+            "Unexpected member 'method' in class '$className': $member.");
+      }
+    }
+
+    check('I', hasMethod: true, isAbstract: true, declaringClass: 'I');
+    check('A', hasMethod: true, declaringClass: 'A');
+    check('B', hasMethod: true, declaringClass: 'B');
+    check('C1', hasMethod: false);
+    check('C2', hasMethod: true, declaringClass: 'C2');
+    check('D1', hasMethod: false);
+    check('D2', hasMethod: false);
+    check('D3', hasMethod: true, declaringClass: 'D3');
+    check('E1', hasMethod: true, declaringClass: 'E1');
+    check('E2', hasMethod: false);
+    check('E3', hasMethod: true, declaringClass: 'E1');
+    check('F1', hasMethod: true, declaringClass: 'F1');
+    check('F2', hasMethod: true, declaringClass: 'F2');
+    check('F3', hasMethod: true, declaringClass: 'F1');
+    check('G1', hasMethod: true, isAbstract: true, declaringClass: 'G1');
+    check('G2', hasMethod: false);
+    check('G3', hasMethod: true, declaringClass: 'G3');
+    check('H1', hasMethod: true, isAbstract: true, declaringClass: 'H1');
+    check('H2', hasMethod: false);
+    check('H3', hasMethod: true, declaringClass: 'H3');
+  });
+}
diff --git a/tests/compiler/dart2js_extra/no_such_method_strong10_test.dart b/tests/compiler/dart2js_extra/no_such_method_strong10_test.dart
new file mode 100644
index 0000000..c09441f
--- /dev/null
+++ b/tests/compiler/dart2js_extra/no_such_method_strong10_test.dart
@@ -0,0 +1,31 @@
+// 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.
+
+// dart2jsOptions=--strong
+
+import 'package:expect/expect.dart';
+
+abstract class A {
+  m(x);
+}
+
+class B implements A {
+  noSuchMethod(Invocation i) {
+    print("nsm call: ${i.memberName}");
+    if (i.isGetter) {
+      throw (" - tearoff");
+    }
+    if (i.isMethod) {
+      print(" - method invocation");
+      return 42;
+    }
+    return 123;
+  }
+}
+
+void main() {
+  A x = new B();
+  var tearoff = x.m;
+  Expect.equals(42, tearoff(3));
+}
diff --git a/tests/compiler/dart2js_extra/no_such_method_strong1_test.dart b/tests/compiler/dart2js_extra/no_such_method_strong1_test.dart
new file mode 100644
index 0000000..62b6c60
--- /dev/null
+++ b/tests/compiler/dart2js_extra/no_such_method_strong1_test.dart
@@ -0,0 +1,30 @@
+// 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.
+
+// dart2jsOptions=--strong
+
+import 'package:expect/expect.dart';
+
+abstract class A {
+  m(x);
+}
+
+class B implements A {
+  noSuchMethod(Invocation i) {
+    print("nsm call: ${i.memberName}");
+    if (i.isGetter) {
+      throw (" - tearoff");
+    }
+    if (i.isMethod) {
+      print(" - method invocation");
+      return 42;
+    }
+    return 123;
+  }
+}
+
+void main() {
+  A x = new B();
+  Expect.equals(42, x.m(3));
+}
diff --git a/tests/compiler/dart2js_extra/no_such_method_strong2_test.dart b/tests/compiler/dart2js_extra/no_such_method_strong2_test.dart
new file mode 100644
index 0000000..e42f388
--- /dev/null
+++ b/tests/compiler/dart2js_extra/no_such_method_strong2_test.dart
@@ -0,0 +1,22 @@
+// 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.
+
+// dart2jsOptions=--strong
+
+import 'package:expect/expect.dart';
+
+abstract class A {
+  m(x);
+}
+
+class B implements A {
+  noSuchMethod(Invocation i) {
+    return i.memberName;
+  }
+}
+
+void main() {
+  A x = new B();
+  Expect.equals(#m, x.m(3));
+}
diff --git a/tests/compiler/dart2js_extra/no_such_method_strong3_test.dart b/tests/compiler/dart2js_extra/no_such_method_strong3_test.dart
new file mode 100644
index 0000000..5353d7b
--- /dev/null
+++ b/tests/compiler/dart2js_extra/no_such_method_strong3_test.dart
@@ -0,0 +1,22 @@
+// 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.
+
+// dart2jsOptions=--strong
+
+import 'package:expect/expect.dart';
+
+abstract class A {
+  m(x, y);
+}
+
+class B implements A {
+  noSuchMethod(Invocation i) {
+    return '${i.positionalArguments[0]},${i.positionalArguments[1]}';
+  }
+}
+
+void main() {
+  A x = new B();
+  Expect.equals('3,4', x.m(3, 4));
+}
diff --git a/tests/compiler/dart2js_extra/no_such_method_strong4_test.dart b/tests/compiler/dart2js_extra/no_such_method_strong4_test.dart
new file mode 100644
index 0000000..1ec9499
--- /dev/null
+++ b/tests/compiler/dart2js_extra/no_such_method_strong4_test.dart
@@ -0,0 +1,23 @@
+// 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.
+
+// dart2jsOptions=--strong
+
+import 'package:expect/expect.dart';
+
+abstract class A {
+  m({a, b});
+}
+
+class B implements A {
+  noSuchMethod(Invocation i) {
+    return '${i.namedArguments[#a]},${i.namedArguments[#b]}';
+  }
+}
+
+void main() {
+  A x = new B();
+  Expect.equals('3,4', x.m(a: 3, b: 4));
+  Expect.equals('3,4', x.m(b: 4, a: 3));
+}
diff --git a/tests/compiler/dart2js_extra/no_such_method_strong5_test.dart b/tests/compiler/dart2js_extra/no_such_method_strong5_test.dart
new file mode 100644
index 0000000..94f1531
--- /dev/null
+++ b/tests/compiler/dart2js_extra/no_such_method_strong5_test.dart
@@ -0,0 +1,22 @@
+// 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.
+
+// dart2jsOptions=--strong
+
+import 'package:expect/expect.dart';
+
+abstract class A {
+  m<X, Y>();
+}
+
+class B implements A {
+  noSuchMethod(Invocation i) {
+    return [i.typeArguments[0], i.typeArguments[1]];
+  }
+}
+
+void main() {
+  A x = new B();
+  Expect.listEquals([int, String], x.m<int, String>());
+}
diff --git a/tests/compiler/dart2js_extra/no_such_method_strong6_test.dart b/tests/compiler/dart2js_extra/no_such_method_strong6_test.dart
new file mode 100644
index 0000000..0018b85
--- /dev/null
+++ b/tests/compiler/dart2js_extra/no_such_method_strong6_test.dart
@@ -0,0 +1,28 @@
+// 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.
+
+// dart2jsOptions=--strong
+
+import 'package:expect/expect.dart';
+
+abstract class A {
+  m();
+}
+
+class B implements A {
+  noSuchMethod(Invocation i) {
+    return 42;
+  }
+}
+
+class C extends B {
+  noSuchMethod(Invocation i) => 87;
+
+  method() => super.m();
+}
+
+void main() {
+  C x = new C();
+  Expect.equals(87, x.method());
+}
diff --git a/tests/compiler/dart2js_extra/no_such_method_strong7_test.dart b/tests/compiler/dart2js_extra/no_such_method_strong7_test.dart
new file mode 100644
index 0000000..7f3925f
--- /dev/null
+++ b/tests/compiler/dart2js_extra/no_such_method_strong7_test.dart
@@ -0,0 +1,22 @@
+// 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.
+
+// dart2jsOptions=--strong
+
+import 'package:expect/expect.dart';
+
+abstract class A {
+  get m;
+}
+
+class B implements A {
+  noSuchMethod(Invocation i) {
+    return i.isGetter ? 42 : 87;
+  }
+}
+
+void main() {
+  A x = new B();
+  Expect.equals(42, x.m);
+}
diff --git a/tests/compiler/dart2js_extra/no_such_method_strong8_test.dart b/tests/compiler/dart2js_extra/no_such_method_strong8_test.dart
new file mode 100644
index 0000000..a98447b
--- /dev/null
+++ b/tests/compiler/dart2js_extra/no_such_method_strong8_test.dart
@@ -0,0 +1,22 @@
+// 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.
+
+// dart2jsOptions=--strong
+
+import 'package:expect/expect.dart';
+
+abstract class A {
+  set m(_);
+}
+
+class B implements A {
+  noSuchMethod(Invocation i) {
+    return i.isSetter ? i.positionalArguments[0] : 87;
+  }
+}
+
+void main() {
+  A x = new B();
+  Expect.equals(42, x.m = 42);
+}
diff --git a/tests/compiler/dart2js_extra/no_such_method_strong9_test.dart b/tests/compiler/dart2js_extra/no_such_method_strong9_test.dart
new file mode 100644
index 0000000..25ccc7e
--- /dev/null
+++ b/tests/compiler/dart2js_extra/no_such_method_strong9_test.dart
@@ -0,0 +1,30 @@
+// 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.
+
+// dart2jsOptions=--strong
+
+import 'package:expect/expect.dart';
+
+abstract class A {
+  m();
+}
+
+abstract class B implements A {
+  noSuchMethod(Invocation i) {
+    return 42;
+  }
+}
+
+class C extends B {}
+
+class D extends C {
+  noSuchMethod(Invocation i) => 87;
+
+  method() => super.m();
+}
+
+void main() {
+  D x = new D();
+  Expect.equals(87, x.method());
+}
diff --git a/tests/language_2/language_2_dart2js.status b/tests/language_2/language_2_dart2js.status
index 01b658e..ef32963 100644
--- a/tests/language_2/language_2_dart2js.status
+++ b/tests/language_2/language_2_dart2js.status
@@ -760,8 +760,6 @@
 no_main_test/01: CompileTimeError
 no_such_method_mock_test: RuntimeError
 nosuchmethod_forwarding/nosuchmethod_forwarding_arguments_test: RuntimeError
-nosuchmethod_forwarding/nosuchmethod_forwarding_test/05: RuntimeError
-nosuchmethod_forwarding/nosuchmethod_forwarding_test/06: RuntimeError
 null_no_such_method_test: CompileTimeError
 number_identity2_test: RuntimeError
 numbers_test: RuntimeError, OK # non JS number semantics
@@ -800,12 +798,7 @@
 string_split_test: CompileTimeError
 string_supertype_checked_test: CompileTimeError
 super_bound_closure_test/none: CompileTimeError
-super_no_such_method1_test: CompileTimeError
-super_no_such_method2_test: CompileTimeError
-super_no_such_method3_test: CompileTimeError
-super_no_such_method4_test: CompileTimeError
-super_no_such_method5_test: CompileTimeError
-super_operator_index5_test: CompileTimeError
+super_call4_test/01: MissingCompileTimeError
 super_operator_index6_test: CompileTimeError
 super_operator_index7_test: CompileTimeError
 super_operator_index8_test: CompileTimeError
@@ -1206,8 +1199,6 @@
 no_main_test/01: CompileTimeError
 no_such_method_mock_test: RuntimeError
 nosuchmethod_forwarding/nosuchmethod_forwarding_arguments_test: RuntimeError
-nosuchmethod_forwarding/nosuchmethod_forwarding_test/05: RuntimeError
-nosuchmethod_forwarding/nosuchmethod_forwarding_test/06: RuntimeError
 null_no_such_method_test: CompileTimeError
 number_identity2_test: RuntimeError
 numbers_test: RuntimeError, OK # non JS number semantics
@@ -1264,12 +1255,7 @@
 string_split_test: CompileTimeError
 string_supertype_checked_test: CompileTimeError
 super_bound_closure_test/none: CompileTimeError
-super_no_such_method1_test: CompileTimeError
-super_no_such_method2_test: CompileTimeError
-super_no_such_method3_test: CompileTimeError
-super_no_such_method4_test: CompileTimeError
-super_no_such_method5_test: CompileTimeError
-super_operator_index5_test: CompileTimeError
+super_call4_test/01: MissingCompileTimeError
 super_operator_index6_test: CompileTimeError
 super_operator_index7_test: CompileTimeError
 super_operator_index8_test: CompileTimeError
@@ -1721,8 +1707,6 @@
 no_such_method_native_test: RuntimeError
 no_such_method_test: RuntimeError
 nosuchmethod_forwarding/nosuchmethod_forwarding_arguments_test: RuntimeError
-nosuchmethod_forwarding/nosuchmethod_forwarding_test/05: RuntimeError
-nosuchmethod_forwarding/nosuchmethod_forwarding_test/06: RuntimeError
 null_no_such_method_test: CompileTimeError
 number_identity2_test: RuntimeError
 numbers_test: RuntimeError, OK # non JS number semantics
@@ -1779,12 +1763,7 @@
 string_split_test: CompileTimeError
 string_supertype_checked_test: CompileTimeError
 super_bound_closure_test/none: CompileTimeError
-super_no_such_method1_test: CompileTimeError
-super_no_such_method2_test: CompileTimeError
-super_no_such_method3_test: CompileTimeError
-super_no_such_method4_test: CompileTimeError
-super_no_such_method5_test: CompileTimeError
-super_operator_index5_test: CompileTimeError
+super_call4_test/01: MissingCompileTimeError
 super_operator_index6_test: CompileTimeError
 super_operator_index7_test: CompileTimeError
 super_operator_index8_test: CompileTimeError
@@ -1871,7 +1850,6 @@
 const_constructor3_test/04: MissingCompileTimeError # OK - Subtype check uses JS number semantics.
 covariant_subtyping_test: Crash
 ct_const_test: RuntimeError
-nosuchmethod_forwarding/nosuchmethod_forwarding_partial_instantiation_test: RuntimeError
 
 [ $compiler == dart2js && $fasta && !$strong ]
 *: SkipByDesign