From d4e1299f1686ff55f9425d6b322c194a1968fe3f Mon Sep 17 00:00:00 2001 From: aperez Date: Wed, 16 Sep 2015 11:01:38 -0700 Subject: [PATCH] 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 BUG=v8:3087 LOG=Y Review URL: https://codereview.chromium.org/1309243003 Cr-Commit-Position: refs/heads/master@{#30772} --- src/array.js | 44 +++--- src/bootstrapper.cc | 10 ++ src/flag-definitions.h | 1 + src/harmony-array.js | 2 +- src/heap/heap.h | 1 + src/macros.py | 1 + src/runtime.js | 2 + test/mjsunit/harmony/array-length.js | 208 +++++++++++++++++++++++++++ 8 files changed, 246 insertions(+), 23 deletions(-) create mode 100644 test/mjsunit/harmony/array-length.js diff --git a/src/array.js b/src/array.js index 0c4985ed86..94775cd633 100644 --- a/src/array.js +++ b/src/array.js @@ -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()); } diff --git a/src/bootstrapper.cc b/src/bootstrapper.cc index 45051024a5..10b2d028a8 100644 --- a/src/bootstrapper.cc +++ b/src/bootstrapper.cc @@ -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 builtins(native_context()->builtins()); + Handle 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++) { diff --git a/src/flag-definitions.h b/src/flag-definitions.h index da587cbc23..63b573871f 100644 --- a/src/flag-definitions.h +++ b/src/flag-definitions.h @@ -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") \ diff --git a/src/harmony-array.js b/src/harmony-array.js index 705adcd06a..50da5f9b1f 100644 --- a/src/harmony-array.js +++ b/src/harmony-array.js @@ -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); } diff --git a/src/heap/heap.h b/src/heap/heap.h index 366afd0bc0..3b6fd98da4 100644 --- a/src/heap/heap.h +++ b/src/heap/heap.h @@ -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") \ diff --git a/src/macros.py b/src/macros.py index 9f6a698a84..4290130d57 100644 --- a/src/macros.py +++ b/src/macros.py @@ -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)); diff --git a/src/runtime.js b/src/runtime.js index fcb9471cd4..39939b1602 100644 --- a/src/runtime.js +++ b/src/runtime.js @@ -24,6 +24,8 @@ var $toPositiveInteger; var $toPrimitive; var $toString; +var harmony_tolength = false; + (function(global, utils) { %CheckIsBootstrapping(); diff --git a/test/mjsunit/harmony/array-length.js b/test/mjsunit/harmony/array-length.js new file mode 100644 index 0000000000..df488196ff --- /dev/null +++ b/test/mjsunit/harmony/array-length.js @@ -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);