Version 2.14.0-197.0.dev

Merge commit '13a17e3d56ab4229ac59ac0619ed441bef3d7436' into 'dev'
diff --git a/pkg/compiler/lib/src/js_emitter/class_stub_generator.dart b/pkg/compiler/lib/src/js_emitter/class_stub_generator.dart
index b016b86..f14d9c0 100644
--- a/pkg/compiler/lib/src/js_emitter/class_stub_generator.dart
+++ b/pkg/compiler/lib/src/js_emitter/class_stub_generator.dart
@@ -228,21 +228,21 @@
         js(r'''function() { throw "Helper 'closureFromTearOff' missing." }''');
   }
 
-  jsAst.Statement tearOffGetter;
+  jsAst.Statement instanceTearOffGetter;
   if (options.useContentSecurityPolicy) {
-    tearOffGetter = js.statement(
+    instanceTearOffGetter = js.statement(
       '''
-      function tearOffGetter(funcs, applyTrampolineIndex, reflectionInfo, name, isIntercepted) {
+      function instanceTearOffGetter(funcs, applyTrampolineIndex, reflectionInfo, name, isIntercepted, needsDirectAccess) {
         var cache = null;
         return isIntercepted
             ? function(receiver) {
                 if (cache === null) cache = #createTearOffClass(
-                    this, funcs, applyTrampolineIndex, reflectionInfo, false, true, name);
+                    funcs, applyTrampolineIndex, reflectionInfo, false, true, name, needsDirectAccess);
                 return new cache(this, funcs[0], receiver, name);
               }
             : function() {
                 if (cache === null) cache = #createTearOffClass(
-                    this, funcs, applyTrampolineIndex, reflectionInfo, false, false, name);
+                    funcs, applyTrampolineIndex, reflectionInfo, false, false, name, needsDirectAccess);
                 return new cache(this, funcs[0], null, name);
               };
       }''',
@@ -270,46 +270,40 @@
     // create a context with the closed-over values. The closed-over values
     // include parameters, (Dart) top-level definitions, and the local `cache`
     // variable all in one context (passing `null` to initialize `cache`).
-    tearOffGetter = js.statement(
+    instanceTearOffGetter = js.statement(
       '''
-function tearOffGetter(funcs, applyTrampolineIndex, reflectionInfo, name, isIntercepted) {
-  return isIntercepted
-
-      ? new Function("funcs, applyTrampolineIndex, reflectionInfo, name, createTearOffClass, cache",
+function instanceTearOffGetter(funcs, applyTrampolineIndex, reflectionInfo, name, isIntercepted, needsDirectAccess) {
+  if (isIntercepted)
+    return new Function("funcs, applyTrampolineIndex, reflectionInfo, name, needsDirectAccess, createTearOffClass, cache",
           "return function tearOff_" + name + (functionCounter++) + "(receiver) {" +
             "if (cache === null) cache = createTearOffClass(" +
-                "this, funcs, applyTrampolineIndex, reflectionInfo, false, true, name);" +
+                "funcs, applyTrampolineIndex, reflectionInfo, false, true, name, needsDirectAccess);" +
                 "return new cache(this, funcs[0], receiver, name);" +
-           "}")(funcs, applyTrampolineIndex, reflectionInfo, name, #createTearOffClass, null)
-
-      : new Function("funcs, applyTrampolineIndex, reflectionInfo, name, createTearOffClass, cache",
+           "}")(funcs, applyTrampolineIndex, reflectionInfo, name, needsDirectAccess, #createTearOffClass, null);
+  else
+    return new Function("funcs, applyTrampolineIndex, reflectionInfo, name, needsDirectAccess, createTearOffClass, cache",
           "return function tearOff_" + name + (functionCounter++)+ "() {" +
             "if (cache === null) cache = createTearOffClass(" +
-                "this, funcs, applyTrampolineIndex, reflectionInfo, false, false, name);" +
+                "funcs, applyTrampolineIndex, reflectionInfo, false, false, name, needsDirectAccess);" +
                 "return new cache(this, funcs[0], null, name);" +
-             "}")(funcs, applyTrampolineIndex, reflectionInfo, name, #createTearOffClass, null);
+             "}")(funcs, applyTrampolineIndex, reflectionInfo, name, needsDirectAccess, #createTearOffClass, null);
 }''',
       {'createTearOffClass': closureFromTearOffAccessExpression},
     );
   }
 
-  jsAst.Statement tearOff = js.statement(
+  jsAst.Statement staticTearOffGetter = js.statement(
     '''
-    function tearOff(funcs, applyTrampolineIndex,
-          reflectionInfo, isStatic, name, isIntercepted) {
-      var cache = null;
-      return isStatic
-          ? function() {
-              if (cache === null) cache = #createTearOffClass(
-                  this, funcs, applyTrampolineIndex,
-                  reflectionInfo, true, false, name).prototype;
-              return cache;
-            }
-          : tearOffGetter(funcs, applyTrampolineIndex,
-              reflectionInfo, name, isIntercepted);
-    }''',
+function staticTearOffGetter(funcs, applyTrampolineIndex, reflectionInfo, name) {
+  var cache = null;
+  return function() {
+    if (cache === null) cache = #createTearOffClass(
+        funcs, applyTrampolineIndex, reflectionInfo, true, false, name).prototype;
+    return cache;
+  }
+}''',
     {'createTearOffClass': closureFromTearOffAccessExpression},
   );
 
-  return [tearOffGetter, tearOff];
+  return [instanceTearOffGetter, staticTearOffGetter];
 }
diff --git a/pkg/compiler/lib/src/js_emitter/model.dart b/pkg/compiler/lib/src/js_emitter/model.dart
index 8903eec..d5579b2 100644
--- a/pkg/compiler/lib/src/js_emitter/model.dart
+++ b/pkg/compiler/lib/src/js_emitter/model.dart
@@ -475,6 +475,14 @@
   /// and [aliasName].
   final js.Name aliasName;
 
+  /// `true` if the tear-off needs to access methods directly rather than rely
+  /// on JavaScript prototype lookup. This happens when a tear-off getter is
+  /// called via `super.method` and there is a shadowing definition of `method`
+  /// in some sublcass.
+  // TODO(sra): Consider instead having an alias per stub, creating tear-off
+  // trampolines that target the stubs.
+  final bool tearOffNeedsDirectAccess;
+
   /// True if this is the implicit `call` instance method of an anonymous
   /// closure. This predicate is false for explicit `call` methods and for
   /// functions that can be torn off.
@@ -486,19 +494,24 @@
   /// Name called via the general 'catch all' path of Function.apply.
   ///final js.Name applyName;
 
-  InstanceMethod(FunctionEntity element, js.Name name, js.Expression code,
-      List<ParameterStubMethod> parameterStubs, js.Name callName,
-      {bool needsTearOff,
-      js.Name tearOffName,
-      this.aliasName,
-      bool canBeApplied,
-      int requiredParameterCount,
-      /* List | Map */ optionalParameterDefaultValues,
-      this.isClosureCallMethod,
-      this.isIntercepted,
-      js.Expression functionType,
-      int applyIndex})
-      : super(element, name, code, parameterStubs, callName,
+  InstanceMethod(
+    FunctionEntity element,
+    js.Name name,
+    js.Expression code,
+    List<ParameterStubMethod> parameterStubs,
+    js.Name callName, {
+    bool needsTearOff,
+    js.Name tearOffName,
+    this.aliasName,
+    this.tearOffNeedsDirectAccess,
+    bool canBeApplied,
+    int requiredParameterCount,
+    /* List | Map */ optionalParameterDefaultValues,
+    this.isClosureCallMethod,
+    this.isIntercepted,
+    js.Expression functionType,
+    int applyIndex,
+  }) : super(element, name, code, parameterStubs, callName,
             needsTearOff: needsTearOff,
             tearOffName: tearOffName,
             canBeApplied: canBeApplied,
diff --git a/pkg/compiler/lib/src/js_emitter/program_builder/program_builder.dart b/pkg/compiler/lib/src/js_emitter/program_builder/program_builder.dart
index d210c3c..2c8489f 100644
--- a/pkg/compiler/lib/src/js_emitter/program_builder/program_builder.dart
+++ b/pkg/compiler/lib/src/js_emitter/program_builder/program_builder.dart
@@ -800,6 +800,7 @@
     if (code == null) return null;
 
     bool canTearOff = false;
+    bool tearOffNeedsDirectAccess = false;
     js.Name tearOffName;
     bool isClosureCallMethod = false;
     bool isNotApplyTarget =
@@ -819,9 +820,11 @@
         isClosureCallMethod = true;
       } else {
         // Careful with operators.
-        canTearOff = _codegenWorld.hasInvokedGetter(element) ||
-            _codegenWorld.methodsNeedsSuperGetter(element);
+        bool needsSuperGetter = _codegenWorld.methodsNeedsSuperGetter(element);
+        canTearOff =
+            _codegenWorld.hasInvokedGetter(element) || needsSuperGetter;
         tearOffName = _namer.getterForElement(element);
+        tearOffNeedsDirectAccess = needsSuperGetter;
       }
     }
 
@@ -865,6 +868,7 @@
         _generateParameterStubs(element, canTearOff, canBeApplied), callName,
         needsTearOff: canTearOff,
         tearOffName: tearOffName,
+        tearOffNeedsDirectAccess: tearOffNeedsDirectAccess,
         isClosureCallMethod: isClosureCallMethod,
         isIntercepted: isIntercepted,
         aliasName: aliasName,
diff --git a/pkg/compiler/lib/src/js_emitter/startup_emitter/fragment_emitter.dart b/pkg/compiler/lib/src/js_emitter/startup_emitter/fragment_emitter.dart
index 6590471..a32d4a1 100644
--- a/pkg/compiler/lib/src/js_emitter/startup_emitter/fragment_emitter.dart
+++ b/pkg/compiler/lib/src/js_emitter/startup_emitter/fragment_emitter.dart
@@ -4,17 +4,6 @@
 
 part of dart2js.js_emitter.startup_emitter.model_emitter;
 
-/// The name of the property that stores the tear-off getter on a static
-/// function.
-///
-/// This property is only used when isolates are used.
-///
-/// When serializing static functions we transmit the
-/// name of the static function, but not the name of the function's getter. We
-/// store the getter-function on the static function itself, which allows us to
-/// find it easily.
-const String _tearOffPropertyName = r'$tearOff';
-
 /// The fast startup emitter's goal is to minimize the amount of work that the
 /// JavaScript engine has to do before it can start running user code.
 ///
@@ -232,7 +221,7 @@
 
 // Each deferred hunk comes with its own types which are added to the end
 // of the types-array.
-// The `funTypes` passed to the `installTearOff` function below is relative to
+// The `funType` passed to the `installTearOff` function below is relative to
 // the hunk the function comes from. The `typesOffset` variable encodes the
 // offset at which the new types will be added.
 var typesOffset = 0;
@@ -253,13 +242,18 @@
 // different tearOffCode?
 function installTearOff(
     container, getterName, isStatic, isIntercepted, requiredParameterCount,
-    optionalParameterDefaultValues, callNames, funsOrNames, funType, applyIndex) {
+    optionalParameterDefaultValues, callNames, funsOrNames, funType, applyIndex,
+    needsDirectAccess) {
   // A function can have several stubs (for example to fill in optional
   // arguments). We collect these functions in the `funs` array.
   var funs = [];
   for (var i = 0; i < funsOrNames.length; i++) {
     var fun = funsOrNames[i];
-    if ((typeof fun) == "string") fun = container[fun];
+    if ((typeof fun) == "string") {
+      var stubName = fun;
+      fun = container[fun];
+      fun.#stubName = stubName;
+    }
     fun.#callName = callNames[i];
     funs.push(fun);
   }
@@ -271,19 +265,18 @@
   fun[#defaultArgumentValues] = optionalParameterDefaultValues;
   var reflectionInfo = funType;
   if (typeof reflectionInfo == "number") {
-    // The reflectionInfo can either be a function, or a pointer into the types
-    // table. If it points into the types-table we need to update the index,
-    // in case the tear-off is part of a deferred hunk.
+    // The reflectionInfo can be a string type recipe or an index into the types
+    // table.  If it points into the types-table we need to update the index, in
+    // case the tear-off is part of a deferred hunk.
     reflectionInfo = reflectionInfo + typesOffset;
   }
   var name = funsOrNames[0];
-  fun.#stubName = name;
-  var getterFunction =
-      tearOff(funs, applyIndex || 0, reflectionInfo, isStatic, name, isIntercepted);
+  applyIndex = applyIndex || 0;
+  var getterFunction = isStatic
+      ? staticTearOffGetter(funs, applyIndex, reflectionInfo, name)
+      : instanceTearOffGetter(funs, applyIndex, reflectionInfo, name,
+          isIntercepted, needsDirectAccess);
   container[getterName] = getterFunction;
-  if (isStatic) {
-    fun.$_tearOffPropertyName = getterFunction;
-  }
 }
 
 function installStaticTearOff(
@@ -295,18 +288,18 @@
   return installTearOff(
       container, getterName, true, false,
       requiredParameterCount, optionalParameterDefaultValues,
-      callNames, funsOrNames, funType, applyIndex);
+      callNames, funsOrNames, funType, applyIndex, false);
 }
 
 function installInstanceTearOff(
     container, getterName, isIntercepted,
     requiredParameterCount, optionalParameterDefaultValues,
-    callNames, funsOrNames, funType, applyIndex) {
+    callNames, funsOrNames, funType, applyIndex, needsDirectAccess) {
   // TODO(sra): Specialize installTearOff for instance methods.
   return installTearOff(
       container, getterName, false, isIntercepted,
       requiredParameterCount, optionalParameterDefaultValues,
-      callNames, funsOrNames, funType, applyIndex);
+      callNames, funsOrNames, funType, applyIndex, !!needsDirectAccess);
 }
 
 // Instead of setting the interceptor tags directly we use this update
@@ -359,7 +352,8 @@
       return installInstanceTearOff(
           container, getterName, isIntercepted,
           requiredParameterCount, optionalParameterDefaultValues,
-          callNames, [name], funType, applyIndex);
+          callNames, [name], funType, applyIndex,
+          /*needsDirectAccess:*/ false);
     }
   },
 
@@ -1471,10 +1465,13 @@
           "applyIndex": applyIndex,
         });
       } else {
+        bool tearOffNeedsDirectAccess =
+            (method as InstanceMethod).tearOffNeedsDirectAccess;
         if (requiredParameterCount <= 2 &&
             callNames.length == 1 &&
             optionalParameterDefaultValues is js.LiteralNull &&
-            method.applyIndex == 0) {
+            method.applyIndex == 0 &&
+            !tearOffNeedsDirectAccess) {
           js.Statement finish(int arity) {
             // Short form for exactly 0/1/2 arguments.
             String isInterceptedTag = isIntercepted ? 'i' : 'u';
@@ -1501,7 +1498,8 @@
         return js.js.statement('''
             #install(#container, #getterName, #isIntercepted,
                      #requiredParameterCount, #optionalParameterDefaultValues,
-                     #callNames, #funsOrNames, #funType, #applyIndex)''', {
+                     #callNames, #funsOrNames, #funType, #applyIndex,
+                     #tearOffNeedsDirectAccess)''', {
           "install": install,
           "container": container,
           "getterName": js.quoteName(method.tearOffName),
@@ -1513,6 +1511,9 @@
           "funsOrNames": funsOrNamesArray,
           "funType": method.functionType,
           "applyIndex": applyIndex,
+          // 'Truthy' values are ok for `tearOffNeedsDirectAccess`.
+          "tearOffNeedsDirectAccess":
+              js.number(tearOffNeedsDirectAccess ? 1 : 0),
         });
       }
     }
