ES6: Array.prototype.slice and friends should use ToLength instead of ToUint32

Defines a new --harmony-tolength flag, and a ToLengthFlagged() runtime function,
that is used where ES6 requires ToLength(), but a pre-ES6 conversion existed
before. When the flag is disabled, the function uses TO_UINT32(), which is
the pre-ES6 behaviour. When the flag enabled, the ES6-compliant ToLength()
conversion is used.

Based on a patch initially from Diego Pino <dpino@igalia.com>

BUG=v8:3087
LOG=Y

Review URL: https://codereview.chromium.org/1309243003

Cr-Commit-Position: refs/heads/master@{#30772}
This commit is contained in:
aperez 2015-09-16 11:01:38 -07:00 committed by Commit bot
parent 0d017282d3
commit d4e1299f16
8 changed files with 246 additions and 23 deletions

View File

@ -395,7 +395,7 @@ function ArrayToString() {
function InnerArrayToLocaleString(array, length) {
var len = TO_UINT32(length);
var len = TO_LENGTH_OR_UINT32(length);
if (len === 0) return "";
return Join(array, len, ',', ConvertToLocaleString);
}
@ -434,7 +434,7 @@ function ArrayJoin(separator) {
CHECK_OBJECT_COERCIBLE(this, "Array.prototype.join");
var array = TO_OBJECT(this);
var length = TO_UINT32(array.length);
var length = TO_LENGTH_OR_UINT32(array.length);
return InnerArrayJoin(separator, array, length);
}
@ -463,7 +463,7 @@ function ArrayPop() {
CHECK_OBJECT_COERCIBLE(this, "Array.prototype.pop");
var array = TO_OBJECT(this);
var n = TO_UINT32(array.length);
var n = TO_LENGTH_OR_UINT32(array.length);
if (n == 0) {
array.length = n;
return;
@ -481,7 +481,7 @@ function ArrayPop() {
function ObservedArrayPush() {
var n = TO_UINT32(this.length);
var n = TO_LENGTH_OR_UINT32(this.length);
var m = %_ArgumentsLength();
try {
@ -509,7 +509,7 @@ function ArrayPush() {
return ObservedArrayPush.apply(this, arguments);
var array = TO_OBJECT(this);
var n = TO_UINT32(array.length);
var n = TO_LENGTH_OR_UINT32(array.length);
var m = %_ArgumentsLength();
for (var i = 0; i < m; i++) {
@ -606,7 +606,7 @@ function ArrayReverse() {
CHECK_OBJECT_COERCIBLE(this, "Array.prototype.reverse");
var array = TO_OBJECT(this);
var len = TO_UINT32(array.length);
var len = TO_LENGTH_OR_UINT32(array.length);
var isArray = IS_ARRAY(array);
if (UseSparseVariant(array, len, isArray, len)) {
@ -641,7 +641,7 @@ function ArrayShift() {
CHECK_OBJECT_COERCIBLE(this, "Array.prototype.shift");
var array = TO_OBJECT(this);
var len = TO_UINT32(array.length);
var len = TO_LENGTH_OR_UINT32(array.length);
if (len === 0) {
array.length = 0;
@ -668,7 +668,7 @@ function ArrayShift() {
function ObservedArrayUnshift() {
var len = TO_UINT32(this.length);
var len = TO_LENGTH_OR_UINT32(this.length);
var num_arguments = %_ArgumentsLength();
try {
@ -695,7 +695,7 @@ function ArrayUnshift(arg1) { // length == 1
return ObservedArrayUnshift.apply(this, arguments);
var array = TO_OBJECT(this);
var len = TO_UINT32(array.length);
var len = TO_LENGTH_OR_UINT32(array.length);
var num_arguments = %_ArgumentsLength();
if (len > 0 && UseSparseVariant(array, len, IS_ARRAY(array), len) &&
@ -719,7 +719,7 @@ function ArraySlice(start, end) {
CHECK_OBJECT_COERCIBLE(this, "Array.prototype.slice");
var array = TO_OBJECT(this);
var len = TO_UINT32(array.length);
var len = TO_LENGTH_OR_UINT32(array.length);
var start_i = TO_INTEGER(start);
var end_i = len;
@ -790,7 +790,7 @@ function ComputeSpliceDeleteCount(delete_count, num_arguments, len, start_i) {
function ObservedArraySplice(start, delete_count) {
var num_arguments = %_ArgumentsLength();
var len = TO_UINT32(this.length);
var len = TO_LENGTH_OR_UINT32(this.length);
var start_i = ComputeSpliceStartIndex(TO_INTEGER(start), len);
var del_count = ComputeSpliceDeleteCount(delete_count, num_arguments, len,
start_i);
@ -837,7 +837,7 @@ function ArraySplice(start, delete_count) {
var num_arguments = %_ArgumentsLength();
var array = TO_OBJECT(this);
var len = TO_UINT32(array.length);
var len = TO_LENGTH_OR_UINT32(array.length);
var start_i = ComputeSpliceStartIndex(TO_INTEGER(start), len);
var del_count = ComputeSpliceDeleteCount(delete_count, num_arguments, len,
start_i);
@ -1173,7 +1173,7 @@ function ArraySort(comparefn) {
CHECK_OBJECT_COERCIBLE(this, "Array.prototype.sort");
var array = TO_OBJECT(this);
var length = TO_UINT32(array.length);
var length = TO_LENGTH_OR_UINT32(array.length);
return InnerArraySort(array, length, comparefn);
}
@ -1207,7 +1207,7 @@ function ArrayFilter(f, receiver) {
// Pull out the length so that modifications to the length in the
// loop will not affect the looping and side effects are visible.
var array = TO_OBJECT(this);
var length = TO_UINT32(array.length);
var length = TO_LENGTH_OR_UINT32(array.length);
var accumulator = InnerArrayFilter(f, receiver, array, length);
var result = new GlobalArray();
%MoveArrayContents(accumulator, result);
@ -1235,7 +1235,7 @@ function ArrayForEach(f, receiver) {
// Pull out the length so that modifications to the length in the
// loop will not affect the looping and side effects are visible.
var array = TO_OBJECT(this);
var length = TO_UINT32(array.length);
var length = TO_LENGTH_OR_UINT32(array.length);
InnerArrayForEach(f, receiver, array, length);
}
@ -1265,7 +1265,7 @@ function ArraySome(f, receiver) {
// Pull out the length so that modifications to the length in the
// loop will not affect the looping and side effects are visible.
var array = TO_OBJECT(this);
var length = TO_UINT32(array.length);
var length = TO_LENGTH_OR_UINT32(array.length);
return InnerArraySome(f, receiver, array, length);
}
@ -1292,7 +1292,7 @@ function ArrayEvery(f, receiver) {
// Pull out the length so that modifications to the length in the
// loop will not affect the looping and side effects are visible.
var array = TO_OBJECT(this);
var length = TO_UINT32(array.length);
var length = TO_LENGTH_OR_UINT32(array.length);
return InnerArrayEvery(f, receiver, array, length);
}
@ -1321,7 +1321,7 @@ function ArrayMap(f, receiver) {
// Pull out the length so that modifications to the length in the
// loop will not affect the looping and side effects are visible.
var array = TO_OBJECT(this);
var length = TO_UINT32(array.length);
var length = TO_LENGTH_OR_UINT32(array.length);
var accumulator = InnerArrayMap(f, receiver, array, length);
var result = new GlobalArray();
%MoveArrayContents(accumulator, result);
@ -1390,7 +1390,7 @@ function InnerArrayIndexOf(array, element, index, length) {
function ArrayIndexOf(element, index) {
CHECK_OBJECT_COERCIBLE(this, "Array.prototype.indexOf");
var length = TO_UINT32(this.length);
var length = TO_LENGTH_OR_UINT32(this.length);
return InnerArrayIndexOf(this, element, index, length);
}
@ -1448,7 +1448,7 @@ function InnerArrayLastIndexOf(array, element, index, length, argumentsLength) {
function ArrayLastIndexOf(element, index) {
CHECK_OBJECT_COERCIBLE(this, "Array.prototype.lastIndexOf");
var length = TO_UINT32(this.length);
var length = TO_LENGTH_OR_UINT32(this.length);
return InnerArrayLastIndexOf(this, element, index, length,
%_ArgumentsLength());
}
@ -1490,7 +1490,7 @@ function ArrayReduce(callback, current) {
// Pull out the length so that modifications to the length in the
// loop will not affect the looping and side effects are visible.
var array = TO_OBJECT(this);
var length = TO_UINT32(array.length);
var length = TO_LENGTH_OR_UINT32(array.length);
return InnerArrayReduce(callback, current, array, length,
%_ArgumentsLength());
}
@ -1533,7 +1533,7 @@ function ArrayReduceRight(callback, current) {
// Pull out the length so that side effects are visible before the
// callback function is checked.
var array = TO_OBJECT(this);
var length = TO_UINT32(array.length);
var length = TO_LENGTH_OR_UINT32(array.length);
return InnerArrayReduceRight(callback, current, array, length,
%_ArgumentsLength());
}

View File

@ -1860,6 +1860,15 @@ EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_unicode_regexps)
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_tostring)
void Genesis::InitializeGlobal_harmony_tolength() {
Handle<JSObject> builtins(native_context()->builtins());
Handle<Object> flag(factory()->ToBoolean(FLAG_harmony_tolength));
Runtime::SetObjectProperty(isolate(), builtins,
factory()->harmony_tolength_string(), flag,
STRICT).Assert();
}
void Genesis::InitializeGlobal_harmony_reflect() {
if (!FLAG_harmony_reflect) return;
@ -2542,6 +2551,7 @@ bool Genesis::InstallExperimentalNatives() {
"native harmony-concat-spreadable.js", nullptr};
static const char* harmony_simd_natives[] = {"native harmony-simd.js",
nullptr};
static const char* harmony_tolength_natives[] = {nullptr};
for (int i = ExperimentalNatives::GetDebuggerCount();
i < ExperimentalNatives::GetBuiltinsCount(); i++) {

View File

@ -194,6 +194,7 @@ DEFINE_BOOL(legacy_const, true, "legacy semantics for const in sloppy mode")
V(harmony_proxies, "harmony proxies") \
V(harmony_sloppy_function, "harmony sloppy function block scoping") \
V(harmony_unicode_regexps, "harmony unicode regexps") \
V(harmony_tolength, "harmony ToLength") \
V(harmony_reflect, "harmony Reflect API") \
V(harmony_destructuring, "harmony destructuring") \
V(harmony_default_parameters, "harmony default parameters") \

View File

@ -173,7 +173,7 @@ function ArrayFill(value, start, end) {
CHECK_OBJECT_COERCIBLE(this, "Array.prototype.fill");
var array = TO_OBJECT(this);
var length = TO_UINT32(array.length);
var length = TO_LENGTH_OR_UINT32(array.length);
return InnerArrayFill(value, start, end, array, length);
}

View File

@ -258,6 +258,7 @@ namespace internal {
V(multiline_string, "multiline") \
V(sticky_string, "sticky") \
V(unicode_string, "unicode") \
V(harmony_tolength_string, "harmony_tolength") \
V(input_string, "input") \
V(index_string, "index") \
V(last_index_string, "lastIndex") \

View File

@ -147,6 +147,7 @@ macro TO_INTEGER_FOR_SIDE_EFFECT(arg) = (%_IsSmi(%IS_VAR(arg)) ? arg : ToNumber(
macro TO_INTEGER_MAP_MINUS_ZERO(arg) = (%_IsSmi(%IS_VAR(arg)) ? arg : %NumberToIntegerMapMinusZero(ToNumber(arg)));
macro TO_INT32(arg) = (arg | 0);
macro TO_UINT32(arg) = (arg >>> 0);
macro TO_LENGTH_OR_UINT32(arg) = (harmony_tolength ? $toLength(arg) : TO_UINT32(arg));
macro TO_STRING_INLINE(arg) = (IS_STRING(%IS_VAR(arg)) ? arg : $nonStringToString(arg));
macro TO_NUMBER_INLINE(arg) = (IS_NUMBER(%IS_VAR(arg)) ? arg : $nonNumberToNumber(arg));
macro TO_OBJECT(arg) = (%_ToObject(arg));

View File

@ -24,6 +24,8 @@ var $toPositiveInteger;
var $toPrimitive;
var $toString;
var harmony_tolength = false;
(function(global, utils) {
%CheckIsBootstrapping();

View File

@ -0,0 +1,208 @@
// Copyright 2015 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --harmony-tolength
// Test array functions do not cause infinite loops when length is negative,
// max_value, etc.
// ArrayToString
var o = { length: Number.MIN_VALUE };
var result = Array.prototype.toString.call(o);
assertEquals("[object Object]", result);
// ArrayToLocaleString
var o = { length: Number.MIN_VALUE };
var result = Array.prototype.toLocaleString.call(o);
assertEquals("", result);
// ArrayJoin
var o = { length: Number.MIN_VALUE };
var result = Array.prototype.join.call(o);
assertEquals(0, result.length);
// ArrayPush
var o = { length: Number.MIN_VALUE };
Array.prototype.push.call(o, 1);
assertEquals(1, o.length);
assertEquals(1, o[0]);
var o = { length: Number.MAX_VALUE };
Array.prototype.push.call(o, 1);
assertEquals(o.length, Number.MAX_SAFE_INTEGER + 1);
assertEquals(1, o[Number.MAX_SAFE_INTEGER]);
Array.prototype.push.call(o, 2);
assertEquals(o.length, Number.MAX_SAFE_INTEGER + 1);
assertEquals(2, o[Number.MAX_SAFE_INTEGER]);
// ArrayPop
var o = { length: 0 };
Array.prototype.pop.call(o);
assertEquals(0, o.length);
var o = { length: Number.MIN_VALUE };
Array.prototype.pop.call(o);
assertEquals(0, o.length);
var o = { length: Number.MAX_VALUE };
Array.prototype.pop.call(o);
assertEquals(o.length, Number.MAX_SAFE_INTEGER - 1);
// ArrayReverse
var o = { 0: 'foo', length: Number.MIN_VALUE }
var result = Array.prototype.reverse.call(o);
assertEquals('object', typeof(result));
assertEquals(Number.MIN_VALUE, result.length);
assertEquals(Number.MIN_VALUE, o.length);
// ArrayShift
var o = { 0: "foo", length: Number.MIN_VALUE }
var result = Array.prototype.shift.call(o);
assertEquals(undefined, result);
assertEquals(0, o.length);
// ArrayUnshift
var o = { length: 0 };
Array.prototype.unshift.call(o);
assertEquals(0, o.length);
var o = { length: 0 };
Array.prototype.unshift.call(o, 'foo');
assertEquals('foo', o[0]);
assertEquals(1, o.length);
var o = { length: Number.MIN_VALUE };
Array.prototype.unshift.call(o);
assertEquals(0, o.length);
var o = { length: Number.MIN_VALUE };
Array.prototype.unshift.call(o, 'foo');
assertEquals('foo', o[0]);
assertEquals(1, o.length);
// ArraySplice
var o = { length: Number.MIN_VALUE };
Array.prototype.splice.call(o);
assertEquals(0, o.length);
var o = { length: Number.MIN_VALUE };
Array.prototype.splice.call(o, 0, 10, ['foo']);
assertEquals(['foo'], o[0]);
assertEquals(1, o.length);
var o = { length: Number.MIN_VALUE };
Array.prototype.splice.call(o, -1);
assertEquals(0, o.length);
var o = { length: Number.MAX_SAFE_INTEGER };
Array.prototype.splice.call(o, -1);
assertEquals(Number.MAX_SAFE_INTEGER - 1, o.length);
// ArraySlice
var o = { length: Number.MIN_VALUE };
var result = Array.prototype.slice.call(o);
assertEquals(0, result.length);
var o = { length: Number.MIN_VALUE };
var result = Array.prototype.slice.call(o, Number.MAX_VALUE);
assertEquals(0, result.length);
var o = { length: Number.MAX_VALUE };
var result = Array.prototype.slice.call(o, Number.MAX_VALUE - 1);
assertEquals(0, result.length);
// ArrayIndexOf
var o = { length: Number.MIN_VALUE };
var result = Array.prototype.indexOf.call(o);
assertEquals(-1, result);
var o = { length: Number.MAX_SAFE_INTEGER }
o[Number.MAX_SAFE_INTEGER - 1] = "foo"
var result = Array.prototype.indexOf.call(o,
"foo", Number.MAX_SAFE_INTEGER - 2);
assertEquals(Number.MAX_SAFE_INTEGER - 1, result);
var o = { length: Number.MAX_SAFE_INTEGER };
o[Number.MAX_SAFE_INTEGER - 1] = "foo";
var result = Array.prototype.indexOf.call(o, "foo", -1);
assertEquals(Number.MAX_SAFE_INTEGER - 1, result);
// ArrayLastIndexOf
var o = { length: Number.MIN_VALUE };
var result = Array.prototype.lastIndexOf.call(o);
assertEquals(-1, result);
var o = { length: Number.MAX_SAFE_INTEGER }
o[Number.MAX_SAFE_INTEGER - 1] = "foo"
var result = Array.prototype.lastIndexOf.call(o,
"foo", Number.MAX_SAFE_INTEGER);
assertEquals(Number.MAX_SAFE_INTEGER - 1, result);
var o = { length: Number.MAX_SAFE_INTEGER };
o[Number.MAX_SAFE_INTEGER - 1] = "foo";
var result = Array.prototype.lastIndexOf.call(o, "foo", -1);
assertEquals(Number.MAX_SAFE_INTEGER - 1, result);
// ArrayFilter
var func = function(v) { return v; }
var o = { length: Number.MIN_VALUE };
Array.prototype.filter.call(o, func);
assertEquals(Number.MIN_VALUE, o.length);
// ArrayForEach
var o = { length: Number.MIN_VALUE };
Array.prototype.forEach.call(o, func);
assertEquals(Number.MIN_VALUE, o.length);
// ArraySome
var o = { length: Number.MIN_VALUE };
Array.prototype.some.call(o, func);
assertEquals(Number.MIN_VALUE, o.length);
// ArrayEvery
var o = { length: Number.MIN_VALUE };
Array.prototype.every.call(o, func);
assertEquals(Number.MIN_VALUE, o.length);
// ArrayMap
var o = { length: Number.MIN_VALUE };
Array.prototype.map.call(o, func);
assertEquals(Number.MIN_VALUE, o.length);
// ArrayReduce
var o = { length: Number.MIN_VALUE };
Array.prototype.reduce.call(o, func, 0);
assertEquals(Number.MIN_VALUE, o.length);
// ArrayReduceRight
var o = { length: Number.MIN_VALUE };
Array.prototype.reduceRight.call(o, func, 0);
assertEquals(Number.MIN_VALUE, o.length);
// ArrayFill
var o = { length: Number.MIN_VALUE };
Array.prototype.fill(o, 0);
assertEquals(Number.MIN_VALUE, o.length);