mirror of
https://github.com/dart-lang/sdk
synced 2024-11-05 18:22:09 +00:00
5c582f82f0
`int` variables can attain NaN values because web int arithmetic implemented by JavaScript numbers (doubles) is not closed under many operations. It is possible get NaN using only addition: int a = 1, b = -1; while (a + a != a) { a += a; b += b; } int nan = a + b; On the VM, a, b and nan are all zero. On the web, a, b and nan are Infinity, -Infinity and NaN, respectively. Since NaN can leak into int arithmetic, is it helpful if bounds checks catch NaN indexes. NaN compares false in any comparison, so a test of the form if (index < 0 || index >= a.length) throw ioore(a, index); fails to detect a NaN value of `index`. This is fixed by negating the comparisons, and applying De Morgan's law: if (!(index >= 0 && index < a.length)) throw ioore(a, index); These changes have been applied to JSArray.[], JSArray.[]= and String.[] For dart2js the change is a little more involved. Primitive indexing is lowered to code with a HBoundsCheck check instruction. The code generated for the instruction now uses, e.g. `!(i>=0)` instead of `i<0`. This leads to a small code size regression. There is no regression at -O4 since bounds checks are omitted at -O4. At -O3 (where the regression is largest) the regression is 0.01% for cm 0.06% for flutter gallery -- array-heavy diff and layout 0.21% for Meteor -- array-heavy code 0.30% for Box2DOctane -- array-heavy code I believe the regression can be largely alleviated by determining if NaN is impossible at the index check, and if so, reverting to the smaller code pattern. The analysis could be global, incorporating NaN into the global abstract value domain, or a much simpler a local dataflow analysis. Many indexes are loop driven and cannot reach infinity because they are incremented by a small bump and eventually (even without a loop guard) the index would stop growing when the increment falls below the rounding error in O(2^53) iterations. Change-Id: I23ab1eb779f1d0c9c6655e13d69f65d453db9284 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/210321 Commit-Queue: Stephen Adams <sra@google.com> Reviewed-by: Mayank Patke <fishythefish@google.com>
116 lines
3.8 KiB
Dart
116 lines
3.8 KiB
Dart
// 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;
|
|
}
|