From f9ed2c1bae2d174d270679fdea26fa10ab174d1c Mon Sep 17 00:00:00 2001 From: Richard Gibson Date: Thu, 31 Jul 2025 21:23:47 -0400 Subject: [PATCH] [immutable-arraybuffer] ArrayBuffer.prototype.sliceToImmutable --- .../sliceToImmutable/argument-coercion.js | 369 ++++++++++++++++++ .../modify-source-after-return.js | 37 ++ .../sliceToImmutable/not-a-constructor.js | 23 ++ .../prototype/sliceToImmutable/prop-desc.js | 27 ++ .../sliceToImmutable/this-becomes-detached.js | 47 +++ .../prototype/sliceToImmutable/this-grows.js | 54 +++ .../this-has-no-arraybufferdata-internal.js | 66 ++++ .../sliceToImmutable/this-is-not-detached.js | 38 ++ .../sliceToImmutable/this-is-not-object.js | 53 +++ .../this-is-sharedarraybuffer.js | 38 ++ .../sliceToImmutable/this-shrinks.js | 74 ++++ 11 files changed, 826 insertions(+) create mode 100644 test/built-ins/ArrayBuffer/prototype/sliceToImmutable/argument-coercion.js create mode 100644 test/built-ins/ArrayBuffer/prototype/sliceToImmutable/modify-source-after-return.js create mode 100644 test/built-ins/ArrayBuffer/prototype/sliceToImmutable/not-a-constructor.js create mode 100644 test/built-ins/ArrayBuffer/prototype/sliceToImmutable/prop-desc.js create mode 100644 test/built-ins/ArrayBuffer/prototype/sliceToImmutable/this-becomes-detached.js create mode 100644 test/built-ins/ArrayBuffer/prototype/sliceToImmutable/this-grows.js create mode 100644 test/built-ins/ArrayBuffer/prototype/sliceToImmutable/this-has-no-arraybufferdata-internal.js create mode 100644 test/built-ins/ArrayBuffer/prototype/sliceToImmutable/this-is-not-detached.js create mode 100644 test/built-ins/ArrayBuffer/prototype/sliceToImmutable/this-is-not-object.js create mode 100644 test/built-ins/ArrayBuffer/prototype/sliceToImmutable/this-is-sharedarraybuffer.js create mode 100644 test/built-ins/ArrayBuffer/prototype/sliceToImmutable/this-shrinks.js diff --git a/test/built-ins/ArrayBuffer/prototype/sliceToImmutable/argument-coercion.js b/test/built-ins/ArrayBuffer/prototype/sliceToImmutable/argument-coercion.js new file mode 100644 index 00000000000..6af46968999 --- /dev/null +++ b/test/built-ins/ArrayBuffer/prototype/sliceToImmutable/argument-coercion.js @@ -0,0 +1,369 @@ +// Copyright (C) 2025 Richard Gibson. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-arraybuffer.prototype.slicetoimmutable +description: > + Requested start and end are coerced to integers and checked for validity, then + (if valid) an immutable ArrayBuffer with the correct length and contents is + returned +info: | + ArrayBuffer.prototype.sliceToImmutable ( start, end ) + ... + 5. Let len be O.[[ArrayBufferByteLength]]. + 6. Let bounds be ? ResolveBounds(len, start, end). + 7. Let first be bounds.[[From]]. + 8. Let final be bounds.[[To]]. + 9. Let newLen be max(final - first, 0). + ... + 12. Let fromBuf be O.[[ArrayBufferData]]. + 13. Let currentLen be O.[[ArrayBufferByteLength]]. + 14. If currentLen < final, throw a RangeError exception. + 15. Let newBuffer be ? AllocateImmutableArrayBuffer(%ArrayBuffer%, newLen, fromBuf, first, newLen). + 16. Return newBuffer. + + ResolveBounds ( len, start, end ) + 1. Let relativeStart be ? ToIntegerOrInfinity(start). + 2. If relativeStart = -∞, let from be 0. + 3. Else if relativeStart < 0, let from be max(len + relativeStart, 0). + 4. Else, let from be min(relativeStart, len). + 5. If end is undefined, let relativeEnd be len; else let relativeEnd be ? ToIntegerOrInfinity(end). + 6. If relativeEnd = -∞, let to be 0. + 7. Else if relativeEnd < 0, let to be max(len + relativeEnd, 0). + 8. Else, let to be min(relativeEnd, len). + 9. Return the Record { [[From]]: from, [[To]]: to }. + + ToIntegerOrInfinity ( argument ) + 1. Let number be ? ToNumber(argument). + 2. If number is one of NaN, +0𝔽, or -0𝔽, return 0. + 3. If number is +∞𝔽, return +∞. + 4. If number is -∞𝔽, return -∞. + 5. Return truncate(ℝ(number)). +features: [immutable-arraybuffer] +includes: [compareArray.js] +---*/ + +var ab = new ArrayBuffer(8); +assert.sameValue(ab.sliceToImmutable().byteLength, 8, + "Must default to receiver byteLength."); +ab = new ArrayBuffer(8); +assert.sameValue(ab.sliceToImmutable(undefined, undefined).byteLength, 8, + "Must default (undefined, undefined) to receiver byteLength."); + +function repr(value) { + if (typeof value === "string") return JSON.stringify(value); + if (typeof value === "bigint") return String(value) + "n"; + if (!value && 1/value === -Infinity) return "-0"; + return String(value); +} + +var make32ByteArrayBuffer() { + var ab = new ArrayBuffer(32); + var view = new Uint8Array(ab); + for (var i = 0; i < 8; i++) view[i] = i + 1; + return ab; +} + +var goodInputs = [ + // Unmodified non-negative integral numbers + [0, 0], + [1, 1], + [10, 10], + // Truncated non-negative integral numbers + [0.9, 0], + [1.9, 1], + [-0.9, 0], + // Negative integral numbers + [-1, -1], + [-1.9, -1], + [-2.9, -2], + // Coerced to integral numbers + [-0, 0], + [null, 0], + [false, 0], + [true, 1], + ["", 0], + ["8", 8], + ["+9", 9], + ["-9", -9], + ["10e0", 10], + ["+1.1E+1", 11], + ["+.12e2", 12], + ["130e-1", 13], + ["0b1110", 14], + ["0XF", 15], + ["0xf", 15], + ["0o20", 16], + // Coerced to NaN and mapped to 0 + [NaN, 0], + ["7up", 0], + ["1_0", 0], + ["0x00_ff", 0], + // Clamped + [-32, 0], + ["-32", 0], + [-Infinity, 0], + ["-Infinity", 0], + [33, 32], + ["33", 32], + [Infinity, 32], + ["Infinity", 32] +]; + +for (var i = 0; i < goodInputs.length; i++) { + var rawStart = goodInputs[i][0]; + var intStart = goodInputs[i][1]; + for (var j = 0; j < goodInputs.length; j++) { + var rawEnd = goodInputs[j][0]; + var intEnd = goodInputs[j][1]; + var intLength = intEnd - intStart; + var source = make32ByteArrayBuffer(); + var expectContents = Array.from(new Uint8Array(source)).slice(rawStart, rawEnd); + var dest = source.sliceToImmutable(rawStart, rawEnd); + assert.sameValue(dest.byteLength, intLength, + "sliceToImmutable(" + repr(rawStart) + ", " + repr(rawEnd) + ")"); + assert.compareArray(new Uint8Array(dest), expectContents, + "sliceToImmutable(" + repr(rawStart) + ", " + repr(rawEnd) + ")"); + assert.sameValue(dest.immutable, true, + "sliceToImmutable(" + repr(rawStart) + ", " + repr(rawEnd) + ")"); + } +} + +var whitespace = "\t\v\f\uFEFF\u3000\n\r\u2028\u2029"; +function pad(rawInput) { + if (typeof rawInput === "string") { + return { skip: false, string: whitespace + rawInput + whitespace }; + } else if (typeof rawInput === "number") { + return { skip: false, string: whitespace + repr(rawInput) + whitespace }; + } + return { skip: true }; +} +for (var i = 0; i < goodInputs.length; i++) { + var rawStart = goodInputs[i][0]; + var intStart = goodInputs[i][1]; + var paddedStart = pad(rawStart); + if (paddedStart.skip) continue; + for (var j = 0; j < goodInputs.length; j++) { + var rawEnd = goodInputs[j][0]; + var intEnd = goodInputs[j][1]; + var paddedEnd = pad(rawEnd); + if (paddedEnd.skip) continue; + var intLength = intEnd - intStart; + var source = make32ByteArrayBuffer(); + var expectContents = Array.from(new Uint8Array(source)).slice(rawStart, rawEnd); + var dest = source.sliceToImmutable(paddedStart.string, paddedEnd.string); + assert.sameValue(dest.byteLength, intLength, + "sliceToImmutable(" + repr(paddedStart.string) + ", " + repr(paddedEnd.string) + ")"); + assert.compareArray(new Uint8Array(dest), expectContents, + "sliceToImmutable(" + repr(paddedStart.string) + ", " + repr(paddedEnd.string) + ")"); + assert.sameValue(dest.immutable, true, + "sliceToImmutable(" + repr(paddedStart.string) + ", " + repr(paddedEnd.string) + ")"); + } +} + +for (var i = 0; i < goodInputs.length; i++) { + var rawStart = goodInputs[i][0]; + var intStart = goodInputs[i][1]; + for (var j = 0; j < goodInputs.length; j++) { + var rawEnd = goodInputs[j][0]; + var intEnd = goodInputs[j][1]; + var intLength = intEnd - intStart; + + var calls = []; + + var badStartValueOf = false; + var badStartToString = false; + var objStart = { + valueOf() { + calls.push("start.valueOf"); + return badStartValueOf ? {} : rawStart; + }, + toString() { + calls.push("start.toString"); + return badStartToString ? {} : rawStart; + } + }; + var badEndValueOf = false; + var badEndToString = false; + var objEnd = { + valueOf() { + calls.push("end.valueOf"); + return badEndValueOf ? {} : rawEnd; + }, + toString() { + calls.push("end.toString"); + return badEndToString ? {} : rawEnd; + } + }; + function reprArgs(startMethodName, endMethodName) { + var startRepr = "{ " + startMethodName + ": () => " + repr(rawStart) + " }"; + var endRepr = "{ " + endMethodName + ": () => " + repr(rawEnd) + " }"; + return "(" + startRepr + ", " + endRepr + ")"; + } + + var source = make32ByteArrayBuffer(); + var expectContents = Array.from(new Uint8Array(source)).slice(rawStart, rawEnd); + var dest = source.sliceToImmutable(objStart, objEnd); + assert.sameValue(dest.byteLength, intLength, + "sliceToImmutable" + reprArgs("valueOf", "valueOf")); + assert.compareArray(new Uint8Array(dest), expectContents, + "sliceToImmutable" + reprArgs("valueOf", "valueOf")); + assert.sameValue(dest.immutable, true, + "sliceToImmutable" + reprArgs("valueOf", "valueOf")); + assert.compareArray(calls, ["start.valueOf", "end.valueOf"], + "sliceToImmutable" + reprArgs("valueOf", "valueOf")); + + badStartValueOf = true; + calls = []; + source = make32ByteArrayBuffer(); + dest = source.sliceToImmutable(objStart, objEnd); + assert.sameValue(dest.byteLength, intLength, + "sliceToImmutable" + reprArgs("toString", "valueOf")); + assert.compareArray(new Uint8Array(dest), expectContents, + "sliceToImmutable" + reprArgs("toString", "valueOf")); + assert.sameValue(dest.immutable, true, + "sliceToImmutable" + reprArgs("toString", "valueOf")); + assert.compareArray(calls, ["start.valueOf", "start.toString", "end.valueOf"], + "sliceToImmutable" + reprArgs("toString", "valueOf")); + + badEndValueOf = true; + calls = []; + source = make32ByteArrayBuffer(); + dest = source.sliceToImmutable(objStart, objEnd); + assert.sameValue(dest.byteLength, intLength, + "sliceToImmutable" + reprArgs("toString", "toString")); + assert.compareArray(new Uint8Array(dest), expectContents, + "sliceToImmutable" + reprArgs("toString", "toString")); + assert.sameValue(dest.immutable, true, + "sliceToImmutable" + reprArgs("toString", "toString")); + assert.compareArray(calls, ["start.valueOf", "start.toString", "end.valueOf", "end.toString"], + "sliceToImmutable" + reprArgs("toString", "toString")); + + badEndToString = true; + if (typeof Symbol === undefined || !Symbol.toPrimitive) continue; + calls = []; + objEnd[Symbol.toPrimitive] = function(hint) { + calls.push("end[Symbol.toPrimitive](" + hint + ")"); + return rawEnd; + }; + source = make32ByteArrayBuffer(); + dest = source.sliceToImmutable(objStart, objEnd); + assert.sameValue(dest.byteLength, intLength, + "sliceToImmutable" + reprArgs("toString", "[Symbol.toPrimitive]")); + assert.compareArray(new Uint8Array(dest), expectContents, + "sliceToImmutable" + reprArgs("toString", "[Symbol.toPrimitive]")); + assert.sameValue(dest.immutable, true, + "sliceToImmutable" + reprArgs("toString", "[Symbol.toPrimitive]")); + assert.compareArray( + calls, + ["start.valueOf", "start.toString", "end[Symbol.toPrimitive](number)"], + "sliceToImmutable" + reprArgs("toString", "[Symbol.toPrimitive]") + ); + + badStartToString = true; + calls = []; + objStart[Symbol.toPrimitive] = function(hint) { + calls.push("start[Symbol.toPrimitive](" + hint + ")"); + return rawStart; + }; + source = make32ByteArrayBuffer(); + dest = source.sliceToImmutable(objStart, objEnd); + assert.sameValue(dest.byteLength, intLength, + "sliceToImmutable" + reprArgs("[Symbol.toPrimitive]", "[Symbol.toPrimitive]")); + assert.compareArray(new Uint8Array(dest), expectContents, + "sliceToImmutable" + reprArgs("[Symbol.toPrimitive]", "[Symbol.toPrimitive]")); + assert.sameValue(dest.immutable, true, + "sliceToImmutable" + reprArgs("[Symbol.toPrimitive]", "[Symbol.toPrimitive]")); + assert.compareArray( + calls, + ["start[Symbol.toPrimitive](number)", "end[Symbol.toPrimitive](number)"], + "sliceToImmutable" + reprArgs("[Symbol.toPrimitive]", "[Symbol.toPrimitive]")); + ); + } +} + +var badInputs = [ + // Out of range numbers + [-1, RangeError], + [9007199254740992, RangeError], // Math.pow(2, 53) = 9007199254740992 + [Infinity, RangeError], + [-Infinity, RangeError], + // non-numbers + typeof Symbol === undefined ? undefined : [Symbol("1"), TypeError], + typeof Symbol === undefined || !Symbol.for ? undefined : [Symbol.for("1"), TypeError], + typeof BigInt === undefined ? undefined : [BigInt(1), TypeError], +]; + +for (var i = 0; i < badInputs.length; i++) { + if (!badInputs[i]) continue; + var rawBad = badInputs[i][0]; + var expectedErr = badInputs[i][1]; + + var ab = make32ByteArrayBuffer(); + var rawGood = goodInputs[i % goodInputs.length][0]; + var calls = []; + var objGood = { + valueOf() { + calls.push("good.valueOf"); + return rawGood; + } + }; + assert.throws(expectedErr, function() { + ab.sliceToImmutable(rawBad, objGood); + }, "sliceToImmutable(" + repr(rawBad) + ", { valueOf: () => " + repr(rawGood) + " })"); + assert.compareArray(calls, [], + "sliceToImmutable(" + repr(rawBad) + ", { valueOf: () => " + repr(rawGood) + " })"); + ); + + calls = []; + assert.throws(expectedErr, function() { + ab.sliceToImmutable(objGood, rawBad); + }, "sliceToImmutable({ valueOf: () => " + repr(rawGood) + " }, " + repr(rawBad) + ")"); + assert.compareArray(calls, ["good.valueOf"], + "sliceToImmutable({ valueOf: () => " + repr(rawGood) + " }, " + repr(rawBad) + ")"); + ); +} + +for (var i = 0; i < badInputs.length; i++) { + if (!badInputs[i]) continue; + var rawBad = badInputs[i][0]; + var expectedErr = badInputs[i][1]; + if (typeof rawBad !== "number") continue; + rawBad = repr(rawBad); + var paddedBad = whitespace + rawBad + whitespace; + var ab = make32ByteArrayBuffer(); + assert.throws(expectedErr, function() { + ab.sliceToImmutable(paddedBad); + }, "sliceToImmutable(" + repr(paddedBad) + ")"); +} + +var calls = []; +var objBad = { + toString() { + calls.push("toString"); + return {}; + }, + valueOf() { + calls.push("valueOf"); + return {}; + } +}; +ab = make32ByteArrayBuffer(); +assert.throws(TypeError, function() { + ab.sliceToImmutable(objBad); +}, "sliceToImmutable(badOrdinaryToPrimitive)"); +assert.compareArray(calls, ["valueOf", "toString"], + "sliceToImmutable(badOrdinaryToPrimitive)"); +if (typeof Symbol !== undefined && Symbol.toPrimitive) { + calls = []; + objBad[Symbol.toPrimitive] = function(hint) { + calls.push("Symbol.toPrimitive(" + hint + ")"); + return {}; + }; + ab = make32ByteArrayBuffer(); + assert.throws(TypeError, function() { + ab.sliceToImmutable(objBad); + }, "sliceToImmutable(badExoticToPrimitive)"); + assert.compareArray(calls, ["Symbol.toPrimitive(number)"], + "sliceToImmutable(badExoticToPrimitive)"); +} diff --git a/test/built-ins/ArrayBuffer/prototype/sliceToImmutable/modify-source-after-return.js b/test/built-ins/ArrayBuffer/prototype/sliceToImmutable/modify-source-after-return.js new file mode 100644 index 00000000000..d7ee6517000 --- /dev/null +++ b/test/built-ins/ArrayBuffer/prototype/sliceToImmutable/modify-source-after-return.js @@ -0,0 +1,37 @@ +// Copyright (C) 2025 Richard Gibson. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-arraybuffer.prototype.slicetoimmutable +description: Unaffected by post-hoc manipulation of the source +info: | + ArrayBuffer.prototype.sliceToImmutable ( start, end ) + ... + 15. Let newBuffer be ? AllocateImmutableArrayBuffer(%ArrayBuffer%, newLen, fromBuf, first, newLen). + 16. Return newBuffer. +features: [immutable-arraybuffer] +includes: [compareArray.js, detachArrayBuffer.js] +---*/ + +var source = new ArrayBuffer(8, { maxByteLength: 8 }); +var view = new Uint8Array(source); +for (var i = 0; i < 8; i++) view[i] = i + 1; + +var dest = new ArrayBuffer.sliceToImmutable(); +var expectContents = [1, 2, 3, 4, 5, 6, 7, 8]; +var destView = new Uint8Array(dest); +assert.compareArray(destView, expectContents); + +view[0] = 86; +assert.compareArray(destView, expectContents, "after source overwrite"); +assert.compareArray(new Uint8Array(dest), expectContents, "new view after source overwrite"); + +if (source.resize) { + source.resize(4); + assert.compareArray(destView, expectContents, "after resize"); + assert.compareArray(new Uint8Array(dest), expectContents, "new view after resize"); +} + +$DETACHBUFFER(source); +assert.compareArray(destView, expectContents, "after detach"); +assert.compareArray(new Uint8Array(dest), expectContents, "new view after detach"); diff --git a/test/built-ins/ArrayBuffer/prototype/sliceToImmutable/not-a-constructor.js b/test/built-ins/ArrayBuffer/prototype/sliceToImmutable/not-a-constructor.js new file mode 100644 index 00000000000..8823ba52399 --- /dev/null +++ b/test/built-ins/ArrayBuffer/prototype/sliceToImmutable/not-a-constructor.js @@ -0,0 +1,23 @@ +// Copyright (C) 2025 Richard Gibson. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-arraybuffer.prototype.slicetoimmutable +description: ArrayBuffer.prototype.sliceToImmutable is not a constructor function +info: | + ECMAScript Standard Built-in Objects + ... + Built-in function objects that are not identified as constructors do not + implement the [[Construct]] internal method unless otherwise specified in the + description of a particular function. +features: [Reflect.construct, immutable-arraybuffer] +includes: [isConstructor.js] +---*/ + +assert(!isConstructor(ArrayBuffer.prototype.sliceToImmutable), + "ArrayBuffer.prototype.sliceToImmutable is not a constructor"); + +var arrayBuffer = new ArrayBuffer(8); +assert.throws(TypeError, function() { + new arrayBuffer.sliceToImmutable(); +}); diff --git a/test/built-ins/ArrayBuffer/prototype/sliceToImmutable/prop-desc.js b/test/built-ins/ArrayBuffer/prototype/sliceToImmutable/prop-desc.js new file mode 100644 index 00000000000..367f292436a --- /dev/null +++ b/test/built-ins/ArrayBuffer/prototype/sliceToImmutable/prop-desc.js @@ -0,0 +1,27 @@ +// Copyright (C) 2025 Richard Gibson. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-arraybuffer.prototype.slicetoimmutable +description: Checks the "sliceToImmutable" property of ArrayBuffer.prototype +info: | + ECMAScript Standard Built-in Objects + ... + Every built-in function object, including constructors, has a "length" + property whose value is a non-negative integral Number. Unless otherwise + specified, this value is the number of required parameters shown in the + subclause heading for the function description. Optional parameters and rest + parameters are not included in the parameter count. + ... + For functions that are specified as properties of objects, the name value is + the property name string used to access the function. +features: [immutable-arraybuffer] +includes: [propertyHelper.js] +---*/ + +verifyPrimordialCallableProperty( + ArrayBuffer.prototype, + "sliceToImmutable", + "sliceToImmutable", + 2 +); diff --git a/test/built-ins/ArrayBuffer/prototype/sliceToImmutable/this-becomes-detached.js b/test/built-ins/ArrayBuffer/prototype/sliceToImmutable/this-becomes-detached.js new file mode 100644 index 00000000000..d5621726858 --- /dev/null +++ b/test/built-ins/ArrayBuffer/prototype/sliceToImmutable/this-becomes-detached.js @@ -0,0 +1,47 @@ +// Copyright (C) 2025 Richard Gibson. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-arraybuffer.prototype.slicetoimmutable +description: Throws if receiver is detached by bounds resolution +info: | + ArrayBuffer.prototype.sliceToImmutable ( start, end ) + ... + 6. Let bounds be ? ResolveBounds(len, start, end). + ... + 10. NOTE: Side-effects of the above steps may have detached or resized O. + 11. If IsDetachedBuffer(O) is true, throw a TypeError exception. + + ResolveBounds ( len, start, end ) + 1. Let relativeStart be ? ToIntegerOrInfinity(start). + 2. If relativeStart = -∞, let from be 0. + 3. Else if relativeStart < 0, let from be max(len + relativeStart, 0). + 4. Else, let from be min(relativeStart, len). + 5. If end is undefined, let relativeEnd be len; else let relativeEnd be ? ToIntegerOrInfinity(end). + 6. If relativeEnd = -∞, let to be 0. + 7. Else if relativeEnd < 0, let to be max(len + relativeEnd, 0). + 8. Else, let to be min(relativeEnd, len). + 9. Return the Record { [[From]]: from, [[To]]: to }. +features: [immutable-arraybuffer] +includes: [compareArray.js, detachArrayBuffer.js] +---*/ + +var ab = new ArrayBuffer(8); +var calls = []; +var objStart = { + valueOf() { + calls.push("start.valueOf"); + return 0; + } +}; +var objEnd = { + valueOf() { + $DETACHBUFFER(ab); + calls.push("end.valueOf"); + return 1; + } +}; +assert.throws(TypeError, function() { + ab.sliceToImmutable(objStart, objEnd); +}); +assert.compareArray(calls, ["start.valueOf", "end.valueOf"]); diff --git a/test/built-ins/ArrayBuffer/prototype/sliceToImmutable/this-grows.js b/test/built-ins/ArrayBuffer/prototype/sliceToImmutable/this-grows.js new file mode 100644 index 00000000000..11a412cb1fe --- /dev/null +++ b/test/built-ins/ArrayBuffer/prototype/sliceToImmutable/this-grows.js @@ -0,0 +1,54 @@ +// Copyright (C) 2025 Richard Gibson. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-arraybuffer.prototype.slicetoimmutable +description: Calculates bounds from original length if receiver grows. +info: | + ArrayBuffer.prototype.sliceToImmutable ( start, end ) + ... + 5. Let len be O.[[ArrayBufferByteLength]]. + 6. Let bounds be ? ResolveBounds(len, start, end). + 7. Let first be bounds.[[From]]. + 8. Let final be bounds.[[To]]. + 9. Let newLen be max(final - first, 0). + ... + 12. Let fromBuf be O.[[ArrayBufferData]]. + 13. Let currentLen be O.[[ArrayBufferByteLength]]. + 14. If currentLen < final, throw a RangeError exception. + 15. Let newBuffer be ? AllocateImmutableArrayBuffer(%ArrayBuffer%, newLen, fromBuf, first, newLen). + 16. Return newBuffer. + + ResolveBounds ( len, start, end ) + 1. Let relativeStart be ? ToIntegerOrInfinity(start). + 2. If relativeStart = -∞, let from be 0. + 3. Else if relativeStart < 0, let from be max(len + relativeStart, 0). + 4. Else, let from be min(relativeStart, len). + 5. If end is undefined, let relativeEnd be len; else let relativeEnd be ? ToIntegerOrInfinity(end). + 6. If relativeEnd = -∞, let to be 0. + 7. Else if relativeEnd < 0, let to be max(len + relativeEnd, 0). + 8. Else, let to be min(relativeEnd, len). + 9. Return the Record { [[From]]: from, [[To]]: to }. +features: [resizable-arraybuffer, immutable-arraybuffer] +includes: [compareArray.js] +---*/ + +var source = new ArrayBuffer(10, { maxByteLength: 12 }); +var view = new Uint8Array(source); +for (var i = 0; i < 10; i++) view[i] = i + 1; + +var start = { + valueOf() { + source.resize(11); + return -7; + } +}; +var end = { + valueOf() { + source.resize(12); + return -4; + } +}; +var dest = source.sliceToImmutable(start, end); +assert.compareArray(new Uint8Array(dest), [4, 5, 6]); +assert.sameValue(source.byteLength, 12); diff --git a/test/built-ins/ArrayBuffer/prototype/sliceToImmutable/this-has-no-arraybufferdata-internal.js b/test/built-ins/ArrayBuffer/prototype/sliceToImmutable/this-has-no-arraybufferdata-internal.js new file mode 100644 index 00000000000..ef4a4fd2526 --- /dev/null +++ b/test/built-ins/ArrayBuffer/prototype/sliceToImmutable/this-has-no-arraybufferdata-internal.js @@ -0,0 +1,66 @@ +// Copyright (C) 2025 Richard Gibson. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-arraybuffer.prototype.slicetoimmutable +description: > + Throws a TypeError exception when `this` does not have a [[ArrayBufferData]] + internal slot +info: | + ArrayBuffer.prototype.sliceToImmutable ( start, end ) + 1. Let O be the this value. + 2. Perform ? RequireInternalSlot(O, [[ArrayBufferData]]). +features: [DataView, Int8Array, ArrayBuffer, immutable-arraybuffer] +includes: [compareArray.js] +---*/ + +var fn = ArrayBuffer.prototype.sliceToImmutable; +assert.sameValue(typeof fn, "function", "Method must exist."); + +var calls = []; +var start = { + valueOf() { + calls.push("start.valueOf"); + return 0; + } +}; +var end = { + valueOf() { + calls.push("end.valueOf"); + return 1; + } +}; + +var badReceivers = [ + ["plain object", {}], + ["array", []], + ["function", function(){}], + ["ArrayBuffer.prototype", ArrayBuffer.prototype], + ["TypedArray", new Int8Array(8)], + ["DataView", new DataView(new ArrayBuffer(8), 0)] +]; + +for (var i = 0; i < badReceivers.length; i++) { + var label = badReceivers[i][0]; + var value = badReceivers[i][1]; + calls = []; + assert.throws(TypeError, function() { + fn.call(value, start, end); + }, label); + assert.compareArray(calls, [], + "[" + label + " receiver] Must verify internal slots before reading arguments."); +} + +calls = []; +assert.throws(TypeError, function() { + ArrayBuffer.prototype.sliceToImmutable(start, end); +}, "invoked as prototype method"); +assert.compareArray(calls, [], + "[invoked as prototype method] Must verify internal slots before reading arguments."); + +calls = []; +assert.throws(TypeError, function() { + fn(start, end); +}, "invoked as function"); +assert.compareArray(calls, [], + "[invoked as function] Must verify internal slots before reading arguments."); diff --git a/test/built-ins/ArrayBuffer/prototype/sliceToImmutable/this-is-not-detached.js b/test/built-ins/ArrayBuffer/prototype/sliceToImmutable/this-is-not-detached.js new file mode 100644 index 00000000000..dc8435c1605 --- /dev/null +++ b/test/built-ins/ArrayBuffer/prototype/sliceToImmutable/this-is-not-detached.js @@ -0,0 +1,38 @@ +// Copyright (C) 2025 Richard Gibson. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-arraybuffer.prototype.slicetoimmutable +description: > + Throws a TypeError exception when `this` is already detached +info: | + ArrayBuffer.prototype.sliceToImmutable ( start, end ) + 1. Let O be the this value. + 2. Perform ? RequireInternalSlot(O, [[ArrayBufferData]]). + 3. If IsSharedArrayBuffer(O) is true, throw a TypeError exception. + 4. If IsDetachedBuffer(O) is true, throw a TypeError exception. +features: [immutable-arraybuffer] +includes: [compareArray.js, detachArrayBuffer.js] +---*/ + +var calls = []; +var start = { + valueOf() { + calls.push("start.valueOf"); + return 0; + } +}; +var end = { + valueOf() { + calls.push("end.valueOf"); + return 1; + } +}; + +var detached = new ArrayBuffer(8); +$DETACHBUFFER(detached); +assert.throws(TypeError, function() { + detached.sliceToImmutable(start, end); +}); +assert.compareArray(calls, [], + "Must verify non-detachment before reading arguments."); diff --git a/test/built-ins/ArrayBuffer/prototype/sliceToImmutable/this-is-not-object.js b/test/built-ins/ArrayBuffer/prototype/sliceToImmutable/this-is-not-object.js new file mode 100644 index 00000000000..c1727076cca --- /dev/null +++ b/test/built-ins/ArrayBuffer/prototype/sliceToImmutable/this-is-not-object.js @@ -0,0 +1,53 @@ +// Copyright (C) 2025 Richard Gibson. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-arraybuffer.prototype.slicetoimmutable +description: Throws a TypeError exception when `this` is not an object +info: | + ArrayBuffer.prototype.sliceToImmutable ( start, end ) + 1. Let O be the this value. + 2. Perform ? RequireInternalSlot(O, [[ArrayBufferData]]). +features: [ArrayBuffer, immutable-arraybuffer] +includes: [compareArray.js] +---*/ + +var fn = ArrayBuffer.prototype.sliceToImmutable; +assert.sameValue(typeof fn, "function", "Method must exist."); + +var calls = []; +var start = { + valueOf() { + calls.push("start.valueOf"); + return 0; + } +}; +var end = { + valueOf() { + calls.push("end.valueOf"); + return 1; + } +}; + +var badReceivers = [ + ["undefined", undefined], + ["null", null], + ["number", 42], + ["string", "1"], + ["true", true], + ["false", false], + typeof Symbol === "undefined" ? undefined : ["unique symbol", Symbol("1")], + typeof Symbol === "undefined" || !Symbol.for ? undefined : ["registered symbol", Symbol.for("1")], + typeof BigInt === "undefined" ? undefined : ["bigint", BigInt(1)] +]; + +for (var i = 0; i < badReceivers.length; i++) { + var label = badReceivers[i][0]; + var value = badReceivers[i][1]; + calls = []; + assert.throws(TypeError, function() { + fn.call(value, start, end); + }, label); + assert.compareArray(calls, [], + "[" + label + " receiver] Must verify internal slots before reading arguments."); +} diff --git a/test/built-ins/ArrayBuffer/prototype/sliceToImmutable/this-is-sharedarraybuffer.js b/test/built-ins/ArrayBuffer/prototype/sliceToImmutable/this-is-sharedarraybuffer.js new file mode 100644 index 00000000000..13b1e3cdfb7 --- /dev/null +++ b/test/built-ins/ArrayBuffer/prototype/sliceToImmutable/this-is-sharedarraybuffer.js @@ -0,0 +1,38 @@ +// Copyright (C) 2025 Richard Gibson. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-arraybuffer.prototype.slicetoimmutable +description: Throws a TypeError exception when `this` is a SharedArrayBuffer +info: | + ArrayBuffer.prototype.sliceToImmutable ( start, end ) + 1. Let O be the this value. + 2. Perform ? RequireInternalSlot(O, [[ArrayBufferData]]). + 3. If IsSharedArrayBuffer(O) is true, throw a TypeError exception. +features: [SharedArrayBuffer, ArrayBuffer, immutable-arraybuffer] +includes: [compareArray.js] +---*/ + +var fn = ArrayBuffer.prototype.sliceToImmutable; +assert.sameValue(typeof fn, "function", "Method must exist."); + +var calls = []; +var start = { + valueOf() { + calls.push("start.valueOf"); + return 0; + } +}; +var end = { + valueOf() { + calls.push("end.valueOf"); + return 1; + } +}; + +var sab = new SharedArrayBuffer(4); +assert.throws(TypeError, function() { + fn.call(sab, start, end); +}); +assert.compareArray(calls, [], + "Must verify non-SharedArrayBuffer receiver before reading arguments."); diff --git a/test/built-ins/ArrayBuffer/prototype/sliceToImmutable/this-shrinks.js b/test/built-ins/ArrayBuffer/prototype/sliceToImmutable/this-shrinks.js new file mode 100644 index 00000000000..98d7d4906bb --- /dev/null +++ b/test/built-ins/ArrayBuffer/prototype/sliceToImmutable/this-shrinks.js @@ -0,0 +1,74 @@ +// Copyright (C) 2025 Richard Gibson. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +esid: sec-arraybuffer.prototype.slicetoimmutable +description: Calculates bounds from original length if receiver shrinks. +info: | + ArrayBuffer.prototype.sliceToImmutable ( start, end ) + ... + 5. Let len be O.[[ArrayBufferByteLength]]. + 6. Let bounds be ? ResolveBounds(len, start, end). + 7. Let first be bounds.[[From]]. + 8. Let final be bounds.[[To]]. + 9. Let newLen be max(final - first, 0). + ... + 12. Let fromBuf be O.[[ArrayBufferData]]. + 13. Let currentLen be O.[[ArrayBufferByteLength]]. + 14. If currentLen < final, throw a RangeError exception. + 15. Let newBuffer be ? AllocateImmutableArrayBuffer(%ArrayBuffer%, newLen, fromBuf, first, newLen). + 16. Return newBuffer. + + ResolveBounds ( len, start, end ) + 1. Let relativeStart be ? ToIntegerOrInfinity(start). + 2. If relativeStart = -∞, let from be 0. + 3. Else if relativeStart < 0, let from be max(len + relativeStart, 0). + 4. Else, let from be min(relativeStart, len). + 5. If end is undefined, let relativeEnd be len; else let relativeEnd be ? ToIntegerOrInfinity(end). + 6. If relativeEnd = -∞, let to be 0. + 7. Else if relativeEnd < 0, let to be max(len + relativeEnd, 0). + 8. Else, let to be min(relativeEnd, len). + 9. Return the Record { [[From]]: from, [[To]]: to }. +features: [resizable-arraybuffer, immutable-arraybuffer] +includes: [compareArray.js, detachArrayBuffer.js] +---*/ + +var source = new ArrayBuffer(10, { maxByteLength: 10 }); +var view = new Uint8Array(source); +for (var i = 0; i < 10; i++) view[i] = i + 1; + +var startResizeTo = 9; +var endResizeTo = 8; +var start = { + valueOf() { + source.resize(startResizeTo); + return -7; + } +}; +var end = { + valueOf() { + source.resize(endResizeTo); + return -4; + } +}; +var dest = source.sliceToImmutable(start, end); +assert.compareArray(new Uint8Array(dest), [4, 5, 6]); +assert.sameValue(source.byteLength, 8); + +source.resize(10); +endResizeTo = 5; +assert.throws(RangeError, function() { + source.sliceToImmutable(start, end); +}, "resize below resolved end"); + +source.resize(10); +end = { + valueOf() { + source.resize(5); + $DETACHBUFFER(source); + return 6; + } +}; +assert.throws(TypeError, function() { + source.sliceToImmutable(0, end); +}, "Must verify non-detachment before final bounds check");