From fe479fe79351475bc402f9216463e40fbba5c638 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marja=20H=C3=B6ltt=C3=A4?= Date: Thu, 3 Mar 2022 15:08:15 +0100 Subject: [PATCH] [rab/gsab] RAB/GSAB support in TA.p.subarray MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bug: v8:11111 Change-Id: I58b76ce0ad47eb47ccbd0244b110f7cb0450ced8 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3468349 Reviewed-by: Shu-yu Guo Reviewed-by: Jakob Kummerow Commit-Queue: Marja Hölttä Cr-Commit-Position: refs/heads/main@{#79350} --- src/builtins/typed-array-subarray.tq | 31 +- .../typedarray-growablesharedarraybuffer.js | 104 ++++++ .../typedarray-resizablearraybuffer-detach.js | 115 +++++++ .../typedarray-resizablearraybuffer.js | 315 +++++++++++++++++- 4 files changed, 549 insertions(+), 16 deletions(-) diff --git a/src/builtins/typed-array-subarray.tq b/src/builtins/typed-array-subarray.tq index 46ce383b50..85ff74dc9c 100644 --- a/src/builtins/typed-array-subarray.tq +++ b/src/builtins/typed-array-subarray.tq @@ -20,42 +20,49 @@ transitioning javascript builtin TypedArrayPrototypeSubArray( const buffer = typed_array::GetTypedArrayBuffer(source); // 6. Let srcLength be O.[[ArrayLength]]. - const srcLength: uintptr = source.length; + let srcLength: uintptr; + try { + srcLength = LoadJSTypedArrayLengthAndCheckDetached(source) + otherwise DetachedOrOutOfBounds; + } label DetachedOrOutOfBounds { + // 7. If srcLength is out-of-bounds, set srcLength to 0. + srcLength = 0; + } - // 7. Let relativeBegin be ? ToInteger(begin). - // 8. If relativeBegin < 0, let beginIndex be max((srcLength + + // 8. Let relativeBegin be ? ToInteger(begin). + // 9. If relativeBegin < 0, let beginIndex be max((srcLength + // relativeBegin), 0); else let beginIndex be min(relativeBegin, // srcLength). const arg0 = arguments[0]; const begin: uintptr = arg0 != Undefined ? ConvertToRelativeIndex(arg0, srcLength) : 0; - // 9. If end is undefined, let relativeEnd be srcLength; + // 10. If end is undefined, let relativeEnd be srcLength; // else, let relativeEnd be ? ToInteger(end). - // 10. If relativeEnd < 0, let endIndex be max((srcLength + relativeEnd), + // 11. If relativeEnd < 0, let endIndex be max((srcLength + relativeEnd), // 0); else let endIndex be min(relativeEnd, srcLength). const arg1 = arguments[1]; const end: uintptr = arg1 != Undefined ? ConvertToRelativeIndex(arg1, srcLength) : srcLength; - // 11. Let newLength be max(endIndex - beginIndex, 0). + // 12. Let newLength be max(endIndex - beginIndex, 0). const newLength: uintptr = Unsigned(IntPtrMax(Signed(end - begin), 0)); - // 12. Let constructorName be the String value of O.[[TypedArrayName]]. - // 13. Let elementSize be the Number value of the Element Size value + // 13. Let constructorName be the String value of O.[[TypedArrayName]]. + // 14. Let elementSize be the Number value of the Element Size value // specified in Table 52 for constructorName. const elementsInfo = typed_array::GetTypedArrayElementsInfo(source); - // 14. Let srcByteOffset be O.[[ByteOffset]]. + // 15. Let srcByteOffset be O.[[ByteOffset]]. const srcByteOffset: uintptr = source.byte_offset; - // 15. Let beginByteOffset be srcByteOffset + beginIndex × elementSize. + // 16. Let beginByteOffset be srcByteOffset + beginIndex × elementSize. const beginByteOffset = srcByteOffset + elementsInfo.CalculateByteLength(begin) otherwise ThrowRangeError(MessageTemplate::kInvalidArrayBufferLength); - // 16. Let argumentsList be « buffer, beginByteOffset, newLength ». - // 17. Return ? TypedArraySpeciesCreate(O, argumentsList). + // 17. Let argumentsList be « buffer, beginByteOffset, newLength ». + // 18. Return ? TypedArraySpeciesCreate(O, argumentsList). return TypedArraySpeciesCreateByBuffer( methodName, source, buffer, beginByteOffset, newLength); } diff --git a/test/mjsunit/typedarray-growablesharedarraybuffer.js b/test/mjsunit/typedarray-growablesharedarraybuffer.js index e7afde1cd8..af8c39980d 100644 --- a/test/mjsunit/typedarray-growablesharedarraybuffer.js +++ b/test/mjsunit/typedarray-growablesharedarraybuffer.js @@ -3260,3 +3260,107 @@ function TestIterationAndGrow(ta, expected, gsab, grow_after, } } })(); + +(function Subarray() { + for (let ctor of ctors) { + const gsab = CreateGrowableSharedArrayBuffer(4 * ctor.BYTES_PER_ELEMENT, + 8 * ctor.BYTES_PER_ELEMENT); + const fixedLength = new ctor(gsab, 0, 4); + const fixedLengthWithOffset = new ctor(gsab, 2 * ctor.BYTES_PER_ELEMENT, 2); + const lengthTracking = new ctor(gsab, 0); + const lengthTrackingWithOffset = new ctor(gsab, 2 * ctor.BYTES_PER_ELEMENT); + + // Write some data into the array. + const taWrite = new ctor(gsab); + for (let i = 0; i < 4; ++i) { + WriteToTypedArray(taWrite, i, 2 * i); + } + + // Orig. array: [0, 2, 4, 6] + // [0, 2, 4, 6] << fixedLength + // [4, 6] << fixedLengthWithOffset + // [0, 2, 4, 6, ...] << lengthTracking + // [4, 6, ...] << lengthTrackingWithOffset + + const fixedLengthSubFull = fixedLength.subarray(0); + assertEquals([0, 2, 4, 6], ToNumbers(fixedLengthSubFull)); + const fixedLengthWithOffsetSubFull = fixedLengthWithOffset.subarray(0); + assertEquals([4, 6], ToNumbers(fixedLengthWithOffsetSubFull)); + const lengthTrackingSubFull = lengthTracking.subarray(0); + assertEquals([0, 2, 4, 6], ToNumbers(lengthTrackingSubFull)); + const lengthTrackingWithOffsetSubFull = + lengthTrackingWithOffset.subarray(0); + assertEquals([4, 6], ToNumbers(lengthTrackingWithOffsetSubFull)); + + // Relative offsets + assertEquals([4, 6], ToNumbers(fixedLength.subarray(-2))); + assertEquals([6], ToNumbers(fixedLengthWithOffset.subarray(-1))); + assertEquals([4, 6], ToNumbers(lengthTracking.subarray(-2))); + assertEquals([6], ToNumbers(lengthTrackingWithOffset.subarray(-1))); + + // Grow. + gsab.grow(6 * ctor.BYTES_PER_ELEMENT); + for (let i = 0; i < 6; ++i) { + WriteToTypedArray(taWrite, i, 2 * i); + } + + // Orig. array: [0, 2, 4, 6, 8, 10] + // [0, 2, 4, 6] << fixedLength + // [4, 6] << fixedLengthWithOffset + // [0, 2, 4, 6, 8, 10, ...] << lengthTracking + // [4, 6, 8, 10, ...] << lengthTrackingWithOffset + + assertEquals([0, 2, 4, 6], ToNumbers(fixedLength.subarray(0))); + assertEquals([4, 6], ToNumbers(fixedLengthWithOffset.subarray(0))); + assertEquals([0, 2, 4, 6, 8, 10], ToNumbers(lengthTracking.subarray(0))); + assertEquals([4, 6, 8, 10], + ToNumbers(lengthTrackingWithOffset.subarray(0))); + + assertEquals(4, fixedLengthSubFull.length); + assertEquals(2, fixedLengthWithOffsetSubFull.length); + + // TODO(v8:11111): Are subarrays of length-tracking TAs also + // length-tracking? See + // https://github.com/tc39/proposal-resizablearraybuffer/issues/91 + assertEquals(4, lengthTrackingSubFull.length); + assertEquals(2, lengthTrackingWithOffsetSubFull.length); + } +})(); + +(function SubarrayParameterConversionGrows() { + // Orig. array: [0, 2, 4, 6] + // [0, 2, 4, 6] << fixedLength + // [0, 2, 4, 6, ...] << lengthTracking + function CreateGsabForTest(ctor) { + const gsab = CreateGrowableSharedArrayBuffer(4 * ctor.BYTES_PER_ELEMENT, + 8 * ctor.BYTES_PER_ELEMENT); + // Write some data into the array. + const taWrite = new ctor(gsab); + for (let i = 0; i < 4; ++i) { + WriteToTypedArray(taWrite, i, 2 * i); + } + return gsab; + } + + // Growing + fixed-length TA. Growing won't affect anything. + for (let ctor of ctors) { + const gsab = CreateGsabForTest(ctor); + const fixedLength = new ctor(gsab, 0, 4); + + const evil = { valueOf: () => { gsab.grow(6 * ctor.BYTES_PER_ELEMENT); + return 0;}}; + assertEquals([0, 2, 4, 6], ToNumbers(fixedLength.subarray(evil))); + } + + // Growing + length-tracking TA. The length computation is done with the + // original length. + for (let ctor of ctors) { + const gsab = CreateGsabForTest(ctor); + const lengthTracking = new ctor(gsab, 0); + + const evil = { valueOf: () => { gsab.grow(6 * ctor.BYTES_PER_ELEMENT); + return 0;}}; + + assertEquals([0, 2, 4, 6], ToNumbers(lengthTracking.subarray(evil))); + } +})(); diff --git a/test/mjsunit/typedarray-resizablearraybuffer-detach.js b/test/mjsunit/typedarray-resizablearraybuffer-detach.js index f23b2aeccd..df161a4625 100644 --- a/test/mjsunit/typedarray-resizablearraybuffer-detach.js +++ b/test/mjsunit/typedarray-resizablearraybuffer-detach.js @@ -1329,3 +1329,118 @@ d8.file.execute('test/mjsunit/typedarray-helpers.js'); TypeError); } })(); + +(function Subarray() { + 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. + const taWrite = new ctor(rab); + for (let i = 0; i < 4; ++i) { + WriteToTypedArray(taWrite, i, 2 * i); + } + + // Orig. array: [0, 2, 4, 6] + // [0, 2, 4, 6] << fixedLength + // [4, 6] << fixedLengthWithOffset + // [0, 2, 4, 6, ...] << lengthTracking + // [4, 6, ...] << lengthTrackingWithOffset + + const fixedLengthSubFull = fixedLength.subarray(0); + assertEquals([0, 2, 4, 6], ToNumbers(fixedLengthSubFull)); + const fixedLengthWithOffsetSubFull = fixedLengthWithOffset.subarray(0); + assertEquals([4, 6], ToNumbers(fixedLengthWithOffsetSubFull)); + const lengthTrackingSubFull = lengthTracking.subarray(0); + assertEquals([0, 2, 4, 6], ToNumbers(lengthTrackingSubFull)); + const lengthTrackingWithOffsetSubFull = + lengthTrackingWithOffset.subarray(0); + assertEquals([4, 6], ToNumbers(lengthTrackingWithOffsetSubFull)); + + %ArrayBufferDetach(rab); + + // The previously created subarrays are OOB. + assertEquals(0, fixedLengthSubFull.length); + assertEquals(0, fixedLengthWithOffsetSubFull.length); + assertEquals(0, lengthTrackingSubFull.length); + assertEquals(0, lengthTrackingWithOffsetSubFull.length); + + // Trying to create new subarrays fails. + assertThrows(() => { fixedLength.subarray(0); }, TypeError); + assertThrows(() => { fixedLengthWithOffset.subarray(0); }, TypeError); + assertThrows(() => { lengthTracking.subarray(0); }, TypeError); + assertThrows(() => { lengthTrackingWithOffset.subarray(0); }, TypeError); + } +})(); + +(function SubarrayParameterConversionDetaches() { + // Orig. array: [0, 2, 4, 6] + // [0, 2, 4, 6] << fixedLength + // [0, 2, 4, 6, ...] << lengthTracking + function CreateRabForTest(ctor) { + const rab = CreateResizableArrayBuffer(4 * ctor.BYTES_PER_ELEMENT, + 8 * ctor.BYTES_PER_ELEMENT); + // Write some data into the array. + const taWrite = new ctor(rab); + for (let i = 0; i < 4; ++i) { + WriteToTypedArray(taWrite, i, 2 * i); + } + return rab; + } + + // Fixed-length TA + first parameter conversion detaches. Can't construct + // even zero-length TAs with a detached buffer. + for (let ctor of ctors) { + const rab = CreateRabForTest(ctor); + const fixedLength = new ctor(rab, 0, 4); + + let evil = { valueOf: () => { + %ArrayBufferDetach(rab); + return 0; + }}; + assertThrows(() => { fixedLength.subarray(evil, 0); }, TypeError); + } + + // Length-tracking TA + first parameter conversion detaches. Can't construct + // even zero-length TAs with a detached buffer. + for (let ctor of ctors) { + const rab = CreateRabForTest(ctor); + const fixedLength = new ctor(rab, 0, 4); + + let evil = { valueOf: () => { + %ArrayBufferDetach(rab); + return 0; + }}; + assertThrows(() => { fixedLength.subarray(evil, 0); }, TypeError); + } + + // Fixed-length TA + second parameter conversion detaches. Can't construct + // even zero-length TAs with a detached buffer. + for (let ctor of ctors) { + const rab = CreateRabForTest(ctor); + const fixedLength = new ctor(rab, 0, 4); + + let evil = { valueOf: () => { + %ArrayBufferDetach(rab); + return 0; + }}; + assertThrows(() => { fixedLength.subarray(0, evil); }, TypeError); + } + + // Length-tracking TA + second parameter conversion detaches. Can't construct + // even zero-length TAs with a detached buffer. + for (let ctor of ctors) { + const rab = CreateRabForTest(ctor); + const fixedLength = new ctor(rab, 0, 4); + + let evil = { valueOf: () => { + %ArrayBufferDetach(rab); + return 0; + }}; + assertThrows(() => { fixedLength.subarray(0, evil); }, TypeError); + } +})(); diff --git a/test/mjsunit/typedarray-resizablearraybuffer.js b/test/mjsunit/typedarray-resizablearraybuffer.js index 39138a55c0..369c16d848 100644 --- a/test/mjsunit/typedarray-resizablearraybuffer.js +++ b/test/mjsunit/typedarray-resizablearraybuffer.js @@ -4422,7 +4422,7 @@ function TestIterationAndResize(ta, expected, rab, resize_after, })(); (function IndexOfParameterConversionShrinks() { - // Shinking + fixed-length TA. + // Shrinking + fixed-length TA. for (let ctor of ctors) { const rab = CreateResizableArrayBuffer(4 * ctor.BYTES_PER_ELEMENT, 8 * ctor.BYTES_PER_ELEMENT); @@ -4471,7 +4471,7 @@ function TestIterationAndResize(ta, expected, rab, resize_after, })(); (function LastIndexOfParameterConversionShrinks() { - // Shinking + fixed-length TA. + // Shrinking + fixed-length TA. for (let ctor of ctors) { const rab = CreateResizableArrayBuffer(4 * ctor.BYTES_PER_ELEMENT, 8 * ctor.BYTES_PER_ELEMENT); @@ -4713,7 +4713,7 @@ function TestIterationAndResize(ta, expected, rab, resize_after, })(); (function JoinParameterConversionShrinks() { - // Shinking + fixed-length TA. + // Shrinking + fixed-length TA. for (let ctor of ctors) { const rab = CreateResizableArrayBuffer(4 * ctor.BYTES_PER_ELEMENT, 8 * ctor.BYTES_PER_ELEMENT); @@ -4778,7 +4778,7 @@ function TestIterationAndResize(ta, expected, rab, resize_after, const oldNumberPrototypeToLocaleString = Number.prototype.toLocaleString; const oldBigIntPrototypeToLocaleString = BigInt.prototype.toLocaleString; - // Shinking + fixed-length TA. + // Shrinking + fixed-length TA. for (let ctor of ctors) { const rab = CreateResizableArrayBuffer(4 * ctor.BYTES_PER_ELEMENT, 8 * ctor.BYTES_PER_ELEMENT); @@ -6001,3 +6001,310 @@ function TestIterationAndResize(ta, expected, rab, resize_after, } } })(); + +(function Subarray() { + 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. + const taWrite = new ctor(rab); + for (let i = 0; i < 4; ++i) { + WriteToTypedArray(taWrite, i, 2 * i); + } + + // Orig. array: [0, 2, 4, 6] + // [0, 2, 4, 6] << fixedLength + // [4, 6] << fixedLengthWithOffset + // [0, 2, 4, 6, ...] << lengthTracking + // [4, 6, ...] << lengthTrackingWithOffset + + const fixedLengthSubFull = fixedLength.subarray(0); + assertEquals([0, 2, 4, 6], ToNumbers(fixedLengthSubFull)); + const fixedLengthWithOffsetSubFull = fixedLengthWithOffset.subarray(0); + assertEquals([4, 6], ToNumbers(fixedLengthWithOffsetSubFull)); + const lengthTrackingSubFull = lengthTracking.subarray(0); + assertEquals([0, 2, 4, 6], ToNumbers(lengthTrackingSubFull)); + const lengthTrackingWithOffsetSubFull = + lengthTrackingWithOffset.subarray(0); + assertEquals([4, 6], ToNumbers(lengthTrackingWithOffsetSubFull)); + + // Relative offsets + assertEquals([4, 6], ToNumbers(fixedLength.subarray(-2))); + assertEquals([6], ToNumbers(fixedLengthWithOffset.subarray(-1))); + assertEquals([4, 6], ToNumbers(lengthTracking.subarray(-2))); + assertEquals([6], ToNumbers(lengthTrackingWithOffset.subarray(-1))); + + // Shrink so that fixed length TAs go out of bounds. + rab.resize(3 * ctor.BYTES_PER_ELEMENT); + + // Orig. array: [0, 2, 4] + // [0, 2, 4, ...] << lengthTracking + // [4, ...] << lengthTrackingWithOffset + + // We can create subarrays of OOB arrays (which have length 0), as long as + // the new arrays are not OOB. + assertEquals([], ToNumbers(fixedLength.subarray(0))); + assertEquals([], ToNumbers(fixedLengthWithOffset.subarray(0))); + + assertEquals([0, 2, 4], ToNumbers(lengthTracking.subarray(0))); + assertEquals([4], ToNumbers(lengthTrackingWithOffset.subarray(0))); + + // Also the previously created subarrays are OOB. + assertEquals(0, fixedLengthSubFull.length); + assertEquals(0, fixedLengthWithOffsetSubFull.length); + + // Relative offsets + assertEquals([2, 4], ToNumbers(lengthTracking.subarray(-2))); + assertEquals([4], ToNumbers(lengthTrackingWithOffset.subarray(-1))); + + // Shrink so that the TAs with offset go out of bounds. + rab.resize(1 * ctor.BYTES_PER_ELEMENT); + + assertEquals([], ToNumbers(fixedLength.subarray(0))); + assertEquals([0], ToNumbers(lengthTracking.subarray(0))); + + // Even the 0-length subarray of fixedLengthWithOffset would be OOB -> + // this throws. + assertThrows(() => { fixedLengthWithOffset.subarray(0); }, RangeError); + + // Also the previously created subarrays are OOB. + assertEquals(0, fixedLengthSubFull.length); + assertEquals(0, fixedLengthWithOffsetSubFull.length); + assertEquals(0, lengthTrackingWithOffsetSubFull.length); + + // Shrink to zero. + rab.resize(0); + + assertEquals([], ToNumbers(fixedLength.subarray(0))); + assertEquals([], ToNumbers(lengthTracking.subarray(0))); + + assertThrows(() => { fixedLengthWithOffset.subarray(0); }, RangeError); + assertThrows(() => { lengthTrackingWithOffset.subarray(0); }, RangeError); + + // Also the previously created subarrays are OOB. + assertEquals(0, fixedLengthSubFull.length); + assertEquals(0, fixedLengthWithOffsetSubFull.length); + assertEquals(0, lengthTrackingWithOffsetSubFull.length); + + // Grow so that all TAs are back in-bounds. + rab.resize(6 * ctor.BYTES_PER_ELEMENT); + for (let i = 0; i < 6; ++i) { + WriteToTypedArray(taWrite, i, 2 * i); + } + + // Orig. array: [0, 2, 4, 6, 8, 10] + // [0, 2, 4, 6] << fixedLength + // [4, 6] << fixedLengthWithOffset + // [0, 2, 4, 6, 8, 10, ...] << lengthTracking + // [4, 6, 8, 10, ...] << lengthTrackingWithOffset + + assertEquals([0, 2, 4, 6], ToNumbers(fixedLength.subarray(0))); + assertEquals([4, 6], ToNumbers(fixedLengthWithOffset.subarray(0))); + assertEquals([0, 2, 4, 6, 8, 10], ToNumbers(lengthTracking.subarray(0))); + assertEquals([4, 6, 8, 10], + ToNumbers(lengthTrackingWithOffset.subarray(0))); + + // Also the previously created subarrays are no longer OOB. + assertEquals(4, fixedLengthSubFull.length); + assertEquals(2, fixedLengthWithOffsetSubFull.length); + + // TODO(v8:11111): Are subarrays of length-tracking TAs also + // length-tracking? See + // https://github.com/tc39/proposal-resizablearraybuffer/issues/91 + assertEquals(4, lengthTrackingSubFull.length); + assertEquals(2, lengthTrackingWithOffsetSubFull.length); + } +})(); + +(function SubarrayParameterConversionShrinks() { + // Orig. array: [0, 2, 4, 6] + // [0, 2, 4, 6] << fixedLength + // [0, 2, 4, 6, ...] << lengthTracking + function CreateRabForTest(ctor) { + const rab = CreateResizableArrayBuffer(4 * ctor.BYTES_PER_ELEMENT, + 8 * ctor.BYTES_PER_ELEMENT); + // Write some data into the array. + const taWrite = new ctor(rab); + for (let i = 0; i < 4; ++i) { + WriteToTypedArray(taWrite, i, 2 * i); + } + return rab; + } + + // Fixed-length TA + first parameter conversion shrinks. The old length is + // used in the length computation, and the subarray construction fails. + for (let ctor of ctors) { + const rab = CreateRabForTest(ctor); + const fixedLength = new ctor(rab, 0, 4); + + let evil = { valueOf: () => { + rab.resize(2 * ctor.BYTES_PER_ELEMENT); + return 0; + }}; + assertThrows(() => { fixedLength.subarray(evil); }, RangeError); + } + + // Like the previous test, but now we construct a smaller subarray and it + // succeeds. + for (let ctor of ctors) { + const rab = CreateRabForTest(ctor); + const fixedLength = new ctor(rab, 0, 4); + + let evil = { valueOf: () => { + rab.resize(2 * ctor.BYTES_PER_ELEMENT); + return 0; + }}; + assertEquals([0], ToNumbers(fixedLength.subarray(evil, 1))); + } + + // Fixed-length TA + second parameter conversion shrinks. The old length is + // used in the length computation, and the subarray construction fails. + for (let ctor of ctors) { + const rab = CreateRabForTest(ctor); + const fixedLength = new ctor(rab, 0, 4); + + let evil = { valueOf: () => { + rab.resize(2 * ctor.BYTES_PER_ELEMENT); + return 3; + }}; + assertThrows(() => { fixedLength.subarray(0, evil); }, RangeError); + } + + // Like the previous test, but now we construct a smaller subarray and it + // succeeds. + for (let ctor of ctors) { + const rab = CreateRabForTest(ctor); + const fixedLength = new ctor(rab, 0, 4); + + let evil = { valueOf: () => { + rab.resize(2 * ctor.BYTES_PER_ELEMENT); + return 1; + }}; + assertEquals([0], ToNumbers(fixedLength.subarray(0, evil))); + } + + // Shrinking + fixed-length TA, subarray construction succeeds even though the + // TA goes OOB. + for (let ctor of ctors) { + const rab = CreateRabForTest(ctor); + const fixedLength = new ctor(rab, 0, 4); + + const evil = { valueOf: () => { rab.resize(2 * ctor.BYTES_PER_ELEMENT); + return 0;}}; + + assertEquals([0], ToNumbers(fixedLength.subarray(evil, 1))); + } + + // Length-tracking TA + first parameter conversion shrinks. The old length is + // used in the length computation, and the subarray construction fails. + for (let ctor of ctors) { + const rab = CreateRabForTest(ctor); + const lengthTracking = new ctor(rab); + + let evil = { valueOf: () => { + rab.resize(2 * ctor.BYTES_PER_ELEMENT); + return 0; + }}; + assertThrows(() => { lengthTracking.subarray(evil); }); + } + + // Like the previous test, but now we construct a smaller subarray and it + // succeeds. + for (let ctor of ctors) { + const rab = CreateRabForTest(ctor); + const lengthTracking = new ctor(rab); + + let evil = { valueOf: () => { + rab.resize(2 * ctor.BYTES_PER_ELEMENT); + return 0; + }}; + assertEquals([0], ToNumbers(lengthTracking.subarray(evil, 1))); + } + + // Length-tracking TA + first parameter conversion shrinks. The second + // parameter is negative -> the relative index is not recomputed, and the + // subarray construction fails. + for (let ctor of ctors) { + const rab = CreateRabForTest(ctor); + const lengthTracking = new ctor(rab); + + let evil = { valueOf: () => { + rab.resize(2 * ctor.BYTES_PER_ELEMENT); + return 0; + }}; + assertThrows(() => { lengthTracking.subarray(evil, -1); }); + } + + // Length-tracking TA + second parameter conversion shrinks. The second + // parameter is too large -> the subarray construction fails. + for (let ctor of ctors) { + const rab = CreateRabForTest(ctor); + const lengthTracking = new ctor(rab); + + let evil = { valueOf: () => { + rab.resize(2 * ctor.BYTES_PER_ELEMENT); + return 3; + }}; + assertThrows(() => { lengthTracking.subarray(0, evil); }); + } +})(); + +(function SubarrayParameterConversionGrows() { + // Orig. array: [0, 2, 4, 6] + // [0, 2, 4, 6] << fixedLength + // [0, 2, 4, 6, ...] << lengthTracking + function CreateRabForTest(ctor) { + const rab = CreateResizableArrayBuffer(4 * ctor.BYTES_PER_ELEMENT, + 8 * ctor.BYTES_PER_ELEMENT); + // Write some data into the array. + const taWrite = new ctor(rab); + for (let i = 0; i < 4; ++i) { + WriteToTypedArray(taWrite, i, 2 * i); + } + return rab; + } + + // Growing a fixed length TA back in bounds. + for (let ctor of ctors) { + const rab = CreateRabForTest(ctor); + const fixedLength = new ctor(rab, 0, 4); + + // Make `fixedLength` OOB. + rab.resize(2 * ctor.BYTES_PER_ELEMENT); + + const evil = { valueOf: () => { rab.resize(4 * ctor.BYTES_PER_ELEMENT); + return 0;}}; + + // The length computation is done before parameter conversion. At that + // point, the length is 0, since the TA is OOB. + assertEquals([], ToNumbers(fixedLength.subarray(evil, 0, 1))); + } + + // Growing + fixed-length TA. Growing won't affect anything. + for (let ctor of ctors) { + const rab = CreateRabForTest(ctor); + const fixedLength = new ctor(rab, 0, 4); + + const evil = { valueOf: () => { rab.resize(6 * ctor.BYTES_PER_ELEMENT); + return 0;}}; + + assertEquals([0, 2, 4, 6], ToNumbers(fixedLength.subarray(evil))); + } + + // Growing + length-tracking TA. The length computation is done with the + // original length. + for (let ctor of ctors) { + const rab = CreateRabForTest(ctor); + const lengthTracking = new ctor(rab, 0); + + const evil = { valueOf: () => { rab.resize(6 * ctor.BYTES_PER_ELEMENT); + return 0;}}; + + assertEquals([0, 2, 4, 6], ToNumbers(lengthTracking.subarray(evil))); + } +})();