diff --git a/runtime/observatory/tests/service/get_instances_as_array_rpc_expression_evaluation_on_internal_test.dart b/runtime/observatory/tests/service/get_instances_as_array_rpc_expression_evaluation_on_internal_test.dart
new file mode 100644
index 0000000..7980ac9
--- /dev/null
+++ b/runtime/observatory/tests/service/get_instances_as_array_rpc_expression_evaluation_on_internal_test.dart
@@ -0,0 +1,51 @@
+// Copyright (c) 2021, 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:observatory/service_io.dart";
+
+import "test_helper.dart";
+
+bool gotError = false;
+
+var tests = <IsolateTest>[
+  (Isolate isolate) async {
+    Future<void> getInstancesAndExecuteExpression(Map member) async {
+      final Map params = {
+        "objectId": member["class"]["id"],
+        "includeSubclasses": false,
+        "includeImplementors": false,
+      };
+      final result = await isolate.invokeRpc("_getInstancesAsArray", params);
+      // This has previously caused an exception like
+      // "ServerRpcException(evaluate: Unexpected exception:
+      // FormatException: Unexpected character (at offset 329)"
+      final evalResult = await isolate.eval(result, "this");
+      if (evalResult.isError) {
+        gotError = true;
+        final DartError error = evalResult as DartError;
+        if (error.message
+                ?.contains("Cannot evaluate against a VM-internal object") !=
+            true) {
+          throw "Got error $error but expected another message.";
+        }
+      }
+    }
+
+    final params = {};
+    final result =
+        await isolate.invokeRpcNoUpgrade('_getAllocationProfile', params);
+    final List members = result['members'];
+    for (var member in members) {
+      final name = member["class"]["name"];
+      if (name == "Library") {
+        await getInstancesAndExecuteExpression(member);
+      }
+    }
+    if (!gotError) {
+      throw "Didn't get expected error!";
+    }
+  },
+];
+
+main(args) async => runIsolateTests(args, tests);
diff --git a/runtime/observatory/tests/service/service_kernel.status b/runtime/observatory/tests/service/service_kernel.status
index 8b88460..3baefe9 100644
--- a/runtime/observatory/tests/service/service_kernel.status
+++ b/runtime/observatory/tests/service/service_kernel.status
@@ -16,6 +16,7 @@
 eval_test: SkipByDesign # No incremental compiler available.
 evaluate_*: SkipByDesign # No incremental compiler available.
 get_allocation_traces_test: SkipByDesign # Allocation traces aren't consistent when running from snapshot.
