template <class ExternalArrayClass, class ElementType>
-static void IterateExternalArrayElements(Isolate* isolate,
- Handle<JSObject> receiver,
- bool elements_are_ints,
- bool elements_are_guaranteed_smis,
- ArrayConcatVisitor* visitor) {
+static void IterateTypedArrayElements(Isolate* isolate,
+ Handle<JSObject> receiver,
+ bool elements_are_ints,
+ bool elements_are_guaranteed_smis,
+ ArrayConcatVisitor* visitor) {
Handle<ExternalArrayClass> array(
ExternalArrayClass::cast(receiver->elements()));
uint32_t len = static_cast<uint32_t>(array->length());
/**
- * A helper function that visits elements of a JSArray in numerical
+ * A helper function that visits elements of a JSObject in numerical
* order.
*
* The visitor argument called for each existing element in the array
* length.
* Returns false if any access threw an exception, otherwise true.
*/
-static bool IterateElements(Isolate* isolate, Handle<JSArray> receiver,
+static bool IterateElements(Isolate* isolate, Handle<JSObject> receiver,
ArrayConcatVisitor* visitor) {
- uint32_t length = static_cast<uint32_t>(receiver->length()->Number());
+ uint32_t length = 0;
+
+ if (receiver->IsJSArray()) {
+ Handle<JSArray> array(Handle<JSArray>::cast(receiver));
+ length = static_cast<uint32_t>(array->length()->Number());
+ } else {
+ Handle<Object> val;
+ Handle<Object> key(isolate->heap()->length_string(), isolate);
+ ASSIGN_RETURN_ON_EXCEPTION_VALUE(isolate, val,
+ Runtime::GetObjectProperty(isolate, receiver, key), false);
+ // TODO(caitp): implement ToLength() abstract operation for C++
+ val->ToUint32(&length);
+ }
+
switch (receiver->GetElementsKind()) {
case FAST_SMI_ELEMENTS:
case FAST_ELEMENTS:
}
break;
}
+ case UINT8_CLAMPED_ELEMENTS: {
+ Handle<FixedUint8ClampedArray> pixels(
+ FixedUint8ClampedArray::cast(receiver->elements()));
+ for (uint32_t j = 0; j < length; j++) {
+ Handle<Smi> e(Smi::FromInt(pixels->get_scalar(j)), isolate);
+ visitor->visit(j, e);
+ }
+ break;
+ }
case EXTERNAL_INT8_ELEMENTS: {
- IterateExternalArrayElements<ExternalInt8Array, int8_t>(
+ IterateTypedArrayElements<ExternalInt8Array, int8_t>(
isolate, receiver, true, true, visitor);
break;
}
+ case INT8_ELEMENTS: {
+ IterateTypedArrayElements<FixedInt8Array, int8_t>(
+ isolate, receiver, true, true, visitor);
+ break;
+ }
case EXTERNAL_UINT8_ELEMENTS: {
- IterateExternalArrayElements<ExternalUint8Array, uint8_t>(
+ IterateTypedArrayElements<ExternalUint8Array, uint8_t>(
isolate, receiver, true, true, visitor);
break;
}
+ case UINT8_ELEMENTS: {
+ IterateTypedArrayElements<FixedUint8Array, uint8_t>(
+ isolate, receiver, true, true, visitor);
+ break;
+ }
case EXTERNAL_INT16_ELEMENTS: {
- IterateExternalArrayElements<ExternalInt16Array, int16_t>(
+ IterateTypedArrayElements<ExternalInt16Array, int16_t>(
isolate, receiver, true, true, visitor);
break;
}
+ case INT16_ELEMENTS: {
+ IterateTypedArrayElements<FixedInt16Array, int16_t>(
+ isolate, receiver, true, true, visitor);
+ break;
+ }
case EXTERNAL_UINT16_ELEMENTS: {
- IterateExternalArrayElements<ExternalUint16Array, uint16_t>(
+ IterateTypedArrayElements<ExternalUint16Array, uint16_t>(
isolate, receiver, true, true, visitor);
break;
}
+ case UINT16_ELEMENTS: {
+ IterateTypedArrayElements<FixedUint16Array, uint16_t>(
+ isolate, receiver, true, true, visitor);
+ break;
+ }
case EXTERNAL_INT32_ELEMENTS: {
- IterateExternalArrayElements<ExternalInt32Array, int32_t>(
+ IterateTypedArrayElements<ExternalInt32Array, int32_t>(
isolate, receiver, true, false, visitor);
break;
}
+ case INT32_ELEMENTS: {
+ IterateTypedArrayElements<FixedInt32Array, int32_t>(
+ isolate, receiver, true, false, visitor);
+ break;
+ }
case EXTERNAL_UINT32_ELEMENTS: {
- IterateExternalArrayElements<ExternalUint32Array, uint32_t>(
+ IterateTypedArrayElements<ExternalUint32Array, uint32_t>(
isolate, receiver, true, false, visitor);
break;
}
+ case UINT32_ELEMENTS: {
+ IterateTypedArrayElements<FixedUint32Array, uint32_t>(
+ isolate, receiver, true, false, visitor);
+ break;
+ }
case EXTERNAL_FLOAT32_ELEMENTS: {
- IterateExternalArrayElements<ExternalFloat32Array, float>(
+ IterateTypedArrayElements<ExternalFloat32Array, float>(
isolate, receiver, false, false, visitor);
break;
}
+ case FLOAT32_ELEMENTS: {
+ IterateTypedArrayElements<FixedFloat32Array, float>(
+ isolate, receiver, false, false, visitor);
+ break;
+ }
case EXTERNAL_FLOAT64_ELEMENTS: {
- IterateExternalArrayElements<ExternalFloat64Array, double>(
+ IterateTypedArrayElements<ExternalFloat64Array, double>(
isolate, receiver, false, false, visitor);
break;
}
- default:
- UNREACHABLE();
+ case FLOAT64_ELEMENTS: {
+ IterateTypedArrayElements<FixedFloat64Array, double>(
+ isolate, receiver, false, false, visitor);
break;
+ }
+ case SLOPPY_ARGUMENTS_ELEMENTS: {
+ ElementsAccessor* accessor = receiver->GetElementsAccessor();
+ for (uint32_t index = 0; index < length; index++) {
+ HandleScope loop_scope(isolate);
+ if (accessor->HasElement(receiver, receiver, index)) {
+ Handle<Object> element;
+ ASSIGN_RETURN_ON_EXCEPTION_VALUE(
+ isolate, element, accessor->Get(receiver, receiver, index),
+ false);
+ visitor->visit(index, element);
+ }
+ }
+ break;
+ }
}
visitor->increase_index_offset(length);
return true;
}
+static bool IsConcatSpreadable(Isolate* isolate, Handle<Object> obj) {
+ HandleScope handle_scope(isolate);
+ if (!obj->IsSpecObject()) return false;
+ if (obj->IsJSArray()) return true;
+ if (FLAG_harmony_arrays) {
+ Handle<Symbol> key(isolate->factory()->is_concat_spreadable_symbol());
+ Handle<Object> value;
+ MaybeHandle<Object> maybeValue =
+ i::Runtime::GetObjectProperty(isolate, obj, key);
+ if (maybeValue.ToHandle(&value)) {
+ return value->BooleanValue();
+ }
+ }
+ return false;
+}
+
+
/**
* Array::concat implementation.
* See ECMAScript 262, 15.4.4.4.
for (int i = 0; i < argument_count; i++) {
Handle<Object> obj(elements->get(i), isolate);
- if (obj->IsJSArray()) {
- Handle<JSArray> array = Handle<JSArray>::cast(obj);
- if (!IterateElements(isolate, array, &visitor)) {
+ bool spreadable = IsConcatSpreadable(isolate, obj);
+ if (isolate->has_pending_exception()) return isolate->heap()->exception();
+ if (spreadable) {
+ Handle<JSObject> object = Handle<JSObject>::cast(obj);
+ if (!IterateElements(isolate, object, &visitor)) {
return isolate->heap()->exception();
}
} else {
--- /dev/null
+// Copyright 2014 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-arrays --harmony-classes
+
+(function testArrayConcatArity() {
+ "use strict";
+ assertEquals(1, Array.prototype.concat.length);
+})();
+
+
+(function testArrayConcatNoPrototype() {
+ "use strict";
+ assertEquals(void 0, Array.prototype.concat.prototype);
+})();
+
+
+(function testArrayConcatDescriptor() {
+ "use strict";
+ var desc = Object.getOwnPropertyDescriptor(Array.prototype, 'concat');
+ assertEquals(false, desc.enumerable);
+})();
+
+
+(function testConcatArrayLike() {
+ "use strict";
+ var obj = {
+ "length": 6,
+ "1": "A",
+ "3": "B",
+ "5": "C"
+ };
+ obj[Symbol.isConcatSpreadable] = true;
+ var obj2 = { length: 3, "0": "0", "1": "1", "2": "2" };
+ var arr = ["X", "Y", "Z"];
+ assertEquals([void 0, "A", void 0, "B", void 0, "C",
+ { "length": 3, "0": "0", "1": "1", "2": "2" },
+ "X", "Y", "Z"], Array.prototype.concat.call(obj, obj2, arr));
+})();
+
+
+(function testConcatHoleyArray() {
+ "use strict";
+ var arr = [];
+ arr[4] = "Item 4";
+ arr[8] = "Item 8";
+ var arr2 = [".", "!", "?"];
+ assertEquals([void 0, void 0, void 0, void 0, "Item 4", void 0, void 0,
+ void 0, "Item 8", ".", "!", "?"], arr.concat(arr2));
+})();
+
+
+(function testIsConcatSpreadableGetterThrows() {
+ "use strict";
+ function MyError() {}
+ var obj = {};
+ Object.defineProperty(obj, Symbol.isConcatSpreadable, {
+ get: function() { throw new MyError(); }
+ });
+
+ assertThrows(function() {
+ [].concat(obj);
+ }, MyError);
+
+ assertThrows(function() {
+ Array.prototype.concat.call(obj, 1, 2, 3);
+ }, MyError);
+})();
+
+
+(function testConcatLengthThrows() {
+ "use strict";
+ function MyError() {}
+ var obj = {};
+ obj[Symbol.isConcatSpreadable] = true;
+ Object.defineProperty(obj, "length", {
+ get: function() { throw new MyError(); }
+ });
+
+ assertThrows(function() {
+ [].concat(obj);
+ }, MyError);
+
+ assertThrows(function() {
+ Array.prototype.concat.call(obj, 1, 2, 3);
+ }, MyError);
+})();
+
+
+(function testConcatArraySubclass() {
+ "use strict";
+ // TODO(caitp): when concat is called on instances of classes which extend
+ // Array, they should:
+ //
+ // - return an instance of the class, rather than an Array instance (if from
+ // same Realm)
+ // - always treat such classes as concat-spreadable
+})();
+
+
+(function testConcatNonArray() {
+ "use strict";
+ class NonArray {
+ constructor() { Array.apply(this, arguments); }
+ };
+
+ var obj = new NonArray(1,2,3);
+ var result = Array.prototype.concat.call(obj, 4, 5, 6);
+ assertEquals(Array, result.constructor);
+ assertEquals([obj,4,5,6], result);
+ assertFalse(result instanceof NonArray);
+})();
+
+
+function testConcatTypedArray(type, elems, modulo) {
+ "use strict";
+ var items = new Array(elems);
+ var ta_by_len = new type(elems);
+ for (var i = 0; i < elems; ++i) {
+ ta_by_len[i] = items[i] = modulo === false ? i : elems % modulo;
+ }
+ var ta = new type(items);
+ assertEquals([ta, ta], [].concat(ta, ta));
+ ta[Symbol.isConcatSpreadable] = true;
+ assertEquals(items, [].concat(ta));
+
+ assertEquals([ta_by_len, ta_by_len], [].concat(ta_by_len, ta_by_len));
+ ta_by_len[Symbol.isConcatSpreadable] = true;
+ assertEquals(items, [].concat(ta_by_len));
+}
+
+(function testConcatSmallTypedArray() {
+ var max = [2^8, 2^16, 2^32, false, false];
+ [
+ Uint8Array,
+ Uint16Array,
+ Uint32Array,
+ Float32Array,
+ Float64Array
+ ].forEach(function(ctor, i) {
+ testConcatTypedArray(ctor, 1, max[i]);
+ });
+})();
+
+
+(function testConcatLargeTypedArray() {
+ var max = [2^8, 2^16, 2^32, false, false];
+ [
+ Uint8Array,
+ Uint16Array,
+ Uint32Array,
+ Float32Array,
+ Float64Array
+ ].forEach(function(ctor, i) {
+ testConcatTypedArray(ctor, 4000, max[i]);
+ });
+})();
+
+
+(function testConcatStrictArguments() {
+ var args = (function(a, b, c) { "use strict"; return arguments; })(1,2,3);
+ args[Symbol.isConcatSpreadable] = true;
+ assertEquals([1, 2, 3, 1, 2, 3], [].concat(args, args));
+})();
+
+
+(function testConcatSloppyArguments() {
+ var args = (function(a, b, c) { return arguments; })(1,2,3);
+ args[Symbol.isConcatSpreadable] = true;
+ assertEquals([1, 2, 3, 1, 2, 3], [].concat(args, args));
+})();
+
+
+(function testConcatSloppyArgumentsWithDupes() {
+ var args = (function(a, a, a) { return arguments; })(1,2,3);
+ args[Symbol.isConcatSpreadable] = true;
+ assertEquals([1, 2, 3, 1, 2, 3], [].concat(args, args));
+})();
+
+
+(function testConcatSloppyArgumentsThrows() {
+ function MyError() {}
+ var args = (function(a) { return arguments; })(1,2,3);
+ Object.defineProperty(args, 0, {
+ get: function() { throw new MyError(); }
+ });
+ args[Symbol.isConcatSpreadable] = true;
+ assertThrows(function() {
+ return [].concat(args, args);
+ }, MyError);
+})();
+
+
+(function testConcatHoleySloppyArguments() {
+ var args = (function(a) { return arguments; })(1,2,3);
+ delete args[1];
+ args[Symbol.isConcatSpreadable] = true;
+ assertEquals([1, void 0, 3, 1, void 0, 3], [].concat(args, args));
+})();
+
+
+// ES5 tests
+(function testArrayConcatES5() {
+ "use strict";
+ var poses;
+ var pos;
+
+ poses = [140, 4000000000];
+ while (pos = poses.shift()) {
+ var a = new Array(pos);
+ var array_proto = [];
+ a.__proto__ = array_proto;
+ assertEquals(pos, a.length);
+ a.push('foo');
+ assertEquals(pos + 1, a.length);
+ var b = ['bar'];
+ var c = a.concat(b);
+ assertEquals(pos + 2, c.length);
+ assertEquals("undefined", typeof(c[pos - 1]));
+ assertEquals("foo", c[pos]);
+ assertEquals("bar", c[pos + 1]);
+
+ // Can we fool the system by putting a number in a string?
+ var onetwofour = "124";
+ a[onetwofour] = 'doo';
+ assertEquals(a[124], 'doo');
+ c = a.concat(b);
+ assertEquals(c[124], 'doo');
+
+ // If we put a number in the prototype, then the spec says it should be
+ // copied on concat.
+ array_proto["123"] = 'baz';
+ assertEquals(a[123], 'baz');
+
+ c = a.concat(b);
+ assertEquals(pos + 2, c.length);
+ assertEquals("baz", c[123]);
+ assertEquals("undefined", typeof(c[pos - 1]));
+ assertEquals("foo", c[pos]);
+ assertEquals("bar", c[pos + 1]);
+
+ // When we take the number off the prototype it disappears from a, but
+ // the concat put it in c itself.
+ array_proto["123"] = undefined;
+ assertEquals("undefined", typeof(a[123]));
+ assertEquals("baz", c[123]);
+
+ // If the element of prototype is shadowed, the element on the instance
+ // should be copied, but not the one on the prototype.
+ array_proto[123] = 'baz';
+ a[123] = 'xyz';
+ assertEquals('xyz', a[123]);
+ c = a.concat(b);
+ assertEquals('xyz', c[123]);
+
+ // Non-numeric properties on the prototype or the array shouldn't get
+ // copied.
+ array_proto.moe = 'joe';
+ a.ben = 'jerry';
+ assertEquals(a["moe"], 'joe');
+ assertEquals(a["ben"], 'jerry');
+ c = a.concat(b);
+ // ben was not copied
+ assertEquals("undefined", typeof(c.ben));
+
+ // When we take moe off the prototype it disappears from all arrays.
+ array_proto.moe = undefined;
+ assertEquals("undefined", typeof(c.moe));
+
+ // Negative indices don't get concated.
+ a[-1] = 'minus1';
+ assertEquals("minus1", a[-1]);
+ assertEquals("undefined", typeof(a[0xffffffff]));
+ c = a.concat(b);
+ assertEquals("undefined", typeof(c[-1]));
+ assertEquals("undefined", typeof(c[0xffffffff]));
+ assertEquals(c.length, a.length + 1);
+ }
+
+ poses = [140, 4000000000];
+ while (pos = poses.shift()) {
+ var a = new Array(pos);
+ assertEquals(pos, a.length);
+ a.push('foo');
+ assertEquals(pos + 1, a.length);
+ var b = ['bar'];
+ var c = a.concat(b);
+ assertEquals(pos + 2, c.length);
+ assertEquals("undefined", typeof(c[pos - 1]));
+ assertEquals("foo", c[pos]);
+ assertEquals("bar", c[pos + 1]);
+
+ // Can we fool the system by putting a number in a string?
+ var onetwofour = "124";
+ a[onetwofour] = 'doo';
+ assertEquals(a[124], 'doo');
+ c = a.concat(b);
+ assertEquals(c[124], 'doo');
+
+ // If we put a number in the prototype, then the spec says it should be
+ // copied on concat.
+ Array.prototype["123"] = 'baz';
+ assertEquals(a[123], 'baz');
+
+ c = a.concat(b);
+ assertEquals(pos + 2, c.length);
+ assertEquals("baz", c[123]);
+ assertEquals("undefined", typeof(c[pos - 1]));
+ assertEquals("foo", c[pos]);
+ assertEquals("bar", c[pos + 1]);
+
+ // When we take the number off the prototype it disappears from a, but
+ // the concat put it in c itself.
+ Array.prototype["123"] = undefined;
+ assertEquals("undefined", typeof(a[123]));
+ assertEquals("baz", c[123]);
+
+ // If the element of prototype is shadowed, the element on the instance
+ // should be copied, but not the one on the prototype.
+ Array.prototype[123] = 'baz';
+ a[123] = 'xyz';
+ assertEquals('xyz', a[123]);
+ c = a.concat(b);
+ assertEquals('xyz', c[123]);
+
+ // Non-numeric properties on the prototype or the array shouldn't get
+ // copied.
+ Array.prototype.moe = 'joe';
+ a.ben = 'jerry';
+ assertEquals(a["moe"], 'joe');
+ assertEquals(a["ben"], 'jerry');
+ c = a.concat(b);
+ // ben was not copied
+ assertEquals("undefined", typeof(c.ben));
+ // moe was not copied, but we can see it through the prototype
+ assertEquals("joe", c.moe);
+
+ // When we take moe off the prototype it disappears from all arrays.
+ Array.prototype.moe = undefined;
+ assertEquals("undefined", typeof(c.moe));
+
+ // Negative indices don't get concated.
+ a[-1] = 'minus1';
+ assertEquals("minus1", a[-1]);
+ assertEquals("undefined", typeof(a[0xffffffff]));
+ c = a.concat(b);
+ assertEquals("undefined", typeof(c[-1]));
+ assertEquals("undefined", typeof(c[0xffffffff]));
+ assertEquals(c.length, a.length + 1);
+
+ }
+
+ a = [];
+ c = a.concat('Hello');
+ assertEquals(1, c.length);
+ assertEquals("Hello", c[0]);
+ assertEquals("Hello", c.toString());
+
+ // Check that concat preserves holes.
+ var holey = [void 0,'a',,'c'].concat(['d',,'f',[0,,2],void 0])
+ assertEquals(9, holey.length); // hole in embedded array is ignored
+ for (var i = 0; i < holey.length; i++) {
+ if (i == 2 || i == 5) {
+ assertFalse(i in holey);
+ } else {
+ assertTrue(i in holey);
+ }
+ }
+
+ // Polluted prototype from prior tests.
+ delete Array.prototype[123];
+
+ // Check that concat reads getters in the correct order.
+ var arr1 = [,2];
+ var arr2 = [1,3];
+ var r1 = [].concat(arr1, arr2); // [,2,1,3]
+ assertEquals([,2,1,3], r1);
+
+ // Make first array change length of second array.
+ Object.defineProperty(arr1, 0, {get: function() {
+ arr2.push("X");
+ return undefined;
+ }, configurable: true})
+ var r2 = [].concat(arr1, arr2); // [undefined,2,1,3,"X"]
+ assertEquals([undefined,2,1,3,"X"], r2);
+
+ // Make first array change length of second array massively.
+ arr2.length = 2;
+ Object.defineProperty(arr1, 0, {get: function() {
+ arr2[500000] = "X";
+ return undefined;
+ }, configurable: true})
+ var r3 = [].concat(arr1, arr2); // [undefined,2,1,3,"X"]
+ var expected = [undefined,2,1,3];
+ expected[500000 + 2] = "X";
+
+ assertEquals(expected, r3);
+
+ var arr3 = [];
+ var trace = [];
+ var expectedTrace = []
+ function mkGetter(i) { return function() { trace.push(i); }; }
+ arr3.length = 10000;
+ for (var i = 0; i < 100; i++) {
+ Object.defineProperty(arr3, i * i, {get: mkGetter(i)});
+ expectedTrace[i] = i;
+ expectedTrace[100 + i] = i;
+ }
+ var r4 = [0].concat(arr3, arr3);
+ assertEquals(1 + arr3.length * 2, r4.length);
+ assertEquals(expectedTrace, trace);
+})();