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