Skip to content

Commit 51629c7

Browse files
committed
[immutable-arraybuffer] ArrayBuffer.prototype.sliceToImmutable
1 parent f807ed7 commit 51629c7

11 files changed

+826
-0
lines changed
Lines changed: 369 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,369 @@
1+
// Copyright (C) 2025 Richard Gibson. All rights reserved.
2+
// This code is governed by the BSD license found in the LICENSE file.
3+
4+
/*---
5+
esid: sec-arraybuffer.prototype.slicetoimmutable
6+
description: >
7+
Requested start and end are coerced to integers and checked for validity, then
8+
(if valid) an immutable ArrayBuffer with the correct length and contents is
9+
returned
10+
info: |
11+
ArrayBuffer.prototype.sliceToImmutable ( start, end )
12+
...
13+
5. Let len be O.[[ArrayBufferByteLength]].
14+
6. Let bounds be ? ResolveBounds(len, start, end).
15+
7. Let first be bounds.[[From]].
16+
8. Let final be bounds.[[To]].
17+
9. Let newLen be max(final - first, 0).
18+
...
19+
12. Let fromBuf be O.[[ArrayBufferData]].
20+
13. Let currentLen be O.[[ArrayBufferByteLength]].
21+
14. If currentLen < final, throw a RangeError exception.
22+
15. Let newBuffer be ? AllocateImmutableArrayBuffer(%ArrayBuffer%, newLen, fromBuf, first, newLen).
23+
16. Return newBuffer.
24+
25+
ResolveBounds ( len, start, end )
26+
1. Let relativeStart be ? ToIntegerOrInfinity(start).
27+
2. If relativeStart = -∞, let from be 0.
28+
3. Else if relativeStart < 0, let from be max(len + relativeStart, 0).
29+
4. Else, let from be min(relativeStart, len).
30+
5. If end is undefined, let relativeEnd be len; else let relativeEnd be ? ToIntegerOrInfinity(end).
31+
6. If relativeEnd = -∞, let to be 0.
32+
7. Else if relativeEnd < 0, let to be max(len + relativeEnd, 0).
33+
8. Else, let to be min(relativeEnd, len).
34+
9. Return the Record { [[From]]: from, [[To]]: to }.
35+
36+
ToIntegerOrInfinity ( argument )
37+
1. Let number be ? ToNumber(argument).
38+
2. If number is one of NaN, +0𝔽, or -0𝔽, return 0.
39+
3. If number is +∞𝔽, return +∞.
40+
4. If number is -∞𝔽, return -∞.
41+
5. Return truncate(ℝ(number)).
42+
features: [immutable-arraybuffer]
43+
includes: [compareArray.js]
44+
---*/
45+
46+
var ab = new ArrayBuffer(8);
47+
assert.sameValue(ab.sliceToImmutable().byteLength, 8,
48+
"Must default to receiver byteLength.");
49+
ab = new ArrayBuffer(8);
50+
assert.sameValue(ab.sliceToImmutable(undefined, undefined).byteLength, 8,
51+
"Must default (undefined, undefined) to receiver byteLength.");
52+
53+
function repr(value) {
54+
if (typeof value === "string") return JSON.stringify(value);
55+
if (typeof value === "bigint") return String(value) + "n";
56+
if (!value && 1/value === -Infinity) return "-0";
57+
return String(value);
58+
}
59+
60+
var make32ByteArrayBuffer() {
61+
var ab = new ArrayBuffer(32);
62+
var view = new Uint8Array(ab);
63+
for (var i = 0; i < 8; i++) view[i] = i + 1;
64+
return ab;
65+
}
66+
67+
var goodInputs = [
68+
// Unmodified non-negative integral numbers
69+
[0, 0],
70+
[1, 1],
71+
[10, 10],
72+
// Truncated non-negative integral numbers
73+
[0.9, 0],
74+
[1.9, 1],
75+
[-0.9, 0],
76+
// Negative integral numbers
77+
[-1, -1],
78+
[-1.9, -1],
79+
[-2.9, -2],
80+
// Coerced to integral numbers
81+
[-0, 0],
82+
[null, 0],
83+
[false, 0],
84+
[true, 1],
85+
["", 0],
86+
["8", 8],
87+
["+9", 9],
88+
["-9", -9],
89+
["10e0", 10],
90+
["+1.1E+1", 11],
91+
["+.12e2", 12],
92+
["130e-1", 13],
93+
["0b1110", 14],
94+
["0XF", 15],
95+
["0xf", 15],
96+
["0o20", 16],
97+
// Coerced to NaN and mapped to 0
98+
[NaN, 0],
99+
["7up", 0],
100+
["1_0", 0],
101+
["0x00_ff", 0],
102+
// Clamped
103+
[-32, 0],
104+
["-32", 0],
105+
[-Infinity, 0],
106+
["-Infinity", 0],
107+
[33, 32],
108+
["33", 32],
109+
[Infinity, 32],
110+
["Infinity", 32]
111+
];
112+
113+
for (var i = 0; i < goodInputs.length; i++) {
114+
var rawStart = goodInputs[i][0];
115+
var intStart = goodInputs[i][1];
116+
for (var j = 0; j < goodInputs.length; j++) {
117+
var rawEnd = goodInputs[j][0];
118+
var intEnd = goodInputs[j][1];
119+
var intLength = intEnd - intStart;
120+
var source = make32ByteArrayBuffer();
121+
var expectContents = Array.from(new Uint8Array(source)).slice(rawStart, rawEnd);
122+
var dest = source.sliceToImmutable(rawStart, rawEnd);
123+
assert.sameValue(dest.byteLength, intLength,
124+
"sliceToImmutable(" + repr(rawStart) + ", " + repr(rawEnd) + ")");
125+
assert.compareArray(new Uint8Array(dest), expectContents,
126+
"sliceToImmutable(" + repr(rawStart) + ", " + repr(rawEnd) + ")");
127+
assert.sameValue(dest.immutable, true,
128+
"sliceToImmutable(" + repr(rawStart) + ", " + repr(rawEnd) + ")");
129+
}
130+
}
131+
132+
var whitespace = "\t\v\f\uFEFF\u3000\n\r\u2028\u2029";
133+
function pad(rawInput) {
134+
if (typeof rawInput === "string") {
135+
return { skip: false, string: whitespace + rawInput + whitespace };
136+
} else if (typeof rawInput === "number") {
137+
return { skip: false, string: whitespace + repr(rawInput) + whitespace };
138+
}
139+
return { skip: true };
140+
}
141+
for (var i = 0; i < goodInputs.length; i++) {
142+
var rawStart = goodInputs[i][0];
143+
var intStart = goodInputs[i][1];
144+
var paddedStart = pad(rawStart);
145+
if (paddedStart.skip) continue;
146+
for (var j = 0; j < goodInputs.length; j++) {
147+
var rawEnd = goodInputs[j][0];
148+
var intEnd = goodInputs[j][1];
149+
var paddedEnd = pad(rawEnd);
150+
if (paddedEnd.skip) continue;
151+
var intLength = intEnd - intStart;
152+
var source = make32ByteArrayBuffer();
153+
var expectContents = Array.from(new Uint8Array(source)).slice(rawStart, rawEnd);
154+
var dest = source.sliceToImmutable(paddedStart.string, paddedEnd.string);
155+
assert.sameValue(dest.byteLength, intLength,
156+
"sliceToImmutable(" + repr(paddedStart.string) + ", " + repr(paddedEnd.string) + ")");
157+
assert.compareArray(new Uint8Array(dest), expectContents,
158+
"sliceToImmutable(" + repr(paddedStart.string) + ", " + repr(paddedEnd.string) + ")");
159+
assert.sameValue(dest.immutable, true,
160+
"sliceToImmutable(" + repr(paddedStart.string) + ", " + repr(paddedEnd.string) + ")");
161+
}
162+
}
163+
164+
for (var i = 0; i < goodInputs.length; i++) {
165+
var rawStart = goodInputs[i][0];
166+
var intStart = goodInputs[i][1];
167+
for (var j = 0; j < goodInputs.length; j++) {
168+
var rawEnd = goodInputs[j][0];
169+
var intEnd = goodInputs[j][1];
170+
var intLength = intEnd - intStart;
171+
172+
var calls = [];
173+
174+
var badStartValueOf = false;
175+
var badStartToString = false;
176+
var objStart = {
177+
valueOf() {
178+
calls.push("start.valueOf");
179+
return badStartValueOf ? {} : rawStart;
180+
},
181+
toString() {
182+
calls.push("start.toString");
183+
return badStartToString ? {} : rawStart;
184+
}
185+
};
186+
var badEndValueOf = false;
187+
var badEndToString = false;
188+
var objEnd = {
189+
valueOf() {
190+
calls.push("end.valueOf");
191+
return badEndValueOf ? {} : rawEnd;
192+
},
193+
toString() {
194+
calls.push("end.toString");
195+
return badEndToString ? {} : rawEnd;
196+
}
197+
};
198+
function reprArgs(startMethodName, endMethodName) {
199+
var startRepr = "{ " + startMethodName + ": () => " + repr(rawStart) + " }";
200+
var endRepr = "{ " + endMethodName + ": () => " + repr(rawEnd) + " }";
201+
return "(" + startRepr + ", " + endRepr + ")";
202+
}
203+
204+
var source = make32ByteArrayBuffer();
205+
var expectContents = Array.from(new Uint8Array(source)).slice(rawStart, rawEnd);
206+
var dest = source.sliceToImmutable(objStart, objEnd);
207+
assert.sameValue(dest.byteLength, intLength,
208+
"sliceToImmutable" + reprArgs("valueOf", "valueOf"));
209+
assert.compareArray(new Uint8Array(dest), expectContents,
210+
"sliceToImmutable" + reprArgs("valueOf", "valueOf"));
211+
assert.sameValue(dest.immutable, true,
212+
"sliceToImmutable" + reprArgs("valueOf", "valueOf"));
213+
assert.compareArray(calls, ["start.valueOf", "end.valueOf"],
214+
"sliceToImmutable" + reprArgs("valueOf", "valueOf"));
215+
216+
badStartValueOf = true;
217+
calls = [];
218+
source = make32ByteArrayBuffer();
219+
dest = source.sliceToImmutable(objStart, objEnd);
220+
assert.sameValue(dest.byteLength, intLength,
221+
"sliceToImmutable" + reprArgs("toString", "valueOf"));
222+
assert.compareArray(new Uint8Array(dest), expectContents,
223+
"sliceToImmutable" + reprArgs("toString", "valueOf"));
224+
assert.sameValue(dest.immutable, true,
225+
"sliceToImmutable" + reprArgs("toString", "valueOf"));
226+
assert.compareArray(calls, ["start.valueOf", "start.toString", "end.valueOf"],
227+
"sliceToImmutable" + reprArgs("toString", "valueOf"));
228+
229+
badEndValueOf = true;
230+
calls = [];
231+
source = make32ByteArrayBuffer();
232+
dest = source.sliceToImmutable(objStart, objEnd);
233+
assert.sameValue(dest.byteLength, intLength,
234+
"sliceToImmutable" + reprArgs("toString", "toString"));
235+
assert.compareArray(new Uint8Array(dest), expectContents,
236+
"sliceToImmutable" + reprArgs("toString", "toString"));
237+
assert.sameValue(dest.immutable, true,
238+
"sliceToImmutable" + reprArgs("toString", "toString"));
239+
assert.compareArray(calls, ["start.valueOf", "start.toString", "end.valueOf", "end.toString"],
240+
"sliceToImmutable" + reprArgs("toString", "toString"));
241+
242+
badEndToString = true;
243+
if (typeof Symbol === undefined || !Symbol.toPrimitive) continue;
244+
calls = [];
245+
objEnd[Symbol.toPrimitive] = function(hint) {
246+
calls.push("end[Symbol.toPrimitive](" + hint + ")");
247+
return rawEnd;
248+
};
249+
source = make32ByteArrayBuffer();
250+
dest = source.sliceToImmutable(objStart, objEnd);
251+
assert.sameValue(dest.byteLength, intLength,
252+
"sliceToImmutable" + reprArgs("toString", "[Symbol.toPrimitive]"));
253+
assert.compareArray(new Uint8Array(dest), expectContents,
254+
"sliceToImmutable" + reprArgs("toString", "[Symbol.toPrimitive]"));
255+
assert.sameValue(dest.immutable, true,
256+
"sliceToImmutable" + reprArgs("toString", "[Symbol.toPrimitive]"));
257+
assert.compareArray(
258+
calls,
259+
["start.valueOf", "start.toString", "end[Symbol.toPrimitive](number)"],
260+
"sliceToImmutable" + reprArgs("toString", "[Symbol.toPrimitive]")
261+
);
262+
263+
badStartToString = true;
264+
calls = [];
265+
objStart[Symbol.toPrimitive] = function(hint) {
266+
calls.push("start[Symbol.toPrimitive](" + hint + ")");
267+
return rawStart;
268+
};
269+
source = make32ByteArrayBuffer();
270+
dest = source.sliceToImmutable(objStart, objEnd);
271+
assert.sameValue(dest.byteLength, intLength,
272+
"sliceToImmutable" + reprArgs("[Symbol.toPrimitive]", "[Symbol.toPrimitive]"));
273+
assert.compareArray(new Uint8Array(dest), expectContents,
274+
"sliceToImmutable" + reprArgs("[Symbol.toPrimitive]", "[Symbol.toPrimitive]"));
275+
assert.sameValue(dest.immutable, true,
276+
"sliceToImmutable" + reprArgs("[Symbol.toPrimitive]", "[Symbol.toPrimitive]"));
277+
assert.compareArray(
278+
calls,
279+
["start[Symbol.toPrimitive](number)", "end[Symbol.toPrimitive](number)"],
280+
"sliceToImmutable" + reprArgs("[Symbol.toPrimitive]", "[Symbol.toPrimitive]"));
281+
);
282+
}
283+
}
284+
285+
var badInputs = [
286+
// Out of range numbers
287+
[-1, RangeError],
288+
[9007199254740992, RangeError], // Math.pow(2, 53) = 9007199254740992
289+
[Infinity, RangeError],
290+
[-Infinity, RangeError],
291+
// non-numbers
292+
typeof Symbol === undefined ? undefined : [Symbol("1"), TypeError],
293+
typeof Symbol === undefined || !Symbol.for ? undefined : [Symbol.for("1"), TypeError],
294+
typeof BigInt === undefined ? undefined : [BigInt(1), TypeError],
295+
];
296+
297+
for (var i = 0; i < badInputs.length; i++) {
298+
if (!badInputs[i]) continue;
299+
var rawBad = badInputs[i][0];
300+
var expectedErr = badInputs[i][1];
301+
302+
var ab = make32ByteArrayBuffer();
303+
var rawGood = goodInputs[i % goodInputs.length][0];
304+
var calls = [];
305+
var objGood = {
306+
valueOf() {
307+
calls.push("good.valueOf");
308+
return rawGood;
309+
}
310+
};
311+
assert.throws(expectedErr, function() {
312+
ab.sliceToImmutable(rawBad, objGood);
313+
}, "sliceToImmutable(" + repr(rawBad) + ", { valueOf: () => " + repr(rawGood) + " })");
314+
assert.compareArray(calls, [],
315+
"sliceToImmutable(" + repr(rawBad) + ", { valueOf: () => " + repr(rawGood) + " })");
316+
);
317+
318+
calls = [];
319+
assert.throws(expectedErr, function() {
320+
ab.sliceToImmutable(objGood, rawBad);
321+
}, "sliceToImmutable({ valueOf: () => " + repr(rawGood) + " }, " + repr(rawBad) + ")");
322+
assert.compareArray(calls, ["good.valueOf"],
323+
"sliceToImmutable({ valueOf: () => " + repr(rawGood) + " }, " + repr(rawBad) + ")");
324+
);
325+
}
326+
327+
for (var i = 0; i < badInputs.length; i++) {
328+
if (!badInputs[i]) continue;
329+
var rawBad = badInputs[i][0];
330+
var expectedErr = badInputs[i][1];
331+
if (typeof rawBad !== "number") continue;
332+
rawBad = repr(rawBad);
333+
var paddedBad = whitespace + rawBad + whitespace;
334+
var ab = make32ByteArrayBuffer();
335+
assert.throws(expectedErr, function() {
336+
ab.sliceToImmutable(paddedBad);
337+
}, "sliceToImmutable(" + repr(paddedBad) + ")");
338+
}
339+
340+
var calls = [];
341+
var objBad = {
342+
toString() {
343+
calls.push("toString");
344+
return {};
345+
},
346+
valueOf() {
347+
calls.push("valueOf");
348+
return {};
349+
}
350+
};
351+
ab = make32ByteArrayBuffer();
352+
assert.throws(TypeError, function() {
353+
ab.sliceToImmutable(objBad);
354+
}, "sliceToImmutable(badOrdinaryToPrimitive)");
355+
assert.compareArray(calls, ["valueOf", "toString"],
356+
"sliceToImmutable(badOrdinaryToPrimitive)");
357+
if (typeof Symbol !== undefined && Symbol.toPrimitive) {
358+
calls = [];
359+
objBad[Symbol.toPrimitive] = function(hint) {
360+
calls.push("Symbol.toPrimitive(" + hint + ")");
361+
return {};
362+
};
363+
ab = make32ByteArrayBuffer();
364+
assert.throws(TypeError, function() {
365+
ab.sliceToImmutable(objBad);
366+
}, "sliceToImmutable(badExoticToPrimitive)");
367+
assert.compareArray(calls, ["Symbol.toPrimitive(number)"],
368+
"sliceToImmutable(badExoticToPrimitive)");
369+
}

0 commit comments

Comments
 (0)