Version 2.15.0-63.0.dev

Merge commit '47b7fd4b757b090eba5e7665c5dac0f8c9861964' into 'dev'
diff --git a/pkg/analysis_server/lib/src/services/correction/dart/add_type_annotation.dart b/pkg/analysis_server/lib/src/services/correction/dart/add_type_annotation.dart
index 0c207dd..f0e0091 100644
--- a/pkg/analysis_server/lib/src/services/correction/dart/add_type_annotation.dart
+++ b/pkg/analysis_server/lib/src/services/correction/dart/add_type_annotation.dart
@@ -142,11 +142,9 @@
       return;
     }
     // Ensure that there is a single type.
-    if (variables.length > 1) {
-      for (var v in variables) {
-        if (v != variable && _typeForVariable(v) != type) {
-          return;
-        }
+    for (var i = 1; i < variables.length; i++) {
+      if (_typeForVariable(variables[i]) != type) {
+        return;
       }
     }
     if ((type is! InterfaceType || type.isDartCoreNull) &&
diff --git a/pkg/analysis_server/lib/src/services/correction/dart/create_missing_overrides.dart b/pkg/analysis_server/lib/src/services/correction/dart/create_missing_overrides.dart
index c56a829..36a1566 100644
--- a/pkg/analysis_server/lib/src/services/correction/dart/create_missing_overrides.dart
+++ b/pkg/analysis_server/lib/src/services/correction/dart/create_missing_overrides.dart
@@ -54,8 +54,8 @@
     await builder.addDartFileEdit(file, (builder) {
       final syntheticLeftBracket = targetClass.leftBracket.isSynthetic;
       if (syntheticLeftBracket) {
-        var previousToLeftBracket = targetClass.leftBracket.previous;
-        builder.addSimpleInsertion(previousToLeftBracket!.end, ' {');
+        var previousToLeftBracket = targetClass.leftBracket.previous!;
+        builder.addSimpleInsertion(previousToLeftBracket.end, ' {');
       }
 
       builder.addInsertion(location.offset, (builder) {
@@ -109,8 +109,8 @@
         builder.write(location.suffix);
 
         if (targetClass.rightBracket.isSynthetic) {
-          var next = targetClass.rightBracket.next;
-          if (next!.type != TokenType.CLOSE_CURLY_BRACKET) {
+          var next = targetClass.rightBracket.next!;
+          if (next.type != TokenType.CLOSE_CURLY_BRACKET) {
             if (!syntheticLeftBracket) {
               builder.write(eol);
             }
diff --git a/pkg/compiler/lib/src/ssa/codegen.dart b/pkg/compiler/lib/src/ssa/codegen.dart
index 0abdba8..8cf8ed8 100644
--- a/pkg/compiler/lib/src/ssa/codegen.dart
+++ b/pkg/compiler/lib/src/ssa/codegen.dart
@@ -2644,14 +2644,15 @@
 
   @override
   visitThrow(HThrow node) {
+    SourceInformation sourceInformation = node.sourceInformation;
     if (node.isRethrow) {
       use(node.inputs[0]);
-      pushStatement(
-          new js.Throw(pop()).withSourceInformation(node.sourceInformation));
+      pushStatement(js.Throw(pop()).withSourceInformation(sourceInformation));
     } else {
-      generateThrowWithHelper(
-          _commonElements.wrapExceptionHelper, node.inputs[0],
-          sourceInformation: node.sourceInformation);
+      use(node.inputs[0]);
+      _pushCallStatic(
+          _commonElements.wrapExceptionHelper, [pop()], sourceInformation);
+      pushStatement(js.Throw(pop()).withSourceInformation(sourceInformation));
     }
   }
 
@@ -2683,94 +2684,141 @@
     // If the checks always succeeds, we would have removed the bounds check
     // completely.
     assert(node.staticChecks != HBoundsCheck.ALWAYS_TRUE);
-    if (node.staticChecks != HBoundsCheck.ALWAYS_FALSE) {
-      js.Expression under;
-      js.Expression over;
-      if (node.staticChecks != HBoundsCheck.ALWAYS_ABOVE_ZERO) {
-        use(node.index);
-        if (node.index.isInteger(_abstractValueDomain).isDefinitelyTrue) {
-          under = js.js("# < 0", pop());
-        } else {
-          js.Expression jsIndex = pop();
-          under = js.js("# >>> 0 !== #", [jsIndex, jsIndex]);
-        }
-      } else if (node.index
-          .isInteger(_abstractValueDomain)
-          .isPotentiallyFalse) {
-        checkInt(node.index, '!==');
-        under = pop();
-      }
-      if (node.staticChecks != HBoundsCheck.ALWAYS_BELOW_LENGTH) {
-        var index = node.index;
-        use(index);
-        js.Expression jsIndex = pop();
-        use(node.length);
-        over = new js.Binary(">=", jsIndex, pop());
-      }
-      assert(over != null || under != null);
-      js.Expression underOver = under == null
-          ? over
-          : over == null
-              ? under
-              : new js.Binary("||", under, over);
-      js.Statement thenBody = new js.Block.empty();
-      js.Block oldContainer = currentContainer;
-      currentContainer = thenBody;
-      generateThrowWithHelper(_commonElements.throwIndexOutOfRangeException,
+
+    if (node.staticChecks == HBoundsCheck.ALWAYS_FALSE) {
+      _pushThrowWithHelper(_commonElements.throwIndexOutOfRangeException,
           [node.array, node.reportedIndex],
           sourceInformation: node.sourceInformation);
-      currentContainer = oldContainer;
-      thenBody = unwrapStatement(thenBody);
-      pushStatement(new js.If.noElse(underOver, thenBody)
-          .withSourceInformation(node.sourceInformation));
-    } else {
-      generateThrowWithHelper(_commonElements.throwIndexOutOfRangeException,
-          [node.array, node.index]);
+      return;
     }
+
+    HInstruction index = node.index;
+
+    // Generate a test for out-of-bounds, either under or over the range.  NaN
+    // values can creep in, and comparisons on NaN are false, so
+    //
+    //     if (i < 0) throw ...
+    //
+    // will fail to throw if `i` is NaN. The test
+    //
+    //     if (!(i >= 0)) ...
+    //
+    // is 'NaN-safe'.
+
+    // TODO(sra): Better analysis of possible NaN input.
+    bool indexCanBeNaN = !_isDefinitelyNotNaN(index);
+
+    js.Expression under;
+    js.Expression over;
+
+    if (index.isInteger(_abstractValueDomain).isPotentiallyFalse) {
+      // Combined domain check and low bound check. `a >>> 0 !== a` is true for
+      // `null`, `undefined`, `NaN`, and non-integral number and any integral
+      // number outside the 32-bit unsigned range.
+      use(index);
+      js.Expression jsIndex = pop();
+      // This test is 'NaN-safe' since `a!==b` is the same as `!(a===b)`.
+      under = js.js("# >>> 0 !== #", [jsIndex, jsIndex]);
+      indexCanBeNaN = false;
+    } else if (node.staticChecks != HBoundsCheck.ALWAYS_ABOVE_ZERO) {
+      use(index);
+      // The index must be an `int`, otherwise we could have used the combined
+      // check above.
+      if (indexCanBeNaN) {
+        under = js.js('!(# >= 0)', pop());
+      } else {
+        under = js.js('# < 0', pop());
+      }
+    }
+
+    if (node.staticChecks != HBoundsCheck.ALWAYS_BELOW_LENGTH) {
+      use(index);
+      js.Expression jsIndex = pop();
+      use(node.length);
+      js.Expression jsLength = pop();
+      if (indexCanBeNaN) {
+        over = js.js('!(# < #)', [jsIndex, jsLength]);
+      } else {
+        over = js.js('# >= #', [jsIndex, jsLength]);
+      }
+    }
+
+    assert(over != null || under != null);
+    js.Expression underOver;
+    if (under == null) {
+      underOver = over;
+    } else if (over == null) {
+      underOver = under;
+    } else {
+      if (under is js.Prefix &&
+          under.op == '!' &&
+          over is js.Prefix &&
+          over.op == '!') {
+        // De Morgans law:  !(a) || !(b)  <->  !(a && b)
+        underOver = js.js('!(# && #)', [under.argument, over.argument]);
+      } else {
+        underOver = js.Binary('||', under, over);
+      }
+    }
+
+    // Generate the call to the 'throw' helper in a block in case it needs
+    // multiple statements.
+    js.Statement thenBody = js.Block.empty();
+    js.Block oldContainer = currentContainer;
+    currentContainer = thenBody;
+    _pushThrowWithHelper(_commonElements.throwIndexOutOfRangeException,
+        [node.array, node.reportedIndex],
+        sourceInformation: node.sourceInformation);
+    currentContainer = oldContainer;
+    thenBody = unwrapStatement(thenBody);
+    pushStatement(js.If.noElse(underOver, thenBody)
+        .withSourceInformation(node.sourceInformation));
   }
 
-  void generateThrowWithHelper(FunctionEntity helper, argument,
+  bool _isDefinitelyNotNaN(HInstruction node) {
+    if (node is HConstant) {
+      if (node.isInteger(_abstractValueDomain).isDefinitelyTrue) return true;
+      return false;
+    }
+
+    // TODO(sra): Use some form of dataflow. Starting from a small number you
+    // can add or subtract a small number any number of times and still have a
+    // finite number. Many operations, produce small numbers (some constants,
+    // HGetLength, HBitAnd). This could be used to determine that most loop
+    // indexes are finite and thus not NaN.
+
+    return false;
+  }
+
+  void _pushThrowWithHelper(FunctionEntity helper, List<HInstruction> inputs,
       {SourceInformation sourceInformation}) {
-    js.Expression jsHelper = _emitter.staticFunctionAccess(helper);
-    List arguments = <js.Expression>[];
-    if (argument is List) {
-      argument.forEach((instruction) {
-        use(instruction);
-        arguments.add(pop());
-      });
-    } else {
-      use(argument);
+    List<js.Expression> arguments = [];
+    for (final input in inputs) {
+      use(input);
       arguments.add(pop());
     }
-    _registry.registerStaticUse(new StaticUse.staticInvoke(
-        helper, new CallStructure.unnamed(arguments.length)));
-    js.Call value = new js.Call(jsHelper, arguments.toList(growable: false),
-        sourceInformation: sourceInformation);
+    _pushCallStatic(helper, arguments, sourceInformation);
     // BUG(4906): Using throw/return here adds to the size of the generated code
     // but it has the advantage of explicitly telling the JS engine that
     // this code path will terminate abruptly. Needs more work.
-    if (helper == _commonElements.wrapExceptionHelper) {
-      pushStatement(
-          new js.Throw(value).withSourceInformation(sourceInformation));
-    } else {
-      pushStatement(
-          new js.Return(value).withSourceInformation(sourceInformation));
-    }
+    pushStatement(js.Return(pop()).withSourceInformation(sourceInformation));
+  }
+
+  void _pushCallStatic(FunctionEntity target, List<js.Expression> arguments,
+      SourceInformation sourceInformation) {
+    _registry.registerStaticUse(StaticUse.staticInvoke(
+        target, CallStructure.unnamed(arguments.length)));
+    js.Expression jsTarget = _emitter.staticFunctionAccess(target);
+    js.Call call = js.Call(jsTarget, List.of(arguments, growable: false))
+        .withSourceInformation(sourceInformation);
+    push(call);
   }
 
   @override
   visitThrowExpression(HThrowExpression node) {
-    HInstruction argument = node.inputs[0];
-    use(argument);
-
-    FunctionEntity helper = _commonElements.throwExpressionHelper;
-    _registry.registerStaticUse(
-        new StaticUse.staticInvoke(helper, CallStructure.ONE_ARG));
-
-    js.Expression jsHelper = _emitter.staticFunctionAccess(helper);
-    js.Call value = new js.Call(jsHelper, [pop()])
-        .withSourceInformation(node.sourceInformation);
-    push(value);
+    use(node.inputs[0]);
+    _pushCallStatic(
+        _commonElements.throwExpressionHelper, [pop()], node.sourceInformation);
   }
 
   @override
@@ -2891,14 +2939,6 @@
         .withSourceInformation(node.sourceInformation));
   }
 
-  void checkInt(HInstruction input, String cmp) {
-    use(input);
-    js.Expression left = pop();
-    use(input);
-    js.Expression or0 = new js.Binary("|", pop(), new js.LiteralNumber("0"));
-    push(new js.Binary(cmp, left, or0));
-  }
-
   void checkTypeOf(HInstruction input, String cmp, String typeName,
       SourceInformation sourceInformation) {
     use(input);
@@ -2921,12 +2961,14 @@
   void visitPrimitiveCheck(HPrimitiveCheck node) {
     js.Expression test = _generateReceiverOrArgumentTypeTest(node);
     js.Block oldContainer = currentContainer;
-    js.Statement body = new js.Block.empty();
+    js.Statement body = js.Block.empty();
     currentContainer = body;
     if (node.isArgumentTypeCheck) {
-      generateThrowWithHelper(
-          _commonElements.throwIllegalArgumentException, node.checkedInput,
-          sourceInformation: node.sourceInformation);
+      use(node.checkedInput);
+      _pushCallStatic(_commonElements.throwIllegalArgumentException, [pop()],
+          node.sourceInformation);
+      pushStatement(
+          js.Return(pop()).withSourceInformation(node.sourceInformation));
     } else if (node.isReceiverTypeCheck) {
       use(node.checkedInput);
       js.Name methodName =
@@ -2934,12 +2976,12 @@
       js.Expression call = js.propertyCall(
           pop(), methodName, []).withSourceInformation(node.sourceInformation);
       pushStatement(
-          new js.Return(call).withSourceInformation(node.sourceInformation));
+          js.Return(call).withSourceInformation(node.sourceInformation));
     }
     currentContainer = oldContainer;
     body = unwrapStatement(body);
-    pushStatement(new js.If.noElse(test, body)
-        .withSourceInformation(node.sourceInformation));
+    pushStatement(
+        js.If.noElse(test, body).withSourceInformation(node.sourceInformation));
   }
 
   js.Expression _generateReceiverOrArgumentTypeTest(HPrimitiveCheck node) {
@@ -2972,9 +3014,8 @@
     _registry.registerStaticUse(staticUse);
     use(node.checkedInput);
     List<js.Expression> arguments = [pop()];
-    push(
-        new js.Call(_emitter.staticFunctionAccess(staticUse.element), arguments)
-            .withSourceInformation(node.sourceInformation));
+    push(js.Call(_emitter.staticFunctionAccess(staticUse.element), arguments)
+        .withSourceInformation(node.sourceInformation));
   }
 
   @override
diff --git a/pkg/compiler/test/codegen/value_range_test.dart b/pkg/compiler/test/codegen/value_range_test.dart
index 0676cd7..c4b1a43 100644
--- a/pkg/compiler/test/codegen/value_range_test.dart
+++ b/pkg/compiler/test/codegen/value_range_test.dart
@@ -248,22 +248,23 @@
   return compile(code, check: (String generated) {
     switch (kind) {
       case REMOVED:
-        Expect.isTrue(!generated.contains('ioore'));
+        Expect.isFalse(generated.contains('ioore'));
         break;
 
       case ABOVE_ZERO:
-        Expect.isTrue(!generated.contains('< 0'));
+        Expect.isFalse(generated.contains('< 0') || generated.contains('>= 0'));
         Expect.isTrue(generated.contains('ioore'));
         break;
 
       case BELOW_ZERO_CHECK:
-        Expect.isTrue(generated.contains('< 0'));
-        Expect.isTrue(!generated.contains('||'));
+        // May generate `!(ix < 0)` or `ix >= 0` depending if `ix` can be NaN
+        Expect.isTrue(generated.contains('< 0') || generated.contains('>= 0'));
+        Expect.isFalse(generated.contains('||') || generated.contains('&&'));
         Expect.isTrue(generated.contains('ioore'));
         break;
 
       case BELOW_LENGTH:
-        Expect.isTrue(!generated.contains('||'));
+        Expect.isFalse(generated.contains('||') || generated.contains('&&'));
         Expect.isTrue(generated.contains('ioore'));
         break;
 
@@ -272,13 +273,13 @@
         break;
 
       case ONE_CHECK:
-        RegExp regexp = new RegExp('ioore');
+        RegExp regexp = RegExp('ioore');
         Iterator matches = regexp.allMatches(generated).iterator;
         checkNumberOfMatches(matches, 1);
         break;
 
       case ONE_ZERO_CHECK:
-        RegExp regexp = new RegExp('< 0|>>> 0 !==');
+        RegExp regexp = RegExp('< 0|>>> 0 !==');
         Iterator matches = regexp.allMatches(generated).iterator;
         checkNumberOfMatches(matches, 1);
         break;
diff --git a/sdk/lib/_internal/js_dev_runtime/private/js_array.dart b/sdk/lib/_internal/js_dev_runtime/private/js_array.dart
index 3076131..3fe9b22 100644
--- a/sdk/lib/_internal/js_dev_runtime/private/js_array.dart
+++ b/sdk/lib/_internal/js_dev_runtime/private/js_array.dart
@@ -576,8 +576,8 @@
   E operator [](int index) {
     // Suppress redundant null checks via JS.
     if (index == null ||
-        JS<int>('!', '#', index) >= JS<int>('!', '#.length', this) ||
-        JS<int>('!', '#', index) < 0) {
+        // This form of the range check correctly rejects NaN.
+        JS<bool>('!', '!(# >= 0 && # < #.length)', index, index, this)) {
       throw diagnoseIndexError(this, index);
     }
     return JS<E>('', '#[#]', this, index);
@@ -586,8 +586,8 @@
   void operator []=(int index, E value) {
     checkMutable('indexed set');
     if (index == null ||
-        JS<int>('!', '#', index) >= JS<int>('!', '#.length', this) ||
-        JS<int>('!', '#', index) < 0) {
+        // This form of the range check correctly rejects NaN.
+        JS<bool>('!', '!(# >= 0 && # < #.length)', index, index, this)) {
       throw diagnoseIndexError(this, index);
     }
     JS('void', r'#[#] = #', this, index, value);
diff --git a/sdk/lib/_internal/js_dev_runtime/private/js_string.dart b/sdk/lib/_internal/js_dev_runtime/private/js_string.dart
index 985c02d..09b54dc 100644
--- a/sdk/lib/_internal/js_dev_runtime/private/js_string.dart
+++ b/sdk/lib/_internal/js_dev_runtime/private/js_string.dart
@@ -458,7 +458,11 @@
 
   @notNull
   int compareTo(@nullCheck String other) {
-    return this == other ? 0 : JS<bool>('!', r'# < #', this, other) ? -1 : 1;
+    return this == other
+        ? 0
+        : JS<bool>('!', r'# < #', this, other)
+            ? -1
+            : 1;
   }
 
   // Note: if you change this, also change the function [S].
@@ -495,7 +499,8 @@
 
   @notNull
   String operator [](@nullCheck int index) {
-    if (index >= JS<int>('!', '#.length', this) || index < 0) {
+    // This form of the range check correctly rejects NaN.
+    if (JS<bool>('!', '!(# >= 0 && # < #.length)', index, index, this)) {
       throw diagnoseIndexError(this, index);
     }
     return JS<String>('!', '#[#]', this, index);
diff --git a/sdk/lib/_internal/js_runtime/lib/js_array.dart b/sdk/lib/_internal/js_runtime/lib/js_array.dart
index f0024ca..dde10ed 100644
--- a/sdk/lib/_internal/js_runtime/lib/js_array.dart
+++ b/sdk/lib/_internal/js_runtime/lib/js_array.dart
@@ -705,14 +705,16 @@
 
   E operator [](int index) {
     if (index is! int) throw diagnoseIndexError(this, index);
-    if (index >= length || index < 0) throw diagnoseIndexError(this, index);
+    // This form of the range test correctly rejects NaN.
+    if (!(index >= 0 && index < length)) throw diagnoseIndexError(this, index);
     return JS('', '#[#]', this, index);
   }
 
   void operator []=(int index, E value) {
     checkMutable('indexed set');
     if (index is! int) throw diagnoseIndexError(this, index);
-    if (index >= length || index < 0) throw diagnoseIndexError(this, index);
+    // This form of the range test correctly rejects NaN.
+    if (!(index >= 0 && index < length)) throw diagnoseIndexError(this, index);
     JS('void', r'#[#] = #', this, index, value);
   }
 
diff --git a/sdk/lib/_internal/js_runtime/lib/js_string.dart b/sdk/lib/_internal/js_runtime/lib/js_string.dart
index 6d25e5d..8c8dd55 100644
--- a/sdk/lib/_internal/js_runtime/lib/js_string.dart
+++ b/sdk/lib/_internal/js_runtime/lib/js_string.dart
@@ -431,7 +431,11 @@
 
   int compareTo(String other) {
     if (other is! String) throw argumentErrorValue(other);
-    return this == other ? 0 : JS('bool', r'# < #', this, other) ? -1 : 1;
+    return this == other
+        ? 0
+        : JS('bool', r'# < #', this, other)
+            ? -1
+            : 1;
   }
 
   // Note: if you change this, also change the function [S].
@@ -461,7 +465,8 @@
 
   String operator [](int index) {
     if (index is! int) throw diagnoseIndexError(this, index);
-    if (index >= length || index < 0) throw diagnoseIndexError(this, index);
+    // This form of the range test correctly rejects NaN.
+    if (!(index >= 0 && index < length)) throw diagnoseIndexError(this, index);
     return JS('String', '#[#]', this, index);
   }
 }
diff --git a/tests/web/nan_index_test.dart b/tests/web/nan_index_test.dart
new file mode 100644
index 0000000..639cfbd
--- /dev/null
+++ b/tests/web/nan_index_test.dart
@@ -0,0 +1,116 @@
+// 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 'dart:typed_data';
+
+import 'package:expect/expect.dart';
+
+// Check that NaN is detected as an invalid index for system Lists, Strings and
+// typed lists.
+//
+// There are several methods called `dynamicCallN` for various N that are called
+// with different indexable collection implementations to exercise various
+// dart2js optimizations based on knowing (or not knowing) the concrete type of
+// the List argument.
+
+void main() {
+  int nan = makeIntNaN();
+  Expect.isFalse(nan <= 0);
+  Expect.isFalse(nan >= 0);
+
+  List<int> ints = [1, 2, 3, 4];
+  final bytes = Uint8List(3)
+    ..[0] = 100
+    ..[1] = 101
+    ..[2] = 102;
+  final words = Int16List(3)
+    ..[0] = 16000
+    ..[1] = 16001
+    ..[2] = 16002;
+
+  Expect.throws(() => ints[nan], anyError, 'List[nan]');
+  Expect.throws(() => 'abc'[nan], anyError, 'String[nan]');
+  Expect.throws(() => bytes[nan], anyError, 'UInt8List[nan]');
+  Expect.throws(() => words[nan], anyError, 'Int16List[nan]');
+
+  // [dynamicCall1] Seeded with JSIndexable and Map, so is doing a complete
+  // interceptor dispatch.
+  Expect.equals(2, dynamicCall1(ints, 1));
+  Expect.equals('b', dynamicCall1('abc', 1));
+  Expect.equals(2, dynamicCall1({'a': 1, 'b': 2, 'c': 3}, 'b'));
+
+  Expect.throws(() => dynamicCall1(ints, nan), anyError, 'dynamic List');
+  Expect.throws(() => dynamicCall1('AB', nan), anyError, 'dynamic String');
+  Expect.throws(() => dynamicCall1(bytes, nan), anyError, 'dynamic Uint8List');
+  Expect.throws(() => dynamicCall1(words, nan), anyError, 'dynamic Int16list');
+
+  var a = <int>[];
+  Expect.throws(() => a.removeLast(), contains('-1'));
+
+  // [dynamicCall2] seeded with JSIndexable only, so can be optimized to a
+  // JavaScript indexing operation.
+  Expect.equals(2, dynamicCall2(ints, 1));
+  Expect.equals('b', dynamicCall2('abc', 1));
+
+  Expect.throws(() => dynamicCall2(ints, nan), anyError, 'JSIndexable List');
+  Expect.throws(() => dynamicCall2('AB', nan), anyError, 'JSIndexable String');
+
+  // [dynamicCall3] Seeded with List of known length only, various indexes. The
+  // upper bound is fixed.
+  Expect.throws(() => dynamicCall3(ints, nan), anyError, 'known length nan');
+  Expect.throws(() => dynamicCall3(ints, null), anyError, 'known length null');
+
+  // [dynamicCall4] Seeded with List of known length only.
+  Expect.throws(() => dynamicCall4(ints, nan), anyError, 'dynamic[] List');
+
+  // [dynamicCall5] Seeded with List of unknown length only.
+  Expect.throws(() => dynamicCall5(ints, nan), anyError, 'dynamic[] List');
+  Expect.throws(() => dynamicCall5(a, nan), anyError, 'dynamic[] List');
+
+  // [dynamicCall6] Seeded with Uint8List only.
+  Expect.throws(() => dynamicCall6(bytes, nan), anyError, 'dynamic Uint8List');
+}
+
+bool anyError(error) => true;
+
+bool Function(dynamic) contains(Pattern pattern) =>
+    (error) => '$error'.contains(pattern);
+
+@pragma('dart2js:noInline')
+dynamic dynamicCall1(dynamic indexable, dynamic index) {
+  return indexable[index];
+}
+
+@pragma('dart2js:noInline')
+dynamic dynamicCall2(dynamic indexable, dynamic index) {
+  return indexable[index];
+}
+
+@pragma('dart2js:noInline')
+dynamic dynamicCall3(dynamic indexable, dynamic index) {
+  return indexable[index];
+}
+
+@pragma('dart2js:noInline')
+dynamic dynamicCall4(dynamic indexable, dynamic index) {
+  return indexable[index];
+}
+
+@pragma('dart2js:noInline')
+dynamic dynamicCall5(dynamic indexable, dynamic index) {
+  return indexable[index];
+}
+
+@pragma('dart2js:noInline')
+dynamic dynamicCall6(dynamic indexable, dynamic index) {
+  return indexable[index];
+}
+
+int makeIntNaN() {
+  int n = 2;
+  // Overflow to Infinity.
+  for (int i = 0; i < 10; i++, n *= n) {}
+  // Infinity - Infinity = NaN.
+  return n - n;
+}
diff --git a/tools/VERSION b/tools/VERSION
index 99ba736..a3f7804 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
 MAJOR 2
 MINOR 15
 PATCH 0
-PRERELEASE 62
+PRERELEASE 63
 PRERELEASE_PATCH 0
\ No newline at end of file