From 0ea4814a70f7ba84563aa294dd5e52f422033c0f Mon Sep 17 00:00:00 2001 From: "Ioanna M. Dimitriou H" Date: Fri, 16 Aug 2024 01:03:36 +0200 Subject: [PATCH] Import relevant files from #3888 --- .../prototype/apply/resizable-buffer.js | 156 ++++++++++ .../typedarray-backed-by-resizable-buffer.js | 274 ++++++++++++++++++ .../Object/defineProperty/coerced-P-grow.js | 113 ++++++++ .../Object/defineProperty/coerced-P-shrink.js | 86 ++++++ .../typedarray-backed-by-resizable-buffer.js | 87 ++++++ .../out-of-bounds-behaves-like-detached.js | 30 ++ .../TypedArray/out-of-bounds-get-and-set.js | 91 ++++++ .../built-ins/TypedArray/out-of-bounds-has.js | 77 +++++ ...resizable-and-fixed-have-same-prototype.js | 52 ++++ .../prototype/slice/coerced-start-grow.js | 97 +++++++ .../resizable-buffer-length-tracking-1.js | 91 ++++++ .../resizable-buffer-length-tracking-2.js | 111 +++++++ .../buffer-arg/resizable-out-of-bounds.js | 70 +++++ .../src-typedarray-resizable-buffer.js | 207 +++++++++++++ .../typedarray-backed-by-resizable-buffer.js | 256 ++++++++++++++++ .../statements/for-in/resizable-buffer.js | 54 ++++ ...ked-by-resizable-buffer-grow-before-end.js | 121 ++++++++ ...-by-resizable-buffer-grow-mid-iteration.js | 134 +++++++++ ...y-resizable-buffer-shrink-mid-iteration.js | 149 ++++++++++ ...ble-buffer-shrink-to-zero-mid-iteration.js | 121 ++++++++ .../typedarray-backed-by-resizable-buffer.js | 112 +++++++ 21 files changed, 2489 insertions(+) create mode 100644 test/built-ins/Function/prototype/apply/resizable-buffer.js create mode 100644 test/built-ins/Object/defineProperties/typedarray-backed-by-resizable-buffer.js create mode 100644 test/built-ins/Object/defineProperty/coerced-P-grow.js create mode 100644 test/built-ins/Object/defineProperty/coerced-P-shrink.js create mode 100644 test/built-ins/Object/freeze/typedarray-backed-by-resizable-buffer.js create mode 100644 test/built-ins/TypedArray/out-of-bounds-behaves-like-detached.js create mode 100644 test/built-ins/TypedArray/out-of-bounds-get-and-set.js create mode 100644 test/built-ins/TypedArray/out-of-bounds-has.js create mode 100644 test/built-ins/TypedArray/prototype/resizable-and-fixed-have-same-prototype.js create mode 100644 test/built-ins/TypedArray/prototype/slice/coerced-start-grow.js create mode 100644 test/built-ins/TypedArray/resizable-buffer-length-tracking-1.js create mode 100644 test/built-ins/TypedArray/resizable-buffer-length-tracking-2.js create mode 100644 test/built-ins/TypedArrayConstructors/ctors/buffer-arg/resizable-out-of-bounds.js create mode 100644 test/built-ins/TypedArrayConstructors/ctors/typedarray-arg/src-typedarray-resizable-buffer.js create mode 100644 test/language/destructuring/binding/typedarray-backed-by-resizable-buffer.js create mode 100644 test/language/statements/for-in/resizable-buffer.js create mode 100644 test/language/statements/for-of/typedarray-backed-by-resizable-buffer-grow-before-end.js create mode 100644 test/language/statements/for-of/typedarray-backed-by-resizable-buffer-grow-mid-iteration.js create mode 100644 test/language/statements/for-of/typedarray-backed-by-resizable-buffer-shrink-mid-iteration.js create mode 100644 test/language/statements/for-of/typedarray-backed-by-resizable-buffer-shrink-to-zero-mid-iteration.js create mode 100644 test/language/statements/for-of/typedarray-backed-by-resizable-buffer.js diff --git a/test/built-ins/Function/prototype/apply/resizable-buffer.js b/test/built-ins/Function/prototype/apply/resizable-buffer.js new file mode 100644 index 00000000000..c90a7781645 --- /dev/null +++ b/test/built-ins/Function/prototype/apply/resizable-buffer.js @@ -0,0 +1,156 @@ +// Copyright 2023 the V8 project authors. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-function.prototype.apply +description: > + Function.p.apply behaves correctly when the argument array is a + TypedArray backed by resizable buffer +includes: [compareArray.js] +features: [resizable-arraybuffer] +---*/ + + +class MyUint8Array extends Uint8Array { +} + +class MyFloat32Array extends Float32Array { +} + +class MyBigInt64Array extends BigInt64Array { +} + +const builtinCtors = [ + Uint8Array, + Int8Array, + Uint16Array, + Int16Array, + Uint32Array, + Int32Array, + Float32Array, + Float64Array, + Uint8ClampedArray, + BigUint64Array, + BigInt64Array +]; + +const ctors = [ + ...builtinCtors, + MyUint8Array, + MyFloat32Array, + MyBigInt64Array +]; + +function CreateResizableArrayBuffer(byteLength, maxByteLength) { + return new ArrayBuffer(byteLength, { maxByteLength: maxByteLength }); +} + +function WriteToTypedArray(array, index, value) { + if (array instanceof BigInt64Array || array instanceof BigUint64Array) { + array[index] = BigInt(value); + } else { + array[index] = value; + } +} + +function Convert(item) { + if (typeof item == 'bigint') { + return Number(item); + } + return item; +} + +function ToNumbers(array) { + let result = []; + for (let item of array) { + result.push(Convert(item)); + } + return result; +} + +for (let ctor of ctors) { + const rab = CreateResizableArrayBuffer(4 * ctor.BYTES_PER_ELEMENT, 8 * ctor.BYTES_PER_ELEMENT); + const fixedLength = new ctor(rab, 0, 4); + const fixedLengthWithOffset = new ctor(rab, 2 * ctor.BYTES_PER_ELEMENT, 2); + const lengthTracking = new ctor(rab, 0); + const lengthTrackingWithOffset = new ctor(rab, 2 * ctor.BYTES_PER_ELEMENT); + const taWrite = new ctor(rab); + for (let i = 0; i < 4; ++i) { + WriteToTypedArray(taWrite, i, i); + } + function func(...args) { + return [...args]; + } + assert.compareArray(ToNumbers(func.apply(null, fixedLength)), [ + 0, + 1, + 2, + 3 + ]); + assert.compareArray(ToNumbers(func.apply(null, fixedLengthWithOffset)), [ + 2, + 3 + ]); + assert.compareArray(ToNumbers(func.apply(null, lengthTracking)), [ + 0, + 1, + 2, + 3 + ]); + assert.compareArray(ToNumbers(func.apply(null, lengthTrackingWithOffset)), [ + 2, + 3 + ]); + + // Shrink so that fixed length TAs go out of bounds. + rab.resize(3 * ctor.BYTES_PER_ELEMENT); + assert.compareArray(ToNumbers(func.apply(null, fixedLength)), []); + assert.compareArray(ToNumbers(func.apply(null, fixedLengthWithOffset)), []); + assert.compareArray(ToNumbers(func.apply(null, lengthTracking)), [ + 0, + 1, + 2 + ]); + assert.compareArray(ToNumbers(func.apply(null, lengthTrackingWithOffset)), [2]); + + // Shrink so that the TAs with offset go out of bounds. + rab.resize(1 * ctor.BYTES_PER_ELEMENT); + assert.compareArray(ToNumbers(func.apply(null, fixedLength)), []); + assert.compareArray(ToNumbers(func.apply(null, fixedLengthWithOffset)), []); + assert.compareArray(ToNumbers(func.apply(null, lengthTracking)), [0]); + assert.compareArray(ToNumbers(func.apply(null, lengthTrackingWithOffset)), []); + + // Shrink to zero. + rab.resize(0); + assert.compareArray(ToNumbers(func.apply(null, fixedLength)), []); + assert.compareArray(ToNumbers(func.apply(null, fixedLengthWithOffset)), []); + assert.compareArray(ToNumbers(func.apply(null, lengthTracking)), []); + assert.compareArray(ToNumbers(func.apply(null, lengthTrackingWithOffset)), []); + + // Grow so that all TAs are back in-bounds. New memory is zeroed. + rab.resize(6 * ctor.BYTES_PER_ELEMENT); + assert.compareArray(ToNumbers(func.apply(null, fixedLength)), [ + 0, + 0, + 0, + 0 + ]); + assert.compareArray(ToNumbers(func.apply(null, fixedLengthWithOffset)), [ + 0, + 0 + ]); + assert.compareArray(ToNumbers(func.apply(null, lengthTracking)), [ + 0, + 0, + 0, + 0, + 0, + 0 + ]); + assert.compareArray(ToNumbers(func.apply(null, lengthTrackingWithOffset)), [ + 0, + 0, + 0, + 0 + ]); +} diff --git a/test/built-ins/Object/defineProperties/typedarray-backed-by-resizable-buffer.js b/test/built-ins/Object/defineProperties/typedarray-backed-by-resizable-buffer.js new file mode 100644 index 00000000000..ffd2123fb93 --- /dev/null +++ b/test/built-ins/Object/defineProperties/typedarray-backed-by-resizable-buffer.js @@ -0,0 +1,274 @@ +// Copyright 2023 the V8 project authors. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-object.definepropreties +description: > + Object.defineProperties behaves correctly on TypedArrays backed by + resizable buffers +includes: [compareArray.js] +features: [resizable-arraybuffer] +---*/ + +class MyUint8Array extends Uint8Array { +} + +class MyFloat32Array extends Float32Array { +} + +class MyBigInt64Array extends BigInt64Array { +} + +const builtinCtors = [ + Uint8Array, + Int8Array, + Uint16Array, + Int16Array, + Uint32Array, + Int32Array, + Float32Array, + Float64Array, + Uint8ClampedArray, + BigUint64Array, + BigInt64Array +]; + +const ctors = [ + ...builtinCtors, + MyUint8Array, + MyFloat32Array, + MyBigInt64Array +]; + +function CreateResizableArrayBuffer(byteLength, maxByteLength) { + return new ArrayBuffer(byteLength, { maxByteLength: maxByteLength }); +} + +function Convert(item) { + if (typeof item == 'bigint') { + return Number(item); + } + return item; +} + +function ToNumbers(array) { + let result = []; + for (let item of array) { + result.push(Convert(item)); + } + return result; +} + +function ObjectDefinePropertyHelper(ta, index, value) { + if (ta instanceof BigInt64Array || ta instanceof BigUint64Array) { + Object.defineProperty(ta, index, { value: BigInt(value) }); + } else { + Object.defineProperty(ta, index, { value: value }); + } +} + +function ObjectDefinePropertiesHelper(ta, index, value) { + const values = {}; + if (ta instanceof BigInt64Array || ta instanceof BigUint64Array) { + values[index] = { value: BigInt(value) }; + } else { + values[index] = { value: value }; + } + Object.defineProperties(ta, values); +} + +for (let helper of [ + ObjectDefinePropertyHelper, + ObjectDefinePropertiesHelper + ]) { + for (let ctor of ctors) { + const rab = CreateResizableArrayBuffer(4 * ctor.BYTES_PER_ELEMENT, 8 * ctor.BYTES_PER_ELEMENT); + const fixedLength = new ctor(rab, 0, 4); + const fixedLengthWithOffset = new ctor(rab, 2 * ctor.BYTES_PER_ELEMENT, 2); + const lengthTracking = new ctor(rab, 0); + const lengthTrackingWithOffset = new ctor(rab, 2 * ctor.BYTES_PER_ELEMENT); + const taFull = new ctor(rab, 0); + + // Orig. array: [0, 0, 0, 0] + // [0, 0, 0, 0] << fixedLength + // [0, 0] << fixedLengthWithOffset + // [0, 0, 0, 0, ...] << lengthTracking + // [0, 0, ...] << lengthTrackingWithOffset + + helper(fixedLength, 0, 1); + assert.compareArray(ToNumbers(taFull), [ + 1, + 0, + 0, + 0 + ]); + helper(fixedLengthWithOffset, 0, 2); + assert.compareArray(ToNumbers(taFull), [ + 1, + 0, + 2, + 0 + ]); + helper(lengthTracking, 1, 3); + assert.compareArray(ToNumbers(taFull), [ + 1, + 3, + 2, + 0 + ]); + helper(lengthTrackingWithOffset, 1, 4); + assert.compareArray(ToNumbers(taFull), [ + 1, + 3, + 2, + 4 + ]); + assert.throws(TypeError, () => { + helper(fixedLength, 4, 8); + }); + assert.throws(TypeError, () => { + helper(fixedLengthWithOffset, 2, 8); + }); + assert.throws(TypeError, () => { + helper(lengthTracking, 4, 8); + }); + assert.throws(TypeError, () => { + helper(lengthTrackingWithOffset, 2, 8); + }); + + // Shrink so that fixed length TAs go out of bounds. + rab.resize(3 * ctor.BYTES_PER_ELEMENT); + + // Orig. array: [1, 3, 2] + // [1, 3, 2, ...] << lengthTracking + // [2, ...] << lengthTrackingWithOffset + + assert.throws(TypeError, () => { + helper(fixedLength, 0, 8); + }); + assert.throws(TypeError, () => { + helper(fixedLengthWithOffset, 0, 8); + }); + assert.compareArray(ToNumbers(taFull), [ + 1, + 3, + 2 + ]); + helper(lengthTracking, 0, 5); + assert.compareArray(ToNumbers(taFull), [ + 5, + 3, + 2 + ]); + helper(lengthTrackingWithOffset, 0, 6); + assert.compareArray(ToNumbers(taFull), [ + 5, + 3, + 6 + ]); + + // Shrink so that the TAs with offset go out of bounds. + rab.resize(1 * ctor.BYTES_PER_ELEMENT); + assert.throws(TypeError, () => { + helper(fixedLength, 0, 8); + }); + assert.throws(TypeError, () => { + helper(fixedLengthWithOffset, 0, 8); + }); + assert.throws(TypeError, () => { + helper(lengthTrackingWithOffset, 0, 8); + }); + assert.compareArray(ToNumbers(taFull), [5]); + helper(lengthTracking, 0, 7); + assert.compareArray(ToNumbers(taFull), [7]); + + // Shrink to zero. + rab.resize(0); + assert.throws(TypeError, () => { + helper(fixedLength, 0, 8); + }); + assert.throws(TypeError, () => { + helper(fixedLengthWithOffset, 0, 8); + }); + assert.throws(TypeError, () => { + helper(lengthTracking, 0, 8); + }); + assert.throws(TypeError, () => { + helper(lengthTrackingWithOffset, 0, 8); + }); + assert.compareArray(ToNumbers(taFull), []); + + // Grow so that all TAs are back in-bounds. + rab.resize(6 * ctor.BYTES_PER_ELEMENT); + helper(fixedLength, 0, 9); + assert.compareArray(ToNumbers(taFull), [ + 9, + 0, + 0, + 0, + 0, + 0 + ]); + helper(fixedLengthWithOffset, 0, 10); + assert.compareArray(ToNumbers(taFull), [ + 9, + 0, + 10, + 0, + 0, + 0 + ]); + helper(lengthTracking, 1, 11); + assert.compareArray(ToNumbers(taFull), [ + 9, + 11, + 10, + 0, + 0, + 0 + ]); + helper(lengthTrackingWithOffset, 2, 12); + assert.compareArray(ToNumbers(taFull), [ + 9, + 11, + 10, + 0, + 12, + 0 + ]); + + // Trying to define properties out of the fixed-length bounds throws. + assert.throws(TypeError, () => { + helper(fixedLength, 5, 13); + }); + assert.throws(TypeError, () => { + helper(fixedLengthWithOffset, 3, 13); + }); + assert.compareArray(ToNumbers(taFull), [ + 9, + 11, + 10, + 0, + 12, + 0 + ]); + helper(lengthTracking, 4, 14); + assert.compareArray(ToNumbers(taFull), [ + 9, + 11, + 10, + 0, + 14, + 0 + ]); + helper(lengthTrackingWithOffset, 3, 15); + assert.compareArray(ToNumbers(taFull), [ + 9, + 11, + 10, + 0, + 14, + 15 + ]); + } +} diff --git a/test/built-ins/Object/defineProperty/coerced-P-grow.js b/test/built-ins/Object/defineProperty/coerced-P-grow.js new file mode 100644 index 00000000000..2d47034a91e --- /dev/null +++ b/test/built-ins/Object/defineProperty/coerced-P-grow.js @@ -0,0 +1,113 @@ +// Copyright 2023 the V8 project authors. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-object.defineproperty +description: > + Object.defineProperty behaves correctly when the object is a + TypedArray backed by a resizable buffer that's grown during argument + coercion +includes: [compareArray.js] +features: [resizable-arraybuffer] +---*/ + +class MyUint8Array extends Uint8Array { +} + +class MyFloat32Array extends Float32Array { +} + +class MyBigInt64Array extends BigInt64Array { +} + +const builtinCtors = [ + Uint8Array, + Int8Array, + Uint16Array, + Int16Array, + Uint32Array, + Int32Array, + Float32Array, + Float64Array, + Uint8ClampedArray, + BigUint64Array, + BigInt64Array +]; + +const ctors = [ + ...builtinCtors, + MyUint8Array, + MyFloat32Array, + MyBigInt64Array +]; + +function CreateResizableArrayBuffer(byteLength, maxByteLength) { + return new ArrayBuffer(byteLength, { maxByteLength: maxByteLength }); +} + +function Convert(item) { + if (typeof item == 'bigint') { + return Number(item); + } + return item; +} + +function ToNumbers(array) { + let result = []; + for (let item of array) { + result.push(Convert(item)); + } + return result; +} + +function ObjectDefinePropertyHelper(ta, index, value) { + if (ta instanceof BigInt64Array || ta instanceof BigUint64Array) { + Object.defineProperty(ta, index, { value: BigInt(value) }); + } else { + Object.defineProperty(ta, index, { value: value }); + } +} + +const helper = ObjectDefinePropertyHelper; + +// Fixed length. +for (let ctor of ctors) { + const rab = CreateResizableArrayBuffer(4 * ctor.BYTES_PER_ELEMENT, 8 * ctor.BYTES_PER_ELEMENT); + const fixedLength = new ctor(rab, 0, 4); + // Make fixedLength go OOB. + rab.resize(2 * ctor.BYTES_PER_ELEMENT); + const evil = { + toString: () => { + rab.resize(6 * ctor.BYTES_PER_ELEMENT); + return 0; + } + }; + helper(fixedLength, evil, 8); + assert.compareArray(ToNumbers(fixedLength), [ + 8, + 0, + 0, + 0 + ]); +} + +// Length tracking. +for (let ctor of ctors) { + const rab = CreateResizableArrayBuffer(4 * ctor.BYTES_PER_ELEMENT, 8 * ctor.BYTES_PER_ELEMENT); + const lengthTracking = new ctor(rab, 0); + const evil = { + toString: () => { + rab.resize(6 * ctor.BYTES_PER_ELEMENT); + return 4; // Index valid after resize. + } + }; + helper(lengthTracking, evil, 8); + assert.compareArray(ToNumbers(lengthTracking), [ + 0, + 0, + 0, + 0, + 8, + 0 + ]); +} diff --git a/test/built-ins/Object/defineProperty/coerced-P-shrink.js b/test/built-ins/Object/defineProperty/coerced-P-shrink.js new file mode 100644 index 00000000000..1d7a977f8d5 --- /dev/null +++ b/test/built-ins/Object/defineProperty/coerced-P-shrink.js @@ -0,0 +1,86 @@ +// Copyright 2023 the V8 project authors. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-object.defineproperty +description: > + Object.defineProperty behaves correctly when the object is a + TypedArray backed by a resizable buffer that's shrunk during argument + coercion +includes: [compareArray.js] +features: [resizable-arraybuffer] +---*/ + +class MyUint8Array extends Uint8Array { +} + +class MyFloat32Array extends Float32Array { +} + +class MyBigInt64Array extends BigInt64Array { +} + +const builtinCtors = [ + Uint8Array, + Int8Array, + Uint16Array, + Int16Array, + Uint32Array, + Int32Array, + Float32Array, + Float64Array, + Uint8ClampedArray, + BigUint64Array, + BigInt64Array +]; + +const ctors = [ + ...builtinCtors, + MyUint8Array, + MyFloat32Array, + MyBigInt64Array +]; + +function CreateResizableArrayBuffer(byteLength, maxByteLength) { + return new ArrayBuffer(byteLength, { maxByteLength: maxByteLength }); +} + +function ObjectDefinePropertyHelper(ta, index, value) { + if (ta instanceof BigInt64Array || ta instanceof BigUint64Array) { + Object.defineProperty(ta, index, { value: BigInt(value) }); + } else { + Object.defineProperty(ta, index, { value: value }); + } +} + +const helper = ObjectDefinePropertyHelper; + +// Fixed length. +for (let ctor of ctors) { + const rab = CreateResizableArrayBuffer(4 * ctor.BYTES_PER_ELEMENT, 8 * ctor.BYTES_PER_ELEMENT); + const fixedLength = new ctor(rab, 0, 4); + const evil = { + toString: () => { + rab.resize(2 * ctor.BYTES_PER_ELEMENT); + return 0; + } + }; + assert.throws(TypeError, () => { + helper(fixedLength, evil, 8); + }); +} + +// Length tracking. +for (let ctor of ctors) { + const rab = CreateResizableArrayBuffer(4 * ctor.BYTES_PER_ELEMENT, 8 * ctor.BYTES_PER_ELEMENT); + const lengthTracking = new ctor(rab, 0); + const evil = { + toString: () => { + rab.resize(2 * ctor.BYTES_PER_ELEMENT); + return 3; // Index too large after resize. + } + }; + assert.throws(TypeError, () => { + helper(lengthTracking, evil, 8); + }); +} diff --git a/test/built-ins/Object/freeze/typedarray-backed-by-resizable-buffer.js b/test/built-ins/Object/freeze/typedarray-backed-by-resizable-buffer.js new file mode 100644 index 00000000000..af10241bacf --- /dev/null +++ b/test/built-ins/Object/freeze/typedarray-backed-by-resizable-buffer.js @@ -0,0 +1,87 @@ +// Copyright 2023 the V8 project authors. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-object.freeze +description: > + Object.freeze throws on non-0-length TypedArrays backed by resizable + buffers but do not throw on 0-length ones +features: [resizable-arraybuffer] + +---*/ + +class MyUint8Array extends Uint8Array { +} + +class MyFloat32Array extends Float32Array { +} + +class MyBigInt64Array extends BigInt64Array { +} + +const builtinCtors = [ + Uint8Array, + Int8Array, + Uint16Array, + Int16Array, + Uint32Array, + Int32Array, + Float32Array, + Float64Array, + Uint8ClampedArray, + BigUint64Array, + BigInt64Array +]; + +const ctors = [ + ...builtinCtors, + MyUint8Array, + MyFloat32Array, + MyBigInt64Array +]; + +function CreateResizableArrayBuffer(byteLength, maxByteLength) { + return new ArrayBuffer(byteLength, { maxByteLength: maxByteLength }); +} + +// Freezing non-OOB non-zero-length TAs throws. +for (let ctor of ctors) { + const rab = CreateResizableArrayBuffer(4 * ctor.BYTES_PER_ELEMENT, 8 * ctor.BYTES_PER_ELEMENT); + const fixedLength = new ctor(rab, 0, 4); + const fixedLengthWithOffset = new ctor(rab, 2 * ctor.BYTES_PER_ELEMENT, 2); + const lengthTracking = new ctor(rab, 0); + const lengthTrackingWithOffset = new ctor(rab, 2 * ctor.BYTES_PER_ELEMENT); + assert.throws(TypeError, () => { + Object.freeze(fixedLength); + }); + assert.throws(TypeError, () => { + Object.freeze(fixedLengthWithOffset); + }); + assert.throws(TypeError, () => { + Object.freeze(lengthTracking); + }); + assert.throws(TypeError, () => { + Object.freeze(lengthTrackingWithOffset); + }); +} +// Freezing zero-length TAs doesn't throw. +for (let ctor of ctors) { + const rab = CreateResizableArrayBuffer(4 * ctor.BYTES_PER_ELEMENT, 8 * ctor.BYTES_PER_ELEMENT); + const fixedLength = new ctor(rab, 0, 0); + const fixedLengthWithOffset = new ctor(rab, 2 * ctor.BYTES_PER_ELEMENT, 0); + const lengthTrackingWithOffset = new ctor(rab, 4 * ctor.BYTES_PER_ELEMENT); + Object.freeze(fixedLength); + Object.freeze(fixedLengthWithOffset); + Object.freeze(lengthTrackingWithOffset); +} +// If the buffer has been resized to make length-tracking TAs zero-length, +// freezing them also doesn't throw. +for (let ctor of ctors) { + const rab = CreateResizableArrayBuffer(4 * ctor.BYTES_PER_ELEMENT, 8 * ctor.BYTES_PER_ELEMENT); + const lengthTracking = new ctor(rab); + const lengthTrackingWithOffset = new ctor(rab, 2 * ctor.BYTES_PER_ELEMENT); + rab.resize(2 * ctor.BYTES_PER_ELEMENT); + Object.freeze(lengthTrackingWithOffset); + rab.resize(0 * ctor.BYTES_PER_ELEMENT); + Object.freeze(lengthTracking); +} diff --git a/test/built-ins/TypedArray/out-of-bounds-behaves-like-detached.js b/test/built-ins/TypedArray/out-of-bounds-behaves-like-detached.js new file mode 100644 index 00000000000..27ec251cb5f --- /dev/null +++ b/test/built-ins/TypedArray/out-of-bounds-behaves-like-detached.js @@ -0,0 +1,30 @@ +// Copyright 2023 the V8 project authors. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-isvalidintegerindex +description: > + TypedArrays backed by resizable buffers that are out-of-bounds behave + as if they were detached +features: [resizable-arraybuffer] +---*/ + +function CreateResizableArrayBuffer(byteLength, maxByteLength) { + return new ArrayBuffer(byteLength, { maxByteLength: maxByteLength }); +} + +function ReadElement2(ta) { + return ta[2]; +} +function HasElement2(ta) { + return 2 in ta; +} +const rab = CreateResizableArrayBuffer(16, 40); +const i8a = new Int8Array(rab, 0, 4); +i8a.__proto__ = { 2: 'wrong value' }; +i8a[2] = 10; +assert.sameValue(ReadElement2(i8a), 10); +assert(HasElement2(i8a)); +rab.resize(0); +assert.sameValue(ReadElement2(i8a), undefined); +assert(!HasElement2(i8a)); diff --git a/test/built-ins/TypedArray/out-of-bounds-get-and-set.js b/test/built-ins/TypedArray/out-of-bounds-get-and-set.js new file mode 100644 index 00000000000..3140d822de4 --- /dev/null +++ b/test/built-ins/TypedArray/out-of-bounds-get-and-set.js @@ -0,0 +1,91 @@ +// Copyright 2023 the V8 project authors. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-isvalidintegerindex +description: > + Getting and setting in-bounds and out-of-bounds indices on TypedArrays backed + by resizable buffers. +features: [resizable-arraybuffer] +---*/ + +class MyUint8Array extends Uint8Array { +} + +class MyFloat32Array extends Float32Array { +} + +class MyBigInt64Array extends BigInt64Array { +} + +const builtinCtors = [ + Uint8Array, + Int8Array, + Uint16Array, + Int16Array, + Uint32Array, + Int32Array, + Float32Array, + Float64Array, + Uint8ClampedArray, + BigUint64Array, + BigInt64Array +]; + +const ctors = [ + ...builtinCtors, + MyUint8Array, + MyFloat32Array, + MyBigInt64Array +]; + +function CreateResizableArrayBuffer(byteLength, maxByteLength) { + return new ArrayBuffer(byteLength, { maxByteLength: maxByteLength }); +} + +for (let ctor of ctors) { + if (ctor.BYTES_PER_ELEMENT != 1) { + continue; + } + const rab = CreateResizableArrayBuffer(16, 40); + const array = new ctor(rab, 0, 4); + // Initial values + for (let i = 0; i < 4; ++i) { + assert.sameValue(array[i], 0); + } + // Within-bounds write + for (let i = 0; i < 4; ++i) { + array[i] = i; + } + // Within-bounds read + for (let i = 0; i < 4; ++i) { + assert.sameValue(array[i], i); + } + rab.resize(2); + // OOB read. If the RAB isn't large enough to fit the entire TypedArray, + // the length of the TypedArray is treated as 0. + for (let i = 0; i < 4; ++i) { + assert.sameValue(array[i], undefined); + } + // OOB write (has no effect) + for (let i = 0; i < 4; ++i) { + array[i] = 10; + } + rab.resize(4); + // Within-bounds read + for (let i = 0; i < 2; ++i) { + assert.sameValue(array[i], i); + } + // The shrunk-and-regrown part got zeroed. + for (let i = 2; i < 4; ++i) { + assert.sameValue(array[i], 0); + } + rab.resize(40); + // Within-bounds read + for (let i = 0; i < 2; ++i) { + assert.sameValue(array[i], i); + } + for (let i = 2; i < 4; ++i) { + assert.sameValue(array[i], 0); + } +} diff --git a/test/built-ins/TypedArray/out-of-bounds-has.js b/test/built-ins/TypedArray/out-of-bounds-has.js new file mode 100644 index 00000000000..c08fd290dba --- /dev/null +++ b/test/built-ins/TypedArray/out-of-bounds-has.js @@ -0,0 +1,77 @@ +// Copyright 2023 the V8 project authors. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-isvalidintegerindex +description: > + In-bound indices are testable with `in` on TypedArrays backed by resizable buffers. +info: | + IsValidIntegerIndex ( O, index ) + ... + 6. Let length be IntegerIndexedObjectLength(O, getBufferByteLength). + 7. If length is out-of-bounds or ℝ(index) < 0 or ℝ(index) ≥ length, return false. + ... +features: [resizable-arraybuffer] +---*/ + +class MyUint8Array extends Uint8Array { +} + +class MyFloat32Array extends Float32Array { +} + +class MyBigInt64Array extends BigInt64Array { +} + +const builtinCtors = [ + Uint8Array, + Int8Array, + Uint16Array, + Int16Array, + Uint32Array, + Int32Array, + Float32Array, + Float64Array, + Uint8ClampedArray, + BigUint64Array, + BigInt64Array +]; + +const ctors = [ + ...builtinCtors, + MyUint8Array, + MyFloat32Array, + MyBigInt64Array +]; + +function CreateResizableArrayBuffer(byteLength, maxByteLength) { + return new ArrayBuffer(byteLength, { maxByteLength: maxByteLength }); +} + +for (let ctor of ctors) { + if (ctor.BYTES_PER_ELEMENT != 1) { + continue; + } + const rab = CreateResizableArrayBuffer(16, 40); + const array = new ctor(rab, 0, 4); + // Within-bounds read + for (let i = 0; i < 4; ++i) { + assert(i in array); + } + rab.resize(2); + // OOB read. If the RAB isn't large enough to fit the entire TypedArray, + // the length of the TypedArray is treated as 0. + for (let i = 0; i < 4; ++i) { + assert(!(i in array)); + } + rab.resize(4); + // Within-bounds read + for (let i = 0; i < 4; ++i) { + assert(i in array); + } + rab.resize(40); + // Within-bounds read + for (let i = 0; i < 4; ++i) { + assert(i in array); + } +} diff --git a/test/built-ins/TypedArray/prototype/resizable-and-fixed-have-same-prototype.js b/test/built-ins/TypedArray/prototype/resizable-and-fixed-have-same-prototype.js new file mode 100644 index 00000000000..0897a69d68b --- /dev/null +++ b/test/built-ins/TypedArray/prototype/resizable-and-fixed-have-same-prototype.js @@ -0,0 +1,52 @@ +// Copyright 2023 the V8 project authors. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-%typedarray%.prototype +description: > + TypedArrays that are backed by resizable buffers have the same prototypes + as those backed by fixed-length buffers +features: [resizable-arraybuffer] +---*/ + +class MyUint8Array extends Uint8Array { +} + +class MyFloat32Array extends Float32Array { +} + +class MyBigInt64Array extends BigInt64Array { +} + +const builtinCtors = [ + Uint8Array, + Int8Array, + Uint16Array, + Int16Array, + Uint32Array, + Int32Array, + Float32Array, + Float64Array, + Uint8ClampedArray, + BigUint64Array, + BigInt64Array +]; + +const ctors = [ + ...builtinCtors, + MyUint8Array, + MyFloat32Array, + MyBigInt64Array +]; + +function CreateResizableArrayBuffer(byteLength, maxByteLength) { + return new ArrayBuffer(byteLength, { maxByteLength: maxByteLength }); +} + +const rab = CreateResizableArrayBuffer(40, 80); +const ab = new ArrayBuffer(80); +for (let ctor of ctors) { + const ta_rab = new ctor(rab, 0, 3); + const ta_ab = new ctor(ab, 0, 3); + assert.sameValue(ta_ab.__proto__, ta_rab.__proto__); +} diff --git a/test/built-ins/TypedArray/prototype/slice/coerced-start-grow.js b/test/built-ins/TypedArray/prototype/slice/coerced-start-grow.js new file mode 100644 index 00000000000..a16122da04d --- /dev/null +++ b/test/built-ins/TypedArray/prototype/slice/coerced-start-grow.js @@ -0,0 +1,97 @@ +// Copyright 2023 the V8 project authors. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-%typedarray%.prototype.slice +description: > + TypedArray.p.slice behaves correctly when the receiver is backed by + resizable buffer that is grown by argument coercion +includes: [compareArray.js] +features: [resizable-arraybuffer] +---*/ + +class MyUint8Array extends Uint8Array { +} + +class MyFloat32Array extends Float32Array { +} + +class MyBigInt64Array extends BigInt64Array { +} + +const builtinCtors = [ + Uint8Array, + Int8Array, + Uint16Array, + Int16Array, + Uint32Array, + Int32Array, + Float32Array, + Float64Array, + Uint8ClampedArray, + BigUint64Array, + BigInt64Array +]; + +const ctors = [ + ...builtinCtors, + MyUint8Array, + MyFloat32Array, + MyBigInt64Array +]; + +function CreateResizableArrayBuffer(byteLength, maxByteLength) { + return new ArrayBuffer(byteLength, { maxByteLength: maxByteLength }); +} + +function WriteToTypedArray(array, index, value) { + if (array instanceof BigInt64Array || array instanceof BigUint64Array) { + array[index] = BigInt(value); + } else { + array[index] = value; + } +} + +function Convert(item) { + if (typeof item == 'bigint') { + return Number(item); + } + return item; +} + +function ToNumbers(array) { + let result = []; + for (let item of array) { + result.push(Convert(item)); + } + return result; +} + +const TypedArraySliceHelper = (ta, ...rest) => { + return ta.slice(...rest); +}; + +function SliceParameterConversionGrows() { + for (let ctor of ctors) { + const rab = CreateResizableArrayBuffer(4 * ctor.BYTES_PER_ELEMENT, 8 * ctor.BYTES_PER_ELEMENT); + const lengthTracking = new ctor(rab); + for (let i = 0; i < 4; ++i) { + WriteToTypedArray(lengthTracking, i, i + 1); + } + const evil = { + valueOf: () => { + rab.resize(6 * ctor.BYTES_PER_ELEMENT); + return 0; + } + }; + assert.compareArray(ToNumbers(TypedArraySliceHelper(lengthTracking, evil)), [ + 1, + 2, + 3, + 4 + ]); + assert.sameValue(rab.byteLength, 6 * ctor.BYTES_PER_ELEMENT); + } +} + +SliceParameterConversionGrows(); diff --git a/test/built-ins/TypedArray/resizable-buffer-length-tracking-1.js b/test/built-ins/TypedArray/resizable-buffer-length-tracking-1.js new file mode 100644 index 00000000000..3870dd776bb --- /dev/null +++ b/test/built-ins/TypedArray/resizable-buffer-length-tracking-1.js @@ -0,0 +1,91 @@ +// Copyright 2023 the V8 project authors. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-arraybuffer-length +description: > + Basic functionality of length-tracking TypedArrays backed by resizable + buffers +features: [resizable-arraybuffer] +---*/ + +class MyUint8Array extends Uint8Array { +} + +class MyFloat32Array extends Float32Array { +} + +class MyBigInt64Array extends BigInt64Array { +} + +const builtinCtors = [ + Uint8Array, + Int8Array, + Uint16Array, + Int16Array, + Uint32Array, + Int32Array, + Float32Array, + Float64Array, + Uint8ClampedArray, + BigUint64Array, + BigInt64Array +]; + +const ctors = [ + ...builtinCtors, + MyUint8Array, + MyFloat32Array, + MyBigInt64Array +]; + +function CreateResizableArrayBuffer(byteLength, maxByteLength) { + return new ArrayBuffer(byteLength, { maxByteLength: maxByteLength }); +} + +const rab = CreateResizableArrayBuffer(16, 40); +let tas = []; +for (let ctor of ctors) { + tas.push(new ctor(rab)); +} +for (let ta of tas) { + assert.sameValue(ta.length, 16 / ta.BYTES_PER_ELEMENT); + assert.sameValue(ta.byteLength, 16); +} +rab.resize(40); +for (let ta of tas) { + assert.sameValue(ta.length, 40 / ta.BYTES_PER_ELEMENT); + assert.sameValue(ta.byteLength, 40); +} +// Resize to a number which is not a multiple of all byte_lengths. +rab.resize(19); +for (let ta of tas) { + const expected_length = Math.floor(19 / ta.BYTES_PER_ELEMENT); + assert.sameValue(ta.length, expected_length); + assert.sameValue(ta.byteLength, expected_length * ta.BYTES_PER_ELEMENT); +} +rab.resize(1); +for (let ta of tas) { + if (ta.BYTES_PER_ELEMENT == 1) { + assert.sameValue(ta.length, 1); + assert.sameValue(ta.byteLength, 1); + } else { + assert.sameValue(ta.length, 0); + assert.sameValue(ta.byteLength, 0); + } +} +rab.resize(0); +for (let ta of tas) { + assert.sameValue(ta.length, 0); + assert.sameValue(ta.byteLength, 0); +} +rab.resize(8); +for (let ta of tas) { + assert.sameValue(ta.length, 8 / ta.BYTES_PER_ELEMENT); + assert.sameValue(ta.byteLength, 8); +} +rab.resize(40); +for (let ta of tas) { + assert.sameValue(ta.length, 40 / ta.BYTES_PER_ELEMENT); + assert.sameValue(ta.byteLength, 40); +} diff --git a/test/built-ins/TypedArray/resizable-buffer-length-tracking-2.js b/test/built-ins/TypedArray/resizable-buffer-length-tracking-2.js new file mode 100644 index 00000000000..43b3c1f0ce1 --- /dev/null +++ b/test/built-ins/TypedArray/resizable-buffer-length-tracking-2.js @@ -0,0 +1,111 @@ +// Copyright 2023 the V8 project authors. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-arraybuffer-length +description: > + Length-tracking TypedArrays backed by resizable buffers with offsets + behave correctly +features: [resizable-arraybuffer] +---*/ + +// length-tracking-1 but with offsets. + +class MyUint8Array extends Uint8Array { +} + +class MyFloat32Array extends Float32Array { +} + +class MyBigInt64Array extends BigInt64Array { +} + +const builtinCtors = [ + Uint8Array, + Int8Array, + Uint16Array, + Int16Array, + Uint32Array, + Int32Array, + Float32Array, + Float64Array, + Uint8ClampedArray, + BigUint64Array, + BigInt64Array +]; + +const ctors = [ + ...builtinCtors, + MyUint8Array, + MyFloat32Array, + MyBigInt64Array +]; + +function CreateResizableArrayBuffer(byteLength, maxByteLength) { + return new ArrayBuffer(byteLength, { maxByteLength: maxByteLength }); +} + +const rab = CreateResizableArrayBuffer(16, 40); +const offset = 8; +let tas = []; +for (let ctor of ctors) { + tas.push(new ctor(rab, offset)); +} +for (let ta of tas) { + assert.sameValue(ta.length, (16 - offset) / ta.BYTES_PER_ELEMENT); + assert.sameValue(ta.byteLength, 16 - offset); + assert.sameValue(ta.byteOffset, offset); +} +rab.resize(40); +for (let ta of tas) { + assert.sameValue(ta.length, (40 - offset) / ta.BYTES_PER_ELEMENT); + assert.sameValue(ta.byteLength, 40 - offset); + assert.sameValue(ta.byteOffset, offset); +} +// Resize to a number which is not a multiple of all byte_lengths. +rab.resize(20); +for (let ta of tas) { + const expected_length = Math.floor((20 - offset) / ta.BYTES_PER_ELEMENT); + assert.sameValue(ta.length, expected_length); + assert.sameValue(ta.byteLength, expected_length * ta.BYTES_PER_ELEMENT); + assert.sameValue(ta.byteOffset, offset); +} +// Resize so that all TypedArrays go out of bounds (because of the offset). +rab.resize(7); +for (let ta of tas) { + assert.sameValue(ta.length, 0); + assert.sameValue(ta.byteLength, 0); + assert.sameValue(ta.byteOffset, 0); +} +rab.resize(0); +for (let ta of tas) { + assert.sameValue(ta.length, 0); + assert.sameValue(ta.byteLength, 0); + assert.sameValue(ta.byteOffset, 0); +} +rab.resize(8); +for (let ta of tas) { + assert.sameValue(ta.length, 0); + assert.sameValue(ta.byteLength, 0); + assert.sameValue(ta.byteOffset, offset); +} +// Resize so that the TypedArrays which have element size > 1 go out of bounds +// (because less than 1 full element would fit). +rab.resize(offset + 1); +for (let ta of tas) { + if (ta.BYTES_PER_ELEMENT == 1) { + assert.sameValue(ta.length, 1); + assert.sameValue(ta.byteLength, 1); + assert.sameValue(ta.byteOffset, offset); + } else { + assert.sameValue(ta.length, 0); + assert.sameValue(ta.byteLength, 0); + assert.sameValue(ta.byteOffset, offset); + } +} +rab.resize(40); +for (let ta of tas) { + assert.sameValue(ta.length, (40 - offset) / ta.BYTES_PER_ELEMENT); + assert.sameValue(ta.byteLength, 40 - offset); + assert.sameValue(ta.byteOffset, offset); +} diff --git a/test/built-ins/TypedArrayConstructors/ctors/buffer-arg/resizable-out-of-bounds.js b/test/built-ins/TypedArrayConstructors/ctors/buffer-arg/resizable-out-of-bounds.js new file mode 100644 index 00000000000..f2773ebd27c --- /dev/null +++ b/test/built-ins/TypedArrayConstructors/ctors/buffer-arg/resizable-out-of-bounds.js @@ -0,0 +1,70 @@ +// Copyright 2023 the V8 project authors. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-initializetypedarrayfromarraybuffer +description: > + Creating a TypedArray from a resizable buffer with invalid bounds + throw RangedError +features: [resizable-arraybuffer] +flags: [onlyStrict] +---*/ + +class MyUint8Array extends Uint8Array { +} + +class MyFloat32Array extends Float32Array { +} + +class MyBigInt64Array extends BigInt64Array { +} + +const builtinCtors = [ + Uint8Array, + Int8Array, + Uint16Array, + Int16Array, + Uint32Array, + Int32Array, + Float32Array, + Float64Array, + Uint8ClampedArray, + BigUint64Array, + BigInt64Array +]; + +const ctors = [ + ...builtinCtors, + MyUint8Array, + MyFloat32Array, + MyBigInt64Array +]; + +function CreateResizableArrayBuffer(byteLength, maxByteLength) { + return new ArrayBuffer(byteLength, { maxByteLength: maxByteLength }); +} + +const rab = CreateResizableArrayBuffer(40, 80); +for (let ctor of ctors) { + // Length too big. + assert.throws(RangeError, () => { + new ctor(rab, 0, 40 / ctor.BYTES_PER_ELEMENT + 1); + }); + // Offset too close to the end. + assert.throws(RangeError, () => { + new ctor(rab, 40 - ctor.BYTES_PER_ELEMENT, 2); + }); + // Offset beyond end. + assert.throws(RangeError, () => { + new ctor(rab, 40, 1); + }); + if (ctor.BYTES_PER_ELEMENT > 1) { + // Offset not a multiple of the byte size. + assert.throws(RangeError, () => { + new ctor(rab, 1, 1); + }); + assert.throws(RangeError, () => { + new ctor(rab, 1); + }); + } +} diff --git a/test/built-ins/TypedArrayConstructors/ctors/typedarray-arg/src-typedarray-resizable-buffer.js b/test/built-ins/TypedArrayConstructors/ctors/typedarray-arg/src-typedarray-resizable-buffer.js new file mode 100644 index 00000000000..ff412102203 --- /dev/null +++ b/test/built-ins/TypedArrayConstructors/ctors/typedarray-arg/src-typedarray-resizable-buffer.js @@ -0,0 +1,207 @@ +// Copyright 2023 the V8 project authors. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-initializetypedarrayfromtypedarray +description: > + Initializing a TypedArray from another TypedArray that is backed by a + resizable buffer +includes: [compareArray.js] +features: [resizable-arraybuffer] +---*/ + +class MyUint8Array extends Uint8Array { +} + +class MyFloat32Array extends Float32Array { +} + +class MyBigInt64Array extends BigInt64Array { +} + +const builtinCtors = [ + Uint8Array, + Int8Array, + Uint16Array, + Int16Array, + Uint32Array, + Int32Array, + Float32Array, + Float64Array, + Uint8ClampedArray, + BigUint64Array, + BigInt64Array +]; + +const ctors = [ + ...builtinCtors, + MyUint8Array, + MyFloat32Array, + MyBigInt64Array +]; + +function CreateResizableArrayBuffer(byteLength, maxByteLength) { + return new ArrayBuffer(byteLength, { maxByteLength: maxByteLength }); +} + +function IsBigIntTypedArray(ta) { + return ta instanceof BigInt64Array || ta instanceof BigUint64Array; +} + +function AllBigIntMatchedCtorCombinations(test) { + for (let targetCtor of ctors) { + for (let sourceCtor of ctors) { + if (IsBigIntTypedArray(new targetCtor()) != IsBigIntTypedArray(new sourceCtor())) { + continue; + } + test(targetCtor, sourceCtor); + } + } +} + +function WriteToTypedArray(array, index, value) { + if (array instanceof BigInt64Array || array instanceof BigUint64Array) { + array[index] = BigInt(value); + } else { + array[index] = value; + } +} + +function Convert(item) { + if (typeof item == 'bigint') { + return Number(item); + } + return item; +} + +function ToNumbers(array) { + let result = []; + for (let item of array) { + result.push(Convert(item)); + } + return result; +} + +AllBigIntMatchedCtorCombinations((targetCtor, sourceCtor) => { + const rab = CreateResizableArrayBuffer(4 * sourceCtor.BYTES_PER_ELEMENT, 8 * sourceCtor.BYTES_PER_ELEMENT); + const fixedLength = new sourceCtor(rab, 0, 4); + const fixedLengthWithOffset = new sourceCtor(rab, 2 * sourceCtor.BYTES_PER_ELEMENT, 2); + const lengthTracking = new sourceCtor(rab, 0); + const lengthTrackingWithOffset = new sourceCtor(rab, 2 * sourceCtor.BYTES_PER_ELEMENT); + + // Write some data into the array. + const taFull = new sourceCtor(rab); + for (let i = 0; i < 4; ++i) { + WriteToTypedArray(taFull, i, i + 1); + } + + // Orig. array: [1, 2, 3, 4] + // [1, 2, 3, 4] << fixedLength + // [3, 4] << fixedLengthWithOffset + // [1, 2, 3, 4, ...] << lengthTracking + // [3, 4, ...] << lengthTrackingWithOffset + + assert.compareArray(ToNumbers(new targetCtor(fixedLength)), [ + 1, + 2, + 3, + 4 + ]); + assert.compareArray(ToNumbers(new targetCtor(fixedLengthWithOffset)), [ + 3, + 4 + ]); + assert.compareArray(ToNumbers(new targetCtor(lengthTracking)), [ + 1, + 2, + 3, + 4 + ]); + assert.compareArray(ToNumbers(new targetCtor(lengthTrackingWithOffset)), [ + 3, + 4 + ]); + + // Shrink so that fixed length TAs go out of bounds. + rab.resize(3 * sourceCtor.BYTES_PER_ELEMENT); + + // Orig. array: [1, 2, 3] + // [1, 2, 3, ...] << lengthTracking + // [3, ...] << lengthTrackingWithOffset + + assert.throws(TypeError, () => { + new targetCtor(fixedLength); + }); + assert.throws(TypeError, () => { + new targetCtor(fixedLengthWithOffset); + }); + assert.compareArray(ToNumbers(new targetCtor(lengthTracking)), [ + 1, + 2, + 3 + ]); + assert.compareArray(ToNumbers(new targetCtor(lengthTrackingWithOffset)), [3]); + + // Shrink so that the TAs with offset go out of bounds. + rab.resize(1 * sourceCtor.BYTES_PER_ELEMENT); + assert.throws(TypeError, () => { + new targetCtor(fixedLength); + }); + assert.throws(TypeError, () => { + new targetCtor(fixedLengthWithOffset); + }); + assert.compareArray(ToNumbers(new targetCtor(lengthTracking)), [1]); + assert.throws(TypeError, () => { + new targetCtor(lengthTrackingWithOffset); + }); + + // Shrink to zero. + rab.resize(0); + assert.throws(TypeError, () => { + new targetCtor(fixedLength); + }); + assert.throws(TypeError, () => { + new targetCtor(fixedLengthWithOffset); + }); + assert.compareArray(ToNumbers(new targetCtor(lengthTracking)), []); + assert.throws(TypeError, () => { + new targetCtor(lengthTrackingWithOffset); + }); + + // Grow so that all TAs are back in-bounds. + rab.resize(6 * sourceCtor.BYTES_PER_ELEMENT); + for (let i = 0; i < 6; ++i) { + WriteToTypedArray(taFull, i, i + 1); + } + + // Orig. array: [1, 2, 3, 4, 5, 6] + // [1, 2, 3, 4] << fixedLength + // [3, 4] << fixedLengthWithOffset + // [1, 2, 3, 4, 5, 6, ...] << lengthTracking + // [3, 4, 5, 6, ...] << lengthTrackingWithOffset + + assert.compareArray(ToNumbers(new targetCtor(fixedLength)), [ + 1, + 2, + 3, + 4 + ]); + assert.compareArray(ToNumbers(new targetCtor(fixedLengthWithOffset)), [ + 3, + 4 + ]); + assert.compareArray(ToNumbers(new targetCtor(lengthTracking)), [ + 1, + 2, + 3, + 4, + 5, + 6 + ]); + assert.compareArray(ToNumbers(new targetCtor(lengthTrackingWithOffset)), [ + 3, + 4, + 5, + 6 + ]); +}); diff --git a/test/language/destructuring/binding/typedarray-backed-by-resizable-buffer.js b/test/language/destructuring/binding/typedarray-backed-by-resizable-buffer.js new file mode 100644 index 00000000000..65aa50650cd --- /dev/null +++ b/test/language/destructuring/binding/typedarray-backed-by-resizable-buffer.js @@ -0,0 +1,256 @@ +// Copyright 2023 the V8 project authors. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-arraybuffer-length +description: > + Destructuring assignment on TypedArrays backed by resizable buffer +includes: [compareArray.js] +features: [resizable-arraybuffer] +---*/ + +class MyUint8Array extends Uint8Array { +} + +class MyFloat32Array extends Float32Array { +} + +class MyBigInt64Array extends BigInt64Array { +} + +const builtinCtors = [ + Uint8Array, + Int8Array, + Uint16Array, + Int16Array, + Uint32Array, + Int32Array, + Float32Array, + Float64Array, + Uint8ClampedArray, + BigUint64Array, + BigInt64Array +]; + +const ctors = [ + ...builtinCtors, + MyUint8Array, + MyFloat32Array, + MyBigInt64Array +]; + +function CreateResizableArrayBuffer(byteLength, maxByteLength) { + return new ArrayBuffer(byteLength, { maxByteLength: maxByteLength }); +} + +function WriteToTypedArray(array, index, value) { + if (array instanceof BigInt64Array || array instanceof BigUint64Array) { + array[index] = BigInt(value); + } else { + array[index] = value; + } +} + +function Convert(item) { + if (typeof item == 'bigint') { + return Number(item); + } + return item; +} + +function ToNumbers(array) { + let result = []; + for (let item of array) { + result.push(Convert(item)); + } + return result; +} + +for (let ctor of ctors) { + const rab = CreateResizableArrayBuffer(4 * ctor.BYTES_PER_ELEMENT, 8 * ctor.BYTES_PER_ELEMENT); + const fixedLength = new ctor(rab, 0, 4); + const fixedLengthWithOffset = new ctor(rab, 2 * ctor.BYTES_PER_ELEMENT, 2); + const lengthTracking = new ctor(rab, 0); + const lengthTrackingWithOffset = new ctor(rab, 2 * ctor.BYTES_PER_ELEMENT); + + // Write some data into the array. + let ta_write = new ctor(rab); + for (let i = 0; i < 4; ++i) { + WriteToTypedArray(ta_write, i, i); + } + { + let [a, b, c, d, e] = fixedLength; + assert.compareArray(ToNumbers([ + a, + b, + c, + d + ]), [ + 0, + 1, + 2, + 3 + ]); + assert.sameValue(e, undefined); + } + { + let [a, b, c] = fixedLengthWithOffset; + assert.compareArray(ToNumbers([ + a, + b + ]), [ + 2, + 3 + ]); + assert.sameValue(c, undefined); + } + { + let [a, b, c, d, e] = lengthTracking; + assert.compareArray(ToNumbers([ + a, + b, + c, + d + ]), [ + 0, + 1, + 2, + 3 + ]); + assert.sameValue(e, undefined); + } + { + let [a, b, c] = lengthTrackingWithOffset; + assert.compareArray(ToNumbers([ + a, + b + ]), [ + 2, + 3 + ]); + assert.sameValue(c, undefined); + } + + // Shrink so that fixed length TAs go out of bounds. + rab.resize(3 * ctor.BYTES_PER_ELEMENT); + assert.throws(TypeError, () => { + let [a, b, c] = fixedLength; + }); + assert.throws(TypeError, () => { + let [a, b, c] = fixedLengthWithOffset; + }); + { + let [a, b, c, d] = lengthTracking; + assert.compareArray(ToNumbers([ + a, + b, + c + ]), [ + 0, + 1, + 2 + ]); + assert.sameValue(d, undefined); + } + { + let [a, b] = lengthTrackingWithOffset; + assert.compareArray(ToNumbers([a]), [2]); + assert.sameValue(b, undefined); + } + + // Shrink so that the TAs with offset go out of bounds. + rab.resize(1 * ctor.BYTES_PER_ELEMENT); + assert.throws(TypeError, () => { + let [a, b, c] = fixedLength; + }); + assert.throws(TypeError, () => { + let [a, b, c] = fixedLengthWithOffset; + }); + assert.throws(TypeError, () => { + let [a, b, c] = lengthTrackingWithOffset; + }); + { + let [a, b] = lengthTracking; + assert.compareArray(ToNumbers([a]), [0]); + assert.sameValue(b, undefined); + } + + // Shrink to 0. + rab.resize(0); + assert.throws(TypeError, () => { + let [a, b, c] = fixedLength; + }); + assert.throws(TypeError, () => { + let [a, b, c] = fixedLengthWithOffset; + }); + assert.throws(TypeError, () => { + let [a, b, c] = lengthTrackingWithOffset; + }); + { + let [a] = lengthTracking; + assert.sameValue(a, undefined); + } + + // Grow so that all TAs are back in-bounds. The new memory is zeroed. + rab.resize(6 * ctor.BYTES_PER_ELEMENT); + { + let [a, b, c, d, e] = fixedLength; + assert.compareArray(ToNumbers([ + a, + b, + c, + d + ]), [ + 0, + 0, + 0, + 0 + ]); + assert.sameValue(e, undefined); + } + { + let [a, b, c] = fixedLengthWithOffset; + assert.compareArray(ToNumbers([ + a, + b + ]), [ + 0, + 0 + ]); + assert.sameValue(c, undefined); + } + { + let [a, b, c, d, e, f, g] = lengthTracking; + assert.compareArray(ToNumbers([ + a, + b, + c, + d, + e, + f + ]), [ + 0, + 0, + 0, + 0, + 0, + 0 + ]); + assert.sameValue(g, undefined); + } + { + let [a, b, c, d, e] = lengthTrackingWithOffset; + assert.compareArray(ToNumbers([ + a, + b, + c, + d + ]), [ + 0, + 0, + 0, + 0 + ]); + assert.sameValue(e, undefined); + } +} diff --git a/test/language/statements/for-in/resizable-buffer.js b/test/language/statements/for-in/resizable-buffer.js new file mode 100644 index 00000000000..c6fdb42caeb --- /dev/null +++ b/test/language/statements/for-in/resizable-buffer.js @@ -0,0 +1,54 @@ +// Copyright 2023 the V8 project authors. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-arraybuffer-length +description: > + Indices of TypedArrays backed by resizable buffers are enumerable with + for-in +features: [resizable-arraybuffer] +---*/ + +class MyUint8Array extends Uint8Array { +} + +class MyFloat32Array extends Float32Array { +} + +class MyBigInt64Array extends BigInt64Array { +} + +const builtinCtors = [ + Uint8Array, + Int8Array, + Uint16Array, + Int16Array, + Uint32Array, + Int32Array, + Float32Array, + Float64Array, + Uint8ClampedArray, + BigUint64Array, + BigInt64Array +]; + +const ctors = [ + ...builtinCtors, + MyUint8Array, + MyFloat32Array, + MyBigInt64Array +]; + +function CreateResizableArrayBuffer(byteLength, maxByteLength) { + return new ArrayBuffer(byteLength, { maxByteLength: maxByteLength }); +} + +let rab = CreateResizableArrayBuffer(100, 200); +for (let ctor of ctors) { + const ta = new ctor(rab, 0, 3); + let keys = ''; + for (const key in ta) { + keys += key; + } + assert.sameValue(keys, '012'); +} diff --git a/test/language/statements/for-of/typedarray-backed-by-resizable-buffer-grow-before-end.js b/test/language/statements/for-of/typedarray-backed-by-resizable-buffer-grow-before-end.js new file mode 100644 index 00000000000..20c03a371b6 --- /dev/null +++ b/test/language/statements/for-of/typedarray-backed-by-resizable-buffer-grow-before-end.js @@ -0,0 +1,121 @@ +// Copyright 2023 the V8 project authors. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-arraybuffer-length +description: > + TypedArrays backed by resizable buffers are iterable with for-of and behave + correctly when the buffer is grown during iteration +features: [resizable-arraybuffer] +includes: [compareArray.js] +---*/ + +class MyUint8Array extends Uint8Array { +} + +class MyFloat32Array extends Float32Array { +} + +class MyBigInt64Array extends BigInt64Array { +} + +const builtinCtors = [ + Uint8Array, + Int8Array, + Uint16Array, + Int16Array, + Uint32Array, + Int32Array, + Float32Array, + Float64Array, + Uint8ClampedArray, + BigUint64Array, + BigInt64Array +]; + +const ctors = [ + ...builtinCtors, + MyUint8Array, + MyFloat32Array, + MyBigInt64Array +]; + +function CreateResizableArrayBuffer(byteLength, maxByteLength) { + return new ArrayBuffer(byteLength, { maxByteLength: maxByteLength }); +} + +function WriteToTypedArray(array, index, value) { + if (array instanceof BigInt64Array || array instanceof BigUint64Array) { + array[index] = BigInt(value); + } else { + array[index] = value; + } +} + +function CreateRab(buffer_byte_length, ctor) { + const rab = CreateResizableArrayBuffer(buffer_byte_length, 2 * buffer_byte_length); + let ta_write = new ctor(rab); + for (let i = 0; i < buffer_byte_length / ctor.BYTES_PER_ELEMENT; ++i) { + WriteToTypedArray(ta_write, i, i % 128); + } + return rab; +} + +function TestIterationAndResize(ta, expected, rab, resize_after, new_byte_length) { + let values = []; + let resized = false; + for (const value of ta) { + if (value instanceof Array) { + values.push([ + value[0], + Number(value[1]) + ]); + } else { + values.push(Number(value)); + } + if (!resized && values.length == resize_after) { + rab.resize(new_byte_length); + resized = true; + } + } + assert.compareArray(values, expected); + assert(resized); +} + +const no_elements = 10; +const offset = 2; + +// We need to recreate the RAB between all TA tests, since we grow it. +for (let ctor of ctors) { + const buffer_byte_length = no_elements * ctor.BYTES_PER_ELEMENT; + const byte_offset = offset * ctor.BYTES_PER_ELEMENT; + + // Create various different styles of TypedArrays with the RAB as the + // backing store and iterate them. + + let rab = CreateRab(buffer_byte_length, ctor); + const length_tracking_ta = new ctor(rab); + { + let expected = []; + for (let i = 0; i < no_elements; ++i) { + expected.push(i % 128); + } + // After resizing, the new memory contains zeros. + for (let i = 0; i < no_elements; ++i) { + expected.push(0); + } + TestIterationAndResize(length_tracking_ta, expected, rab, no_elements, buffer_byte_length * 2); + } + rab = CreateRab(buffer_byte_length, ctor); + const length_tracking_ta_with_offset = new ctor(rab, byte_offset); + { + let expected = []; + for (let i = offset; i < no_elements; ++i) { + expected.push(i % 128); + } + for (let i = 0; i < no_elements; ++i) { + expected.push(0); + } + TestIterationAndResize(length_tracking_ta_with_offset, expected, rab, no_elements - offset, buffer_byte_length * 2); + } +} diff --git a/test/language/statements/for-of/typedarray-backed-by-resizable-buffer-grow-mid-iteration.js b/test/language/statements/for-of/typedarray-backed-by-resizable-buffer-grow-mid-iteration.js new file mode 100644 index 00000000000..3422340f593 --- /dev/null +++ b/test/language/statements/for-of/typedarray-backed-by-resizable-buffer-grow-mid-iteration.js @@ -0,0 +1,134 @@ +// Copyright 2023 the V8 project authors. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-arraybuffer-length +description: > + TypedArrays backed by resizable buffers are iterable with for-of and behave + correctly when the buffer is grown during iteration +features: [resizable-arraybuffer] +includes: [compareArray.js] +---*/ + +class MyUint8Array extends Uint8Array { +} + +class MyFloat32Array extends Float32Array { +} + +class MyBigInt64Array extends BigInt64Array { +} + +const builtinCtors = [ + Uint8Array, + Int8Array, + Uint16Array, + Int16Array, + Uint32Array, + Int32Array, + Float32Array, + Float64Array, + Uint8ClampedArray, + BigUint64Array, + BigInt64Array +]; + +const ctors = [ + ...builtinCtors, + MyUint8Array, + MyFloat32Array, + MyBigInt64Array +]; + +function CreateResizableArrayBuffer(byteLength, maxByteLength) { + return new ArrayBuffer(byteLength, { maxByteLength: maxByteLength }); +} + +function WriteToTypedArray(array, index, value) { + if (array instanceof BigInt64Array || array instanceof BigUint64Array) { + array[index] = BigInt(value); + } else { + array[index] = value; + } +} + +function CreateRab(buffer_byte_length, ctor) { + const rab = CreateResizableArrayBuffer(buffer_byte_length, 2 * buffer_byte_length); + let ta_write = new ctor(rab); + for (let i = 0; i < buffer_byte_length / ctor.BYTES_PER_ELEMENT; ++i) { + WriteToTypedArray(ta_write, i, i % 128); + } + return rab; +} + +function TestIterationAndResize(ta, expected, rab, resize_after, new_byte_length) { + let values = []; + let resized = false; + for (const value of ta) { + if (value instanceof Array) { + values.push([ + value[0], + Number(value[1]) + ]); + } else { + values.push(Number(value)); + } + if (!resized && values.length == resize_after) { + rab.resize(new_byte_length); + resized = true; + } + } + assert.compareArray(values, expected); + assert(resized); +} + +const no_elements = 10; +const offset = 2; +for (let ctor of ctors) { + const buffer_byte_length = no_elements * ctor.BYTES_PER_ELEMENT; + const byte_offset = offset * ctor.BYTES_PER_ELEMENT; + + // Create various different styles of TypedArrays with the RAB as the + // backing store and iterate them. + + // Fixed-length TAs aren't affected by resizing. + let rab = CreateRab(buffer_byte_length, ctor); + const ta = new ctor(rab, 0, 3); + TestIterationAndResize(ta, [ + 0, + 1, + 2 + ], rab, 2, buffer_byte_length * 2); + rab = CreateRab(buffer_byte_length, ctor); + const ta_with_offset = new ctor(rab, byte_offset, 3); + TestIterationAndResize(ta_with_offset, [ + 2, + 3, + 4 + ], rab, 2, buffer_byte_length * 2); + rab = CreateRab(buffer_byte_length, ctor); + const length_tracking_ta = new ctor(rab); + { + let expected = []; + for (let i = 0; i < no_elements; ++i) { + expected.push(i % 128); + } + for (let i = 0; i < no_elements; ++i) { + // After resizing, the new memory contains zeros. + expected.push(0); + } + TestIterationAndResize(length_tracking_ta, expected, rab, 2, buffer_byte_length * 2); + } + rab = CreateRab(buffer_byte_length, ctor); + const length_tracking_ta_with_offset = new ctor(rab, byte_offset); + { + let expected = []; + for (let i = offset; i < no_elements; ++i) { + expected.push(i % 128); + } + for (let i = 0; i < no_elements; ++i) { + expected.push(0); + } + TestIterationAndResize(length_tracking_ta_with_offset, expected, rab, 2, buffer_byte_length * 2); + } +} diff --git a/test/language/statements/for-of/typedarray-backed-by-resizable-buffer-shrink-mid-iteration.js b/test/language/statements/for-of/typedarray-backed-by-resizable-buffer-shrink-mid-iteration.js new file mode 100644 index 00000000000..0cbe60e0941 --- /dev/null +++ b/test/language/statements/for-of/typedarray-backed-by-resizable-buffer-shrink-mid-iteration.js @@ -0,0 +1,149 @@ +// Copyright 2023 the V8 project authors. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-arraybuffer-length +description: > + TypedArrays backed by resizable buffers are iterable with for-of and behave + correctly when the buffer is shrunk during iteration +features: [resizable-arraybuffer] +includes: [compareArray.js] +---*/ + +class MyUint8Array extends Uint8Array { +} + +class MyFloat32Array extends Float32Array { +} + +class MyBigInt64Array extends BigInt64Array { +} + +const builtinCtors = [ + Uint8Array, + Int8Array, + Uint16Array, + Int16Array, + Uint32Array, + Int32Array, + Float32Array, + Float64Array, + Uint8ClampedArray, + BigUint64Array, + BigInt64Array +]; + +const ctors = [ + ...builtinCtors, + MyUint8Array, + MyFloat32Array, + MyBigInt64Array +]; + +function CreateResizableArrayBuffer(byteLength, maxByteLength) { + return new ArrayBuffer(byteLength, { maxByteLength: maxByteLength }); +} + +function WriteToTypedArray(array, index, value) { + if (array instanceof BigInt64Array || array instanceof BigUint64Array) { + array[index] = BigInt(value); + } else { + array[index] = value; + } +} + +function CreateRab(buffer_byte_length, ctor) { + const rab = CreateResizableArrayBuffer(buffer_byte_length, 2 * buffer_byte_length); + let ta_write = new ctor(rab); + for (let i = 0; i < buffer_byte_length / ctor.BYTES_PER_ELEMENT; ++i) { + WriteToTypedArray(ta_write, i, i % 128); + } + return rab; +} + +function TestIterationAndResize(ta, expected, rab, resize_after, new_byte_length) { + let values = []; + let resized = false; + for (const value of ta) { + if (value instanceof Array) { + values.push([ + value[0], + Number(value[1]) + ]); + } else { + values.push(Number(value)); + } + if (!resized && values.length == resize_after) { + rab.resize(new_byte_length); + resized = true; + } + } + assert.compareArray(values, expected); + assert(resized); +} + +const no_elements = 10; +const offset = 2; +for (let ctor of ctors) { + const buffer_byte_length = no_elements * ctor.BYTES_PER_ELEMENT; + const byte_offset = offset * ctor.BYTES_PER_ELEMENT; + + // Create various different styles of TypedArrays with the RAB as the + // backing store and iterate them. + + // Fixed-length TAs aren't affected by shrinking if they stay in-bounds. + // They appear detached after shrinking out of bounds. + let rab = CreateRab(buffer_byte_length, ctor); + const ta1 = new ctor(rab, 0, 3); + TestIterationAndResize(ta1, [ + 0, + 1, + 2 + ], rab, 2, buffer_byte_length / 2); + rab = CreateRab(buffer_byte_length, ctor); + const ta2 = new ctor(rab, 0, 3); + assert.throws(TypeError, () => { + TestIterationAndResize(ta2, null, rab, 2, 1); + }); + rab = CreateRab(buffer_byte_length, ctor); + const ta_with_offset1 = new ctor(rab, byte_offset, 3); + TestIterationAndResize(ta_with_offset1, [ + 2, + 3, + 4 + ], rab, 2, buffer_byte_length / 2); + rab = CreateRab(buffer_byte_length, ctor); + const ta_with_offset2 = new ctor(rab, byte_offset, 3); + assert.throws(TypeError, () => { + TestIterationAndResize(ta_with_offset2, null, rab, 2, 0); + }); + + // Length-tracking TA with offset 0 doesn't throw, but its length gracefully + // reduces too. + rab = CreateRab(buffer_byte_length, ctor); + const length_tracking_ta = new ctor(rab); + TestIterationAndResize(length_tracking_ta, [ + 0, + 1, + 2, + 3, + 4 + ], rab, 2, buffer_byte_length / 2); + + // Length-tracking TA appears detached when the buffer is resized beyond the + // offset. + rab = CreateRab(buffer_byte_length, ctor); + const length_tracking_ta_with_offset = new ctor(rab, byte_offset); + assert.throws(TypeError, () => { + TestIterationAndResize(length_tracking_ta_with_offset, null, rab, 2, byte_offset / 2); + }); + + // Length-tracking TA reduces its length gracefully when the buffer is + // resized to barely cover the offset. + rab = CreateRab(buffer_byte_length, ctor); + const length_tracking_ta_with_offset2 = new ctor(rab, byte_offset); + TestIterationAndResize(length_tracking_ta_with_offset2, [ + 2, + 3 + ], rab, 2, byte_offset); +} diff --git a/test/language/statements/for-of/typedarray-backed-by-resizable-buffer-shrink-to-zero-mid-iteration.js b/test/language/statements/for-of/typedarray-backed-by-resizable-buffer-shrink-to-zero-mid-iteration.js new file mode 100644 index 00000000000..80bd5682db2 --- /dev/null +++ b/test/language/statements/for-of/typedarray-backed-by-resizable-buffer-shrink-to-zero-mid-iteration.js @@ -0,0 +1,121 @@ +// Copyright 2023 the V8 project authors. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-arraybuffer-length +description: > + TypedArrays backed by resizable buffers are iterable with for-of and behave + correctly when the buffer is shrunk during iteration +features: [resizable-arraybuffer] +includes: [compareArray.js] +---*/ + +class MyUint8Array extends Uint8Array { +} + +class MyFloat32Array extends Float32Array { +} + +class MyBigInt64Array extends BigInt64Array { +} + +const builtinCtors = [ + Uint8Array, + Int8Array, + Uint16Array, + Int16Array, + Uint32Array, + Int32Array, + Float32Array, + Float64Array, + Uint8ClampedArray, + BigUint64Array, + BigInt64Array +]; + +const ctors = [ + ...builtinCtors, + MyUint8Array, + MyFloat32Array, + MyBigInt64Array +]; + +function CreateResizableArrayBuffer(byteLength, maxByteLength) { + return new ArrayBuffer(byteLength, { maxByteLength: maxByteLength }); +} + +function WriteToTypedArray(array, index, value) { + if (array instanceof BigInt64Array || array instanceof BigUint64Array) { + array[index] = BigInt(value); + } else { + array[index] = value; + } +} + +function CreateRab(buffer_byte_length, ctor) { + const rab = CreateResizableArrayBuffer(buffer_byte_length, 2 * buffer_byte_length); + let ta_write = new ctor(rab); + for (let i = 0; i < buffer_byte_length / ctor.BYTES_PER_ELEMENT; ++i) { + WriteToTypedArray(ta_write, i, i % 128); + } + return rab; +} + +function TestIterationAndResize(ta, expected, rab, resize_after, new_byte_length) { + let values = []; + let resized = false; + for (const value of ta) { + if (value instanceof Array) { + values.push([ + value[0], + Number(value[1]) + ]); + } else { + values.push(Number(value)); + } + if (!resized && values.length == resize_after) { + rab.resize(new_byte_length); + resized = true; + } + } + assert.compareArray(values, expected); + assert(resized); +} + +const no_elements = 10; +const offset = 2; +for (let ctor of ctors) { + const buffer_byte_length = no_elements * ctor.BYTES_PER_ELEMENT; + const byte_offset = offset * ctor.BYTES_PER_ELEMENT; + + // Create various different styles of TypedArrays with the RAB as the + // backing store and iterate them. + + // Fixed-length TAs appear detached after shrinking out of bounds. + let rab = CreateRab(buffer_byte_length, ctor); + const ta = new ctor(rab, 0, 3); + assert.throws(TypeError, () => { + TestIterationAndResize(ta, null, rab, 2, 0); + }); + rab = CreateRab(buffer_byte_length, ctor); + const ta_with_offset = new ctor(rab, byte_offset, 3); + assert.throws(TypeError, () => { + TestIterationAndResize(ta_with_offset, null, rab, 2, 0); + }); + + // Length-tracking TA with offset 0 doesn't throw, but its length gracefully + // goes to zero too. + rab = CreateRab(buffer_byte_length, ctor); + const length_tracking_ta = new ctor(rab); + TestIterationAndResize(length_tracking_ta, [ + 0, + 1 + ], rab, 2, 0); + + // Length-tracking TA which is resized beyond the offset appars detached. + rab = CreateRab(buffer_byte_length, ctor); + const length_tracking_ta_with_offset = new ctor(rab, byte_offset); + assert.throws(TypeError, () => { + TestIterationAndResize(length_tracking_ta_with_offset, null, rab, 2, 0); + }); +} diff --git a/test/language/statements/for-of/typedarray-backed-by-resizable-buffer.js b/test/language/statements/for-of/typedarray-backed-by-resizable-buffer.js new file mode 100644 index 00000000000..c6fddaef7ae --- /dev/null +++ b/test/language/statements/for-of/typedarray-backed-by-resizable-buffer.js @@ -0,0 +1,112 @@ +// Copyright 2023 the V8 project authors. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-arraybuffer-length +description: > + TypedArrays backed by resizable buffers are iterable with for-of +features: [resizable-arraybuffer] +includes: [compareArray.js] +---*/ + +class MyUint8Array extends Uint8Array { +} + +class MyFloat32Array extends Float32Array { +} + +class MyBigInt64Array extends BigInt64Array { +} + +const builtinCtors = [ + Uint8Array, + Int8Array, + Uint16Array, + Int16Array, + Uint32Array, + Int32Array, + Float32Array, + Float64Array, + Uint8ClampedArray, + BigUint64Array, + BigInt64Array +]; + +const ctors = [ + ...builtinCtors, + MyUint8Array, + MyFloat32Array, + MyBigInt64Array +]; + +function CreateResizableArrayBuffer(byteLength, maxByteLength) { + return new ArrayBuffer(byteLength, { maxByteLength: maxByteLength }); +} + +function WriteToTypedArray(array, index, value) { + if (array instanceof BigInt64Array || array instanceof BigUint64Array) { + array[index] = BigInt(value); + } else { + array[index] = value; + } +} + +const no_elements = 10; +const offset = 2; +function TestIteration(ta, expected) { + let values = []; + for (const value of ta) { + values.push(Number(value)); + } + assert.compareArray(values, expected); +} +for (let ctor of ctors) { + const buffer_byte_length = no_elements * ctor.BYTES_PER_ELEMENT; + // We can use the same RAB for all the TAs below, since we won't modify it + // after writing the initial values. + const rab = CreateResizableArrayBuffer(buffer_byte_length, 2 * buffer_byte_length); + const byte_offset = offset * ctor.BYTES_PER_ELEMENT; + + // Write some data into the array. + let ta_write = new ctor(rab); + for (let i = 0; i < no_elements; ++i) { + WriteToTypedArray(ta_write, i, i % 128); + } + + // Create various different styles of TypedArrays with the RAB as the + // backing store and iterate them. + const ta = new ctor(rab, 0, 3); + TestIteration(ta, [ + 0, + 1, + 2 + ]); + const empty_ta = new ctor(rab, 0, 0); + TestIteration(empty_ta, []); + const ta_with_offset = new ctor(rab, byte_offset, 3); + TestIteration(ta_with_offset, [ + 2, + 3, + 4 + ]); + const empty_ta_with_offset = new ctor(rab, byte_offset, 0); + TestIteration(empty_ta_with_offset, []); + const length_tracking_ta = new ctor(rab); + { + let expected = []; + for (let i = 0; i < no_elements; ++i) { + expected.push(i % 128); + } + TestIteration(length_tracking_ta, expected); + } + const length_tracking_ta_with_offset = new ctor(rab, byte_offset); + { + let expected = []; + for (let i = offset; i < no_elements; ++i) { + expected.push(i % 128); + } + TestIteration(length_tracking_ta_with_offset, expected); + } + const empty_length_tracking_ta_with_offset = new ctor(rab, buffer_byte_length); + TestIteration(empty_length_tracking_ta_with_offset, []); +}