Skip to content

Commit fae11cb

Browse files
authored
[immutable-arraybuffer] ArrayBuffer.prototype.immutable (#4541)
1 parent f569285 commit fae11cb

7 files changed

Lines changed: 326 additions & 21 deletions

File tree

harness/propertyHelper.js

Lines changed: 159 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ description: |
77
defines:
88
- verifyProperty
99
- verifyCallableProperty
10+
- verifyAccessorProperty
1011
- verifyEqualTo # deprecated
1112
- verifyWritable # deprecated
1213
- verifyNotWritable # deprecated
@@ -16,6 +17,7 @@ defines:
1617
- verifyNotConfigurable # deprecated
1718
- verifyPrimordialProperty
1819
- verifyPrimordialCallableProperty
20+
- verifyPrimordialAccessorProperty
1921
---*/
2022

2123
// @ts-check
@@ -37,33 +39,31 @@ var nonIndexNumericPropertyName = Math.pow(2, 32) - 1;
3739
* @param {string|symbol} name
3840
* @param {PropertyDescriptor|undefined} desc
3941
* @param {object} [options]
42+
* @param {boolean} [options.label]
4043
* @param {boolean} [options.restore] revert mutations from verifying writable/configurable
4144
*/
4245
function verifyProperty(obj, name, desc, options) {
4346
assert(
4447
arguments.length > 2,
4548
'verifyProperty should receive at least 3 arguments: obj, name, and descriptor'
4649
);
50+
var label = options && options.label || String(name);
4751

4852
var originalDesc = __getOwnPropertyDescriptor(obj, name);
49-
var nameStr = String(name);
5053

5154
// Allows checking for undefined descriptor if it's explicitly given.
5255
if (desc === undefined) {
5356
assert.sameValue(
5457
originalDesc,
5558
undefined,
56-
"obj['" + nameStr + "'] descriptor should be undefined"
59+
label + " descriptor should be undefined"
5760
);
5861

5962
// desc and originalDesc are both undefined, problem solved;
6063
return true;
6164
}
6265

63-
assert(
64-
__hasOwnProperty(obj, name),
65-
"obj should have an own property " + nameStr
66-
);
66+
assert(__hasOwnProperty(obj, name), label + " should be an own property");
6767

6868
assert.notSameValue(
6969
desc,
@@ -94,17 +94,17 @@ function verifyProperty(obj, name, desc, options) {
9494

9595
if (__hasOwnProperty(desc, 'value')) {
9696
if (!isSameValue(desc.value, originalDesc.value)) {
97-
__push(failures, "obj['" + nameStr + "'] descriptor value should be " + desc.value);
97+
__push(failures, label + " descriptor value should be " + String(desc.value));
9898
}
9999
if (!isSameValue(desc.value, obj[name])) {
100-
__push(failures, "obj['" + nameStr + "'] value should be " + desc.value);
100+
__push(failures, label + " value should be " + String(desc.value));
101101
}
102102
}
103103

104104
if (__hasOwnProperty(desc, 'enumerable') && desc.enumerable !== undefined) {
105105
if (desc.enumerable !== originalDesc.enumerable ||
106106
desc.enumerable !== isEnumerable(obj, name)) {
107-
__push(failures, "obj['" + nameStr + "'] descriptor should " + (desc.enumerable ? '' : 'not ') + "be enumerable");
107+
__push(failures, label + " descriptor should " + (desc.enumerable ? '' : 'not ') + "be enumerable");
108108
}
109109
}
110110

@@ -113,14 +113,14 @@ function verifyProperty(obj, name, desc, options) {
113113
if (__hasOwnProperty(desc, 'writable') && desc.writable !== undefined) {
114114
if (desc.writable !== originalDesc.writable ||
115115
desc.writable !== isWritable(obj, name)) {
116-
__push(failures, "obj['" + nameStr + "'] descriptor should " + (desc.writable ? '' : 'not ') + "be writable");
116+
__push(failures, label + " descriptor should " + (desc.writable ? '' : 'not ') + "be writable");
117117
}
118118
}
119119

120120
if (__hasOwnProperty(desc, 'configurable') && desc.configurable !== undefined) {
121121
if (desc.configurable !== originalDesc.configurable ||
122122
desc.configurable !== isConfigurable(obj, name)) {
123-
__push(failures, "obj['" + nameStr + "'] descriptor should " + (desc.configurable ? '' : 'not ') + "be configurable");
123+
__push(failures, label + " descriptor should " + (desc.configurable ? '' : 'not ') + "be configurable");
124124
}
125125
}
126126

@@ -219,13 +219,17 @@ function isWritable(obj, name, verifyProp, value) {
219219
* @param {number} functionLength
220220
* @param {PropertyDescriptor} [desc] defaults to data property conventions (writable, non-enumerable, configurable)
221221
* @param {object} [options]
222+
* @param {boolean} [options.label]
223+
* @param {typeof verifyProperty} [options.verifyProperty]
222224
* @param {boolean} [options.restore] revert mutations from verifying writable/configurable
223225
*/
224226
function verifyCallableProperty(obj, name, functionName, functionLength, desc, options) {
225-
var value = obj[name];
227+
var label = options && options.label || String(name);
228+
var propertyVerifier = options && options.verifyProperty || verifyProperty;
229+
230+
var value = obj && obj[name];
226231

227-
assert.sameValue(typeof value, "function",
228-
"obj['" + String(name) + "'] descriptor should be a function");
232+
assert.sameValue(typeof value, "function", label + " should be a function");
229233

230234
// Every other data property described in clauses 19 through 28 and in
231235
// Annex B.2 has the attributes { [[Writable]]: true, [[Enumerable]]: false,
@@ -242,7 +246,7 @@ function verifyCallableProperty(obj, name, functionName, functionLength, desc, o
242246
desc.value = value;
243247
}
244248

245-
verifyProperty(obj, name, desc, options);
249+
propertyVerifier(obj, name, desc, options);
246250

247251
if (functionName === undefined) {
248252
if (typeof name === "symbol") {
@@ -256,24 +260,125 @@ function verifyCallableProperty(obj, name, functionName, functionLength, desc, o
256260
// [[Configurable]]: true }.
257261
// https://tc39.es/ecma262/multipage/ecmascript-standard-built-in-objects.html#sec-ecmascript-standard-built-in-objects
258262
// https://tc39.es/ecma262/multipage/ordinary-and-exotic-objects-behaviours.html#sec-setfunctionname
259-
verifyProperty(value, "name", {
263+
propertyVerifier(value, "name", {
260264
value: functionName,
261265
writable: false,
262266
enumerable: false,
263267
configurable: desc.configurable
264-
}, options);
268+
}, { label: label + " name", restore: options && options.restore });
265269

266270
// Unless otherwise specified, the "length" property of a built-in function
267271
// object has the attributes { [[Writable]]: false, [[Enumerable]]: false,
268272
// [[Configurable]]: true }.
269273
// https://tc39.es/ecma262/multipage/ecmascript-standard-built-in-objects.html#sec-ecmascript-standard-built-in-objects
270274
// https://tc39.es/ecma262/multipage/ordinary-and-exotic-objects-behaviours.html#sec-setfunctionlength
271-
verifyProperty(value, "length", {
275+
propertyVerifier(value, "length", {
272276
value: functionLength,
273277
writable: false,
274278
enumerable: false,
275279
configurable: desc.configurable
276-
}, options);
280+
}, { label: label + " length", restore: options && options.restore });
281+
}
282+
283+
/**
284+
* Verify that there is an accessor property associated with `obj[name]` and
285+
* following the conventions for built-in objects.
286+
*
287+
* @param {object} obj
288+
* @param {string|symbol} name
289+
* @param {object} desc
290+
* @param {boolean} [desc.enumerable] defaults to accessor property conventions (non-enumerable)
291+
* @param {boolean} [desc.configurable] defaults to accessor property conventions (configurable)
292+
* @param {undefined | Function | {name?: string|symbol, length?: number}} [desc.get] if an object,
293+
* absent fields default to getter conventions (name derived from the property key with a "get "
294+
* prefix, length 0)
295+
* @param {undefined | Function | {name?: string|symbol, length?: number}} [desc.set] if an object,
296+
* absent fields default to getter conventions (name derived from the property key with a "set "
297+
* prefix, length 1)
298+
* @param {object} [options]
299+
* @param {boolean} [options.label]
300+
* @param {typeof verifyProperty} [options.verifyProperty]
301+
* @param {typeof verifyCallableProperty} [options.verifyCallableProperty]
302+
* @param {boolean} [options.restore] revert mutations from verifying property attributes
303+
*/
304+
function verifyAccessorProperty(obj, name, desc, options) {
305+
var checkGet = __hasOwnProperty(desc, "get");
306+
var checkSet = __hasOwnProperty(desc, "set");
307+
assert(
308+
checkGet || checkSet,
309+
'verifyAccessorProperty requires at least one of "get" and "set"'
310+
);
311+
var label = options && options.label || String(name);
312+
var propertyVerifier = options && options.verifyProperty || verifyProperty;
313+
var callabilityVerifier = options && options.verifyCallableProperty || verifyCallableProperty;
314+
315+
var originalDesc = __getOwnPropertyDescriptor(obj, name);
316+
317+
// Every built-in function object, including constructors, has a "name"
318+
// property whose value is a String. Unless otherwise specified, this value is
319+
// the name that is given to the function in this specification. Functions
320+
// that are identified as anonymous functions use the empty String as the
321+
// value of the "name" property. For functions that are specified as
322+
// properties of objects, the name value is the property name string used to
323+
// access the function. Functions that are specified as get or set accessor
324+
// functions of built-in properties have "get" or "set" (respectively) passed
325+
// to the prefix parameter when calling CreateBuiltinFunction.
326+
//
327+
// The value of the "name" property is explicitly specified for each built-in
328+
// functions whose property key is a Symbol value. If such an explicitly
329+
// specified value starts with the prefix "get " or "set " and the function
330+
// for which it is specified is a get or set accessor function of a built-in
331+
// property, the value without the prefix is passed to the name parameter, and
332+
// the value "get" or "set" (respectively) is passed to the prefix parameter
333+
// when calling CreateBuiltinFunction.
334+
// https://tc39.es/ecma262/multipage/ecmascript-standard-built-in-objects.html
335+
if (checkGet) {
336+
var expectGetter = desc.get;
337+
var getterLabel = label + " getter";
338+
if (expectGetter === undefined || typeof expectGetter === "function") {
339+
assert.sameValue(originalDesc.get, expectGetter, getterLabel);
340+
} else {
341+
var getterName = expectGetter.name;
342+
if (getterName === undefined) {
343+
getterName = "get " + (typeof name === "symbol" ? "[" + name.description + "]" : name);
344+
}
345+
var getterLength = expectGetter.length !== undefined ? expectGetter.length : 0;
346+
var getterOptions = { label: getterLabel };
347+
callabilityVerifier(originalDesc, "get", getterName, getterLength, {}, getterOptions);
348+
}
349+
}
350+
if (checkSet) {
351+
var expectSetter = desc.set;
352+
var setterLabel = label + " setter";
353+
if (expectSetter === undefined || typeof expectSetter === "function") {
354+
assert.sameValue(originalDesc.set, expectSetter, setterLabel);
355+
} else {
356+
var setterName = expectSetter.name;
357+
if (setterName === undefined) {
358+
setterName = "set " + (typeof name === "symbol" ? "[" + name.description + "]" : name);
359+
}
360+
var setterLength = expectSetter.length !== undefined ? expectSetter.length : 1;
361+
var setterOptions = { label: setterLabel };
362+
callabilityVerifier(originalDesc, "set", setterName, setterLength, {}, setterOptions);
363+
}
364+
}
365+
366+
// Every accessor property described in clauses 19 through 28 and in Annex B.2
367+
// has the attributes { [[Enumerable]]: false, [[Configurable]]: true } unless
368+
// otherwise specified.
369+
// https://tc39.es/ecma262/multipage/ecmascript-standard-built-in-objects.html
370+
var resolvedDesc = { get: originalDesc.get, set: originalDesc.set };
371+
if (!__hasOwnProperty(desc, "enumerable")) {
372+
resolvedDesc.enumerable = false;
373+
} else if (desc.enumerable !== undefined) {
374+
resolvedDesc.enumerable = desc.enumerable;
375+
}
376+
if (!__hasOwnProperty(desc, "configurable")) {
377+
resolvedDesc.configurable = true;
378+
} else if (desc.configurable !== undefined) {
379+
resolvedDesc.configurable = desc.configurable;
380+
}
381+
propertyVerifier(obj, name, resolvedDesc, options);
277382
}
278383

279384
/**
@@ -367,5 +472,39 @@ var verifyPrimordialProperty = verifyProperty;
367472
* Use this function to verify the primordial function-valued properties.
368473
* For non-primordial functions, use verifyCallableProperty.
369474
* See: https://github.com/tc39/how-we-work/blob/main/terminology.md#primordial
475+
*
476+
* @type {typeof verifyCallableProperty}
370477
*/
371-
var verifyPrimordialCallableProperty = verifyCallableProperty;
478+
function verifyPrimordialCallableProperty(obj, name, functionName, functionLength, desc, options) {
479+
var resolvedOptions = {
480+
verifyProperty: options && options.verifyProperty !== undefined
481+
? options.verifyProperty
482+
: verifyPrimordialProperty
483+
};
484+
if (options && options.label !== undefined) resolvedOptions.label = options.label;
485+
if (options && options.restore !== undefined) resolvedOptions.restore = options.restore;
486+
487+
return verifyCallableProperty(obj, name, functionName, functionLength, desc, resolvedOptions);
488+
}
489+
490+
/**
491+
* Use this function to verify the primordial accessor properties.
492+
* For non-primordial functions, use verifyAccessorProperty.
493+
* See: https://github.com/tc39/how-we-work/blob/main/terminology.md#primordial
494+
*
495+
* @type {typeof verifyAccessorProperty}
496+
*/
497+
function verifyPrimordialAccessorProperty(obj, name, desc, options) {
498+
var resolvedOptions = {
499+
verifyProperty: options && options.verifyProperty !== undefined
500+
? options.verifyProperty
501+
: verifyPrimordialProperty,
502+
verifyCallableProperty: options && options.verifyCallableProperty !== undefined
503+
? options.verifyCallableProperty
504+
: verifyPrimordialCallableProperty
505+
};
506+
if (options && options.label !== undefined) resolvedOptions.label = options.label;
507+
if (options && options.restore !== undefined) resolvedOptions.restore = options.restore;
508+
509+
return verifyAccessorProperty(obj, name, desc, resolvedOptions);
510+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
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-get-arraybuffer.prototype.immutable
6+
description: Checks the "immutable" property of ArrayBuffer.prototype.
7+
info: |
8+
ArrayBuffer.prototype.immutable is an accessor property whose set accessor
9+
function is undefined.
10+
11+
ECMAScript Standard Built-in Objects
12+
...
13+
For functions that are specified as properties of objects, the name value is
14+
the property name string used to access the function. Functions that are
15+
specified as get or set accessor functions of built-in properties have "get"
16+
or "set" (respectively) passed to the prefix parameter when calling
17+
CreateBuiltinFunction.
18+
...
19+
Every accessor property described in clauses 19 through 28 and in Annex B.2
20+
has the attributes { [[Enumerable]]: false, [[Configurable]]: true } unless
21+
otherwise specified. If only a get accessor function is described, the set
22+
accessor function is the default value, undefined.
23+
features: [immutable-arraybuffer]
24+
includes: [propertyHelper.js]
25+
---*/
26+
27+
verifyPrimordialAccessorProperty(ArrayBuffer.prototype, "immutable", {
28+
get: { name: "get immutable", length: 0 },
29+
set: undefined,
30+
}, { label: "ArrayBuffer.prototype.immutable" });
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
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-get-arraybuffer.prototype.immutable
6+
description: Return value according to the [[ArrayBufferIsImmutable]] internal slot.
7+
info: |
8+
get ArrayBuffer.prototype.immutable
9+
1. Let O be the this value.
10+
2. Perform ? RequireInternalSlot(O, [[ArrayBufferData]]).
11+
3. If IsSharedArrayBuffer(O) is true, throw a TypeError exception.
12+
4. Return IsImmutableBuffer(O).
13+
14+
IsImmutableBuffer ( arrayBuffer )
15+
1. If arrayBuffer has an [[ArrayBufferIsImmutable]] internal slot, return true.
16+
2. Return false.
17+
features: [ArrayBuffer, immutable-arraybuffer]
18+
includes: [detachArrayBuffer.js]
19+
---*/
20+
21+
var ab = new ArrayBuffer(1);
22+
assert.sameValue(ab.immutable, false);
23+
24+
var iab = ab.transferToImmutable();
25+
assert.sameValue(iab.immutable, true);
26+
27+
$DETACHBUFFER(ab);
28+
assert.sameValue(ab.immutable, false);
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
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-get-arraybuffer.prototype.immutable
6+
description: >
7+
Throws a TypeError exception when `this` does not have a [[ArrayBufferData]]
8+
internal slot
9+
info: |
10+
get ArrayBuffer.prototype.immutable
11+
1. Let O be the this value.
12+
2. Perform ? RequireInternalSlot(O, [[ArrayBufferData]]).
13+
features: [DataView, Int8Array, ArrayBuffer, immutable-arraybuffer]
14+
---*/
15+
16+
var getter = Object.getOwnPropertyDescriptor(
17+
ArrayBuffer.prototype, "immutable"
18+
).get;
19+
20+
assert.sameValue(typeof getter, "function", "Getter must exist.");
21+
22+
var badReceivers = [
23+
["plain object", {}],
24+
["array", []],
25+
["function", function(){}],
26+
["ArrayBuffer.prototype", ArrayBuffer.prototype],
27+
["TypedArray", new Int8Array(8)],
28+
["DataView", new DataView(new ArrayBuffer(8), 0)]
29+
];
30+
31+
for (var i = 0; i < badReceivers.length; i++) {
32+
var label = badReceivers[i][0];
33+
var value = badReceivers[i][1];
34+
assert.throws(TypeError, function() {
35+
getter.call(value);
36+
}, label);
37+
}
38+
39+
assert.throws(TypeError, function() {
40+
ArrayBuffer.prototype.immutable;
41+
}, "invoked as prototype property access");
42+
43+
assert.throws(TypeError, function() {
44+
getter();
45+
}, "invoked as function");

0 commit comments

Comments
 (0)