+get_instances_as_array_rpc_expression_evaluation_on_internal_test: SkipByDesign # No incremental compiler available.
 get_object_rpc_test: SkipByDesign # App-JIT snapshots don't contain script sources.
 pause_on_exceptions_test: SkipByDesign # No incremental compiler available.
 rewind_optimized_out_test: SkipByDesign # No incremental compiler available.
@@ -102,6 +103,7 @@
 field_script_test: SkipByDesign # Debugger is disabled in AOT mode.
 get_allocation_samples_test: SkipByDesign # Debugger is disabled in AOT mode.
 get_allocation_traces_test: SkipByDesign # Debugger is disabled in AOT mode.
+get_instances_as_array_rpc_expression_evaluation_on_internal_test: SkipByDesign # Debugger is disabled in AOT mode
 get_object_rpc_test: SkipByDesign # Debugger is disabled in AOT mode.
 get_source_report_const_coverage_test: SkipByDesign # Debugger is disabled in AOT mode.
 get_source_report_test: SkipByDesign # Debugger is disabled in AOT mode.
diff --git a/runtime/observatory_2/tests/service_2/get_instances_as_array_rpc_expression_evaluation_on_internal_test.dart b/runtime/observatory_2/tests/service_2/get_instances_as_array_rpc_expression_evaluation_on_internal_test.dart
new file mode 100644
index 0000000..8766d96
--- /dev/null
+++ b/runtime/observatory_2/tests/service_2/get_instances_as_array_rpc_expression_evaluation_on_internal_test.dart
@@ -0,0 +1,51 @@
+// Copyright (c) 2021, 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:observatory_2/service_io.dart";
+
+import "test_helper.dart";
+
+bool gotError = false;
+
+var tests = <IsolateTest>[
+  (Isolate isolate) async {
+    Future<void> getInstancesAndExecuteExpression(Map member) async {
+      final Map params = {
+        "objectId": member["class"]["id"],
+        "includeSubclasses": false,
+        "includeImplementors": false,
+      };
+      final result = await isolate.invokeRpc("_getInstancesAsArray", params);
+      // This has previously caused an exception like
+      // "ServerRpcException(evaluate: Unexpected exception:
+      // FormatException: Unexpected character (at offset 329)"
+      final evalResult = await isolate.eval(result, "this");
+      if (evalResult.isError) {
+        gotError = true;
+        final DartError error = evalResult as DartError;
+        if (error.message
+                ?.contains("Cannot evaluate against a VM-internal object") !=
+            true) {
+          throw "Got error $error but expected another message.";
+        }
+      }
+    }
+
+    final params = {};
+    final result =
+        await isolate.invokeRpcNoUpgrade('_getAllocationProfile', params);
+    final List members = result['members'];
+    for (var member in members) {
+      final name = member["class"]["name"];
+      if (name == "Library") {
+        await getInstancesAndExecuteExpression(member);
+      }
+    }
+    if (!gotError) {
+      throw "Didn't get expected error!";
+    }
+  },
+];
+
+main(args) async => runIsolateTests(args, tests);
diff --git a/runtime/observatory_2/tests/service_2/service_2_kernel.status b/runtime/observatory_2/tests/service_2/service_2_kernel.status
index 8b88460..1fb2444 100644
--- a/runtime/observatory_2/tests/service_2/service_2_kernel.status
+++ b/runtime/observatory_2/tests/service_2/service_2_kernel.status
@@ -16,6 +16,7 @@
 eval_test: SkipByDesign # No incremental compiler available.
 evaluate_*: SkipByDesign # No incremental compiler available.
 get_allocation_traces_test: SkipByDesign # Allocation traces aren't consistent when running from snapshot.
