Skip to content

Commit 0d4361f

Browse files
committed
Better messages for optimized index errors.
Typed list, Array and String indexer methods detect index errors in their own optimized way but now defer to common code to diagnose the error. This code is shared with the optimized 'ioore' helper, which makes the error message consistent between optimized and unoptimized code. The main reason for doing this is to ensure that the error messages have as much information as possible to help debug off-line using captured error messages. BUG= R=lrn@google.com Review URL: https://codereview.chromium.org//1180713003.
1 parent fd1531b commit 0d4361f

10 files changed

Lines changed: 564 additions & 47 deletions

sdk/lib/_internal/compiler/js_lib/interceptors.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import 'dart:_js_helper' show allMatchesInStringUnchecked,
2020
checkNum,
2121
checkString,
2222
defineProperty,
23+
diagnoseIndexError,
2324
getRuntimeType,
2425
initNativeDispatch,
2526
initNativeDispatchFlag,

sdk/lib/_internal/compiler/js_lib/js_array.dart

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -575,21 +575,28 @@ class JSArray<E> extends Interceptor implements List<E>, JSIndexable {
575575

576576
void set length(int newLength) {
577577
checkGrowable('set length');
578-
if (newLength is !int) throw new ArgumentError(newLength);
579-
if (newLength < 0) throw new RangeError.value(newLength);
578+
if (newLength is !int) {
579+
throw new ArgumentError.value(newLength, 'newLength');
580+
}
581+
// TODO(sra): Remove this test and let JavaScript throw an error.
582+
if (newLength < 0) {
583+
throw new RangeError.range(newLength, 0, null, 'newLength');
584+
}
585+
// JavaScript with throw a RangeError for numbers that are too big. The
586+
// message does not contain the value.
580587
JS('void', r'#.length = #', this, newLength);
581588
}
582589

583590
E operator [](int index) {
584-
if (index is !int) throw new ArgumentError(index);
585-
if (index >= length || index < 0) throw new RangeError.value(index);
591+
if (index is !int) throw diagnoseIndexError(this, index);
592+
if (index >= length || index < 0) throw diagnoseIndexError(this, index);
586593
return JS('var', '#[#]', this, index);
587594
}
588595

589596
void operator []=(int index, E value) {
590597
checkMutable('indexed set');
591-
if (index is !int) throw new ArgumentError(index);
592-
if (index >= length || index < 0) throw new RangeError.value(index);
598+
if (index is !int) throw diagnoseIndexError(this, index);
599+
if (index >= length || index < 0) throw diagnoseIndexError(this, index);
593600
JS('void', r'#[#] = #', this, index, value);
594601
}
595602

sdk/lib/_internal/compiler/js_lib/js_helper.dart

Lines changed: 47 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1388,23 +1388,41 @@ class JsCache {
13881388
* for example, if a non-integer index is given to an optimized
13891389
* indexed access.
13901390
*/
1391+
@NoInline()
13911392
iae(argument) {
13921393
throw _argumentError(argument);
13931394
}
13941395

13951396
/**
1396-
* Called by generated code to throw an index-out-of-range exception,
1397-
* for example, if a bounds check fails in an optimized indexed
1398-
* access. This may also be called when the index is not an integer, in
1399-
* which case it throws an illegal-argument exception instead, like
1400-
* [iae], or when the receiver is null.
1397+
* Called by generated code to throw an index-out-of-range exception, for
1398+
* example, if a bounds check fails in an optimized indexed access. This may
1399+
* also be called when the index is not an integer, in which case it throws an
1400+
* illegal-argument exception instead, like [iae], or when the receiver is null.
14011401
*/
1402+
@NoInline()
14021403
ioore(receiver, index) {
14031404
if (receiver == null) receiver.length; // Force a NoSuchMethodError.
1404-
if (index is !int) iae(index);
1405-
throw new RangeError.value(index);
1405+
throw diagnoseIndexError(receiver, index);
1406+
}
1407+
1408+
/**
1409+
* Diagnoses an indexing error. Returns the ArgumentError or RangeError that
1410+
* describes the problem.
1411+
*/
1412+
@NoInline()
1413+
Error diagnoseIndexError(indexable, index) {
1414+
if (index is !int) return new ArgumentError.value(index, 'index');
1415+
int length = indexable.length;
1416+
// The following returns the same error that would be thrown by calling
1417+
// [RangeError.checkValidIndex] with no optional parameters provided.
1418+
if (index < 0 || index >= length) {
1419+
return new RangeError.index(index, indexable, 'index', null, length);
1420+
}
1421+
// The above should always match, but if it does not, use the following.
1422+
return new RangeError.value(index, 'index');
14061423
}
14071424

1425+
14081426
stringLastIndexOfUnchecked(receiver, element, start)
14091427
=> JS('int', r'#.lastIndexOf(#, #)', receiver, element, start);
14101428

@@ -2015,10 +2033,14 @@ unwrapException(ex) {
20152033
return new StackOverflowError();
20162034
}
20172035

2018-
// In general, a RangeError is thrown when trying to pass a number
2019-
// as an argument to a function that does not allow a range that
2020-
// includes that number.
2021-
return saveStackTrace(new ArgumentError());
2036+
// In general, a RangeError is thrown when trying to pass a number as an
2037+
// argument to a function that does not allow a range that includes that
2038+
// number. Translate to a Dart RangeError with the same message.
2039+
String message = tryStringifyException(ex);
2040+
if (message is String) {
2041+
message = JS('String', r'#.replace(/^RangeError:\s*/, "")', message);
2042+
}
2043+
return saveStackTrace(new RangeError(message));
20222044
}
20232045

20242046
// Check for the Firefox specific stack overflow signal.
@@ -2036,6 +2058,20 @@ unwrapException(ex) {
20362058
return ex;
20372059
}
20382060

2061+
String tryStringifyException(ex) {
2062+
// Since this function is called from [unwrapException] which is called from
2063+
// code injected into a catch-clause, use JavaScript try-catch to avoid a
2064+
// potential loop if stringifying crashes.
2065+
return JS('String', r'''
2066+
(function(ex) {
2067+
try {
2068+
return String(ex);
2069+
} catch (e) {}
2070+
return null;
2071+
})(#)
2072+
''', ex);
2073+
}
2074+
20392075
/**
20402076
* Called by generated code to fetch the stack trace from an
20412077
* exception. Should never return null.

sdk/lib/_internal/compiler/js_lib/js_string.dart

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ class JSString extends Interceptor implements String, JSIndexable {
1414
const JSString();
1515

1616
int codeUnitAt(int index) {
17-
if (index is !int) throw new ArgumentError(index);
18-
if (index < 0) throw new RangeError.value(index);
19-
if (index >= length) throw new RangeError.value(index);
17+
if (index is !int) throw diagnoseIndexError(this, index);
18+
if (index < 0) throw diagnoseIndexError(this, index);
19+
if (index >= length) throw diagnoseIndexError(this, index);
2020
return JS('JSUInt31', r'#.charCodeAt(#)', this, index);
2121
}
2222

@@ -44,7 +44,7 @@ class JSString extends Interceptor implements String, JSIndexable {
4444
}
4545

4646
String operator +(String other) {
47-
if (other is !String) throw new ArgumentError(other);
47+
if (other is !String) throw new ArgumentError.value(other);
4848
return JS('String', r'# + #', this, other);
4949
}
5050

@@ -469,8 +469,8 @@ class JSString extends Interceptor implements String, JSIndexable {
469469
int get length => JS('int', r'#.length', this);
470470

471471
String operator [](int index) {
472-
if (index is !int) throw new ArgumentError(index);
473-
if (index >= length || index < 0) throw new RangeError.value(index);
472+
if (index is !int) throw diagnoseIndexError(this, index);
473+
if (index >= length || index < 0) throw diagnoseIndexError(this, index);
474474
return JS('String', '#[#]', this, index);
475475
}
476476
}

sdk/lib/_internal/compiler/js_lib/native_typed_data.dart

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@ library dart.typed_data.implementation;
1111
import 'dart:collection';
1212
import 'dart:_internal';
1313
import 'dart:_interceptors' show JSIndexable, JSUInt32, JSUInt31;
14-
import 'dart:_js_helper'
15-
show Creates, JavaScriptIndexingBehavior, JSName, Native, Null, Returns;
14+
import 'dart:_js_helper' show
15+
Creates, JavaScriptIndexingBehavior, JSName, Native, Null, Returns,
16+
diagnoseIndexError;
1617
import 'dart:_foreign_helper' show JS;
1718
import 'dart:math' as Math;
1819

@@ -445,35 +446,34 @@ class NativeTypedData implements TypedData {
445446
@JSName('BYTES_PER_ELEMENT')
446447
final int elementSizeInBytes;
447448

448-
void _invalidIndex(int index, int length) {
449-
if (index < 0 || index >= length) {
450-
if (this is List) {
451-
var list = this; // Typed as dynamic to avoid warning.
452-
if (length == list.length) {
453-
throw new RangeError.index(index, this);
454-
}
455-
}
456-
throw new RangeError.range(index, 0, length - 1);
449+
void _checkIndex(int index, int length) {
450+
if (JS('bool', '(# >>> 0) !== #', index, index) ||
451+
JS('int', '#', index) >= length) { // 'int' guaranteed by above test.
452+
throw diagnoseIndexError(this, index);
453+
}
454+
}
455+
456+
void _invalidPosition(int position, int length) {
457+
if (position is !int) {
458+
throw new ArgumentError.value(position, null, 'Invalid list position');
457459
} else {
458-
throw new ArgumentError('Invalid list index $index');
460+
throw new RangeError.range(position, 0, length);
459461
}
460462
}
461463

462-
void _checkIndex(int index, int length) {
463-
if (JS('bool', '(# >>> 0) !== #', index, index) ||
464-
JS('int', '#', index) >= length) { // 'int' guaranteed by above test.
465-
_invalidIndex(index, length);
464+
void _checkPosition(int position, int length) {
465+
if (JS('bool', '(# >>> 0) !== #', position, position) ||
466+
JS('int', '#', position) > length) { // 'int' guaranteed by above test.
467+
_invalidPosition(position, length);
466468
}
467469
}
468470

469471
int _checkSublistArguments(int start, int end, int length) {
470472
// For `sublist` the [start] and [end] indices are allowed to be equal to
471-
// [length]. However, [_checkIndex] only allows indices in the range
472-
// 0 .. length - 1. We therefore increment the [length] argument by one
473-
// for the [_checkIndex] checks.
474-
_checkIndex(start, length + 1);
473+
// [length].
474+
_checkPosition(start, length);
475475
if (end == null) return length;
476-
_checkIndex(end, length + 1);
476+
_checkPosition(end, length);
477477
if (start > end) throw new RangeError.range(start, 0, end);
478478
return end;
479479
}
@@ -862,8 +862,8 @@ abstract class NativeTypedArray extends NativeTypedData
862862
void _setRangeFast(int start, int end,
863863
NativeTypedArray source, int skipCount) {
864864
int targetLength = this.length;
865-
_checkIndex(start, targetLength + 1);
866-
_checkIndex(end, targetLength + 1);
865+
_checkPosition(start, targetLength);
866+
_checkPosition(end, targetLength);
867867
if (start > end) throw new RangeError.range(start, 0, end);
868868
int count = end - start;
869869

sdk/lib/core/errors.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,8 @@ class RangeError extends ArgumentError {
282282
static void checkValidIndex(int index, var indexable,
283283
[String name, int length, String message]) {
284284
if (length == null) length = indexable.length;
285-
if (index < 0 || index >= length) {
285+
// Comparing with `0` as receiver produces better dart2js type inference.
286+
if (0 > index || index >= length) {
286287
if (name == null) name = "index";
287288
throw new RangeError.index(index, indexable, name, message, length);
288289
}

0 commit comments

Comments
 (0)