+get_instances_as_array_rpc_expression_evaluation_on_internal_test: SkipByDesign # No incremental compiler available.
 get_object_rpc_test: SkipByDesign # App-JIT snapshots don't contain script sources.
 pause_on_exceptions_test: SkipByDesign # No incremental compiler available.
 rewind_optimized_out_test: SkipByDesign # No incremental compiler available.
@@ -102,6 +103,7 @@
 field_script_test: SkipByDesign # Debugger is disabled in AOT mode.
 get_allocation_samples_test: SkipByDesign # Debugger is disabled in AOT mode.
 get_allocation_traces_test: SkipByDesign # Debugger is disabled in AOT mode.
+get_instances_as_array_rpc_expression_evaluation_on_internal_test: SkipByDesign # Debugger is disabled in AOT mode.
 get_object_rpc_test: SkipByDesign # Debugger is disabled in AOT mode.
 get_source_report_const_coverage_test: SkipByDesign # Debugger is disabled in AOT mode.
 get_source_report_test: SkipByDesign # Debugger is disabled in AOT mode.
diff --git a/runtime/vm/service.cc b/runtime/vm/service.cc
index 729c187..9a90be3 100644
--- a/runtime/vm/service.cc
+++ b/runtime/vm/service.cc
@@ -2795,6 +2795,7 @@
                      "%s: invalid 'targetId' parameter: "
                      "Cannot evaluate against a VM-internal object",
                      js->method());
+      return;
     }
   }
 
diff --git a/sdk/lib/_internal/js_runtime/lib/js_helper.dart b/sdk/lib/_internal/js_runtime/lib/js_helper.dart
index 7fe9802..0706331 100644
--- a/sdk/lib/_internal/js_runtime/lib/js_helper.dart
+++ b/sdk/lib/_internal/js_runtime/lib/js_helper.dart
@@ -1831,8 +1831,7 @@
 /// Superclass for Dart closures.
 ///
 /// All static, tear-off, function declaration and function expression closures
-/// extend this class, but classes that implement Function via a `call` method
-/// do not.
+/// extend this class.
 abstract class Closure implements Function {
   // TODO(ahe): These constants must be in sync with
   // reflection_data_parser.dart.
@@ -1874,13 +1873,13 @@
   /// Caution: this function may be called when building constants.
   /// TODO(ahe): Don't call this function when building constants.
   static fromTearOff(
-    receiver,
     List functions,
-    int? applyTrampolineIndex,
+    int applyTrampolineIndex,
     var reflectionInfo,
     bool isStatic,
     bool isIntercepted,
     String propertyName,
+    bool needsDirectAccess,
   ) {
     JS_EFFECT(() {
       // The functions are called here to model the calls from JS forms below.
@@ -1931,7 +1930,7 @@
     // we only use the new instance to access the constructor property and use
     // Object.create to create the desired prototype.
     //
-    // TODO(sra): Perhaps cache the prototype to avoid the allocation.
+    // TODO(sra): Cache the prototype to avoid the allocation.
     var prototype = isStatic
         ? JS('StaticClosure', 'Object.create(#.constructor.prototype)',
             new StaticClosure())
@@ -1963,7 +1962,7 @@
     // Create a closure and "monkey" patch it with call stubs.
     var trampoline = function;
     if (!isStatic) {
-      trampoline = forwardCallTo(receiver, function, isIntercepted);
+      trampoline = forwardCallTo(function, isIntercepted, needsDirectAccess);
       JS('', '#.\$reflectionInfo = #', trampoline, reflectionInfo);
     } else {
       JS('', '#[#] = #', prototype, STATIC_FUNCTION_NAME_PROPERTY_NAME,
@@ -1982,7 +1981,9 @@
       var stubCallName = JS('String|Null', '#[#]', stub,
           JS_GET_NAME(JsGetName.CALL_NAME_PROPERTY));
       if (stubCallName != null) {
-        stub = isStatic ? stub : forwardCallTo(receiver, stub, isIntercepted);
+        stub = isStatic
+            ? stub
+            : forwardCallTo(stub, isIntercepted, needsDirectAccess);
         JS('', '#[#] = #', prototype, stubCallName, stub);
       }
       if (i == applyTrampolineIndex) {
@@ -2041,10 +2042,22 @@
   }
 
   static cspForwardCall(
-      int arity, bool isSuperCall, String? stubName, function) {
+      int arity, bool needsDirectAccess, String? stubName, function) {
     var getSelf = RAW_DART_FUNCTION_REF(BoundClosure.selfOf);
-    // Handle intercepted stub-names with the default slow case.
-    if (isSuperCall) arity = -1;
+
+    // We have the target method (or an arity stub for the method) in
+    // [function]. These fixed-arity forwarding stubs could use
+    // `Function.prototype.call` on the target directly, but on some browsers it
+    // is quite a bit faster to do a property access again to get the
+    // function. Accessing the property again will fail (retrieve the wrong
+    // function) if the desired property is shadowed. This can happen, e.g.,
+    // when the tear-off was created by a super-getter call `super.method` and
+    // `method` has an override on some subclass.
+    //
+    // To handle the shadowing-of-a-method-that-has-a-super-tearoff case, we use
+    // the default slow case that uses `Function.prototype.apply`.
+    if (needsDirectAccess) arity = -1;
+
     switch (arity) {
       case 0:
         return JS(
@@ -2121,19 +2134,14 @@
 
   static bool get isCsp => JS_GET_FLAG('USE_CONTENT_SECURITY_POLICY');
 
-  static forwardCallTo(receiver, function, bool isIntercepted) {
-    if (isIntercepted) return forwardInterceptedCallTo(receiver, function);
+  static forwardCallTo(function, bool isIntercepted, bool needsDirectAccess) {
+    if (isIntercepted)
+      return forwardInterceptedCallTo(function, needsDirectAccess);
     String? stubName = JS('String|Null', '#.\$stubName', function);
     int arity = JS('int', '#.length', function);
-    var lookedUpFunction = JS('', '#[#]', receiver, stubName);
-    // The receiver[stubName] may not be equal to the function if we try to
-    // forward to a super-method. Especially when we create a bound closure
-    // of a super-call we need to make sure that we don't forward back to the
-    // dynamically looked up function.
-    bool isSuperCall = !identical(function, lookedUpFunction);
 
-    if (isCsp || isSuperCall || arity >= 27) {
-      return cspForwardCall(arity, isSuperCall, stubName, function);
+    if (isCsp || needsDirectAccess || arity >= 27) {
+      return cspForwardCall(arity, needsDirectAccess, stubName, function);
     }
 
     if (arity == 0) {
@@ -2160,11 +2168,11 @@
   }
 
   static cspForwardInterceptedCall(
-      int arity, bool isSuperCall, String? name, function) {
+      int arity, bool needsDirectAccess, String? name, function) {
     var getSelf = RAW_DART_FUNCTION_REF(BoundClosure.selfOf);
     var getReceiver = RAW_DART_FUNCTION_REF(BoundClosure.receiverOf);
     // Handle intercepted stub-names with the default slow case.
-    if (isSuperCall) arity = -1;
+    if (needsDirectAccess) arity = -1;
     switch (arity) {
       case 0:
         // Intercepted functions always takes at least one argument (the
@@ -2252,21 +2260,16 @@
     }
   }
 
-  static forwardInterceptedCallTo(receiver, function) {
+  static forwardInterceptedCallTo(function, bool needsDirectAccess) {
     String selfField = BoundClosure.selfFieldName();
     String receiverField = BoundClosure.receiverFieldName();
     String? stubName = JS('String|Null', '#.\$stubName', function);
     int arity = JS('int', '#.length', function);
     bool isCsp = JS_GET_FLAG('USE_CONTENT_SECURITY_POLICY');
-    var lookedUpFunction = JS('', '#[#]', receiver, stubName);
-    // The receiver[stubName] may not be equal to the function if we try to
-    // forward to a super-method. Especially when we create a bound closure
-    // of a super-call we need to make sure that we don't forward back to the
-    // dynamically looked up function.
-    bool isSuperCall = !identical(function, lookedUpFunction);
 
-    if (isCsp || isSuperCall || arity >= 28) {
-      return cspForwardInterceptedCall(arity, isSuperCall, stubName, function);
+    if (isCsp || needsDirectAccess || arity >= 28) {
+      return cspForwardInterceptedCall(
+          arity, needsDirectAccess, stubName, function);
     }
     if (arity == 1) {
       return JS(
@@ -2310,16 +2313,17 @@
 }
 
 /// Called from implicit method getter (aka tear-off).
-closureFromTearOff(receiver, functions, applyTrampolineIndex, reflectionInfo,
-    isStatic, isIntercepted, name) {
+closureFromTearOff(functions, applyTrampolineIndex, reflectionInfo, isStatic,
+    isIntercepted, name, needsDirectAccess) {
   return Closure.fromTearOff(
-      receiver,
-      JS('JSArray', '#', functions),
-      JS('int|Null', '#', applyTrampolineIndex),
-      reflectionInfo,
-      JS('bool', '!!#', isStatic),
-      JS('bool', '!!#', isIntercepted),
-      JS('String', '#', name));
+    JS('JSArray', '#', functions),
+    JS('int', '#||0', applyTrampolineIndex),
+    reflectionInfo,
+    JS('bool', '!!#', isStatic),
+    JS('bool', '!!#', isIntercepted),
+    JS('String', '#', name),
+    JS('bool', '!!#', needsDirectAccess),
+  );
 }
 
 /// Represents an implicit closure of a function.
@@ -2337,18 +2341,18 @@
 /// Represents a 'tear-off' or property extraction closure of an instance
 /// method, that is an instance method bound to a specific receiver (instance).
 class BoundClosure extends TearOffClosure {
-  /// The receiver or interceptor.
-  // TODO(ahe): This could just be the interceptor, we always know if
-  // we need the interceptor when generating the call method.
+  /// The JavaScript receiver, which is the Dart receiver or the interceptor.
   final _self;
 
   /// The method.
   final _target;
 
-  /// The receiver. Null if [_self] is not an interceptor.
+  /// The Dart receiver if [_target] is an intercepted method (in which case
+  /// [_self] is the interceptor), otherwise `null`.
   final _receiver;
 
-  /// The name of the function. Only used by the mirror system.
+  /// The name of the function. Only used by `toString()`.
+  // TODO(sra): This should be part of the generated tear-off class.
   final String _name;
 
   BoundClosure(this._self, this._target, this._receiver, this._name);
diff --git a/tools/VERSION b/tools/VERSION
index 17c91f5..d2988ee 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 14
 PATCH 0
-PRERELEASE 196
+PRERELEASE 197
 PRERELEASE_PATCH 0
\ No newline at end of file