Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion harness/features.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
atomicsHelper: [Atomics]
testAtomics.js: [ArrayBuffer, Atomics, DataView, SharedArrayBuffer, Symbol, TypedArray]
testAtomics.js: [ArrayBuffer, Atomics, TypedArray]
testTypedArray.js: [TypedArray]
isConstructor.js: [Reflect.construct]
130 changes: 110 additions & 20 deletions harness/testAtomics.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,38 +2,123 @@
// This code is governed by the BSD license found in the LICENSE file.
/*---
description: |
Collection of functions used to assert the correctness of SharedArrayBuffer objects.
Collection of functions used to assert the correctness of Atomics methods.
defines:
- testAtomicsIndexRevalidation
- testWithAtomicsOutOfBoundsIndices
- testWithAtomicsInBoundsIndices
- testWithAtomicsNonViewValues
---*/


/**
* Calls the provided function for a each bad index that should throw a
* RangeError when passed to an Atomics method on a SAB-backed view where
* index 125 is out of range.
* Invokes the provided function with (TypedArray, makeIndex) pairs for
* use in testing index revalidation by Atomics methods.
* The provided function is required to invoke each makeIndex exactly once and
* then use the result in such a way that [RevalidateAtomicAccess](
* https://tc39.es/ecma262/multipage/structured-data.html#sec-revalidateatomicaccess
* ) fails with a thrown exception when makeIndex shrinks the resizable
* ArrayBuffer backing the corresponding TypedArray and returns an index made
* invalid by the shrinking (e.g., using makeIndex as the valueOf or toString
* method of an object used as the index argument for an Atomics method call).
*
* @param TA - a TypedArray constructor
* @param f - the function to call for each (typedArray, getBadIndex)
* combination
*/
function testAtomicsIndexRevalidation(TA, f) {
// Make a resizable ArrayBuffer big enough to hold exactly four elements, and
// use it to back two TypedArrays:
// * one fixed at two elements that start halfway into the ArrayBuffer, and
// * one length-tracking that uses the entire ArrayBuffer.
var bytesPerElement = TA.prototype.BYTES_PER_ELEMENT;
var maxByteLength = bytesPerElement * 4;
var fixedByteOffset = maxByteLength / 2;
var rab = new ArrayBuffer(maxByteLength, { maxByteLength: maxByteLength });
assert(rab.resizable, 'testAtomicsIndexRevalidation requires ArrayBuffer resizing');
var fixedLength = new TA(rab, fixedByteOffset, 2);
var autoLength = new TA(rab);

// Shrinking a fixed-length TypedArray's backing ArrayBuffer to exclude a
// single byte of its last element makes it out-of-bounds.
var callCount = 0;
assert.throws(TypeError, function() {
f(fixedLength, function() {
callCount++;
rab.resize(fixedByteOffset + bytesPerElement * 2 - 1);
return 1;
});
}, 'shrink fixed-length TypedArray buffer');
assert.sameValue(callCount, 1, 'fixed-length TypedArray buffer shrinker must be called');

// Shrinking a length-tracking TypedArray's backing ArrayBuffer to exclude a
// single byte of its second element makes its length 1.
callCount = 0;
assert.throws(RangeError, function() {
f(autoLength, function() {
callCount++;
rab.resize(bytesPerElement * 2 - 1);
return 1;
});
}, 'shrink auto-length TypedArray buffer to just under 2 elements');
assert.sameValue(callCount, 1, 'first auto-length TypedArray buffer shrinker must be called');

// Shrinking a length-tracking TypedArray's backing ArrayBuffer to exclude a
// single byte of its first element makes its length 0.
callCount = 0;
assert.throws(RangeError, function() {
f(autoLength, function() {
callCount++;
rab.resize(bytesPerElement - 1);
return 0;
});
}, 'shrink auto-length TypedArray buffer to just under 1 element');
assert.sameValue(callCount, 1, 'second auto-length TypedArray buffer shrinker must be called');

// The TypedArrays become valid again when sufficient length is restored to
// their backing ArrayBuffer.
rab.resize(maxByteLength);
f(fixedLength, function() { return 1; });
f(autoLength, function() { return 3; });
}

/**
* Constructs a collection of callbacks that each return a bad index for a
* provided TypedArray (i.e., that should cause a RangeError when passed to an
* Atomics method along with that TypedArray) and calls the provided function
* once with each of them.
*
* @param f - the function to call for each bad index.
*/
function testWithAtomicsOutOfBoundsIndices(f) {
var bad_indices = [
function(view) { return -1; },
function(view) { return view.length; },
function(view) { return view.length * 2; },
function(view) { return Number.POSITIVE_INFINITY; },
function(view) { return Number.NEGATIVE_INFINITY; },
function(view) { return { valueOf: function() { return 125; } }; },
function(view) { return { toString: function() { return '125'; }, valueOf: false }; }, // non-callable valueOf triggers invocation of toString
var cases = [
{ label: '-1', makeBadIndex: function(view) { return -1; } },
{ label: 'view.length', makeBadIndex: function(view) { return view.length; } },
{ label: 'view.length * 2', makeBadIndex: function(view) { return view.length * 2; } },
{ label: 'Infinity', makeBadIndex: function(view) { return Number.POSITIVE_INFINITY; } },
{ label: '-Infinity', makeBadIndex: function(view) { return Number.NEGATIVE_INFINITY; } },
{
label: '{ valueOf: () => view.length }',
makeBadIndex: function(view) {
var length = view.length;
return { valueOf: function() { return length; } };
}
},
{
label: '{ toString: () => view.length }',
makeBadIndex: function(view) {
var strLength = String(view.length);
// non-callable valueOf triggers invocation of toString
return { toString: function() { return strLength; }, valueOf: false };
}
},
];

for (var i = 0; i < bad_indices.length; ++i) {
var IdxGen = bad_indices[i];
for (var i = 0; i < cases.length; ++i) {
try {
f(IdxGen);
f(cases[i].makeBadIndex);
} catch (e) {
e.message += ' (Testing with index gen ' + IdxGen + '.)';
e.message += ' (Testing with index ' + cases[i].label + '.)';
throw e;
}
}
Expand Down Expand Up @@ -80,7 +165,6 @@ function testWithAtomicsInBoundsIndices(f) {
*
* @param f - the function to call for each non-view value.
*/

function testWithAtomicsNonViewValues(f) {
var values = [
null,
Expand All @@ -95,20 +179,26 @@ function testWithAtomicsNonViewValues(f) {
new Date,
/a*utomaton/g,
{ password: 'qumquat' },
new DataView(new ArrayBuffer(10)),
new ArrayBuffer(128),
new SharedArrayBuffer(128),
new Error('Ouch'),
[1,1,2,3,5,8],
function(x) { return -x; },
Symbol('halleluja'),
// TODO: Proxy?
Object,
Int32Array,
Date,
Math,
Atomics
];
if (typeof DataView !== 'undefined') {
values.push(new DataView(new ArrayBuffer(10)));
}
if (typeof SharedArrayBuffer !== 'undefined') {
values.push(new SharedArrayBuffer(128));
}
if (typeof Symbol !== 'undefined') {
values.push(Symbol('halleluja'));
}

for (var i = 0; i < values.length; ++i) {
var nonView = values[i];
Expand Down
1 change: 1 addition & 0 deletions harness/testTypedArray.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ defines:
- nonClampedIntArrayConstructors
- intArrayConstructors
- typedArrayConstructors
- bigIntArrayConstructors
- TypedArray
- testWithAllTypedArrayConstructors
- testWithTypedArrayConstructors
Expand Down
16 changes: 16 additions & 0 deletions test/built-ins/Atomics/add/revalidate-range.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright (C) 2026 Richard Gibson. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

/*---
esid: sec-atomics.add
description: >
Atomics.add should revalidate index after argument coercion
includes: [testAtomics.js, testTypedArray.js]
features: [ArrayBuffer, Atomics, TypedArray, resizable-arraybuffer]
---*/

testWithTypedArrayConstructors(function(TA) {
testAtomicsIndexRevalidation(TA, function(view, makeIndex) {
Atomics.add(view, { valueOf: makeIndex }, '10');
});
}, nonClampedIntArrayConstructors.concat(bigIntArrayConstructors), ["passthrough"]);
16 changes: 16 additions & 0 deletions test/built-ins/Atomics/and/revalidate-range.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright (C) 2026 Richard Gibson. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

/*---
esid: sec-atomics.and
description: >
Atomics.and should revalidate index after argument coercion
includes: [testAtomics.js, testTypedArray.js]
features: [ArrayBuffer, Atomics, TypedArray, resizable-arraybuffer]
---*/

testWithTypedArrayConstructors(function(TA) {
testAtomicsIndexRevalidation(TA, function(view, makeIndex) {
Atomics.and(view, { valueOf: makeIndex }, '10');
});
}, nonClampedIntArrayConstructors.concat(bigIntArrayConstructors), ["passthrough"]);
16 changes: 16 additions & 0 deletions test/built-ins/Atomics/compareExchange/revalidate-range.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright (C) 2026 Richard Gibson. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

/*---
esid: sec-atomics.compareexchange
description: >
Atomics.compareExchange should revalidate index after argument coercion
includes: [testAtomics.js, testTypedArray.js]
features: [ArrayBuffer, Atomics, TypedArray, resizable-arraybuffer]
---*/

testWithTypedArrayConstructors(function(TA) {
testAtomicsIndexRevalidation(TA, function(view, makeIndex) {
Atomics.compareExchange(view, { valueOf: makeIndex }, '10', '0');
});
}, nonClampedIntArrayConstructors.concat(bigIntArrayConstructors), ["passthrough"]);
2 changes: 1 addition & 1 deletion test/built-ins/Atomics/exchange/bad-range.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ testWithTypedArrayConstructors(function(TA) {
const view = new TA(buffer);
testWithAtomicsOutOfBoundsIndices(function(IdxGen) {
assert.throws(RangeError, function() {
Atomics.exchange(view, IdxGen(view), 10, 0);
Atomics.exchange(view, IdxGen(view), 10);
});
});
}, views, ["passthrough"]);
2 changes: 1 addition & 1 deletion test/built-ins/Atomics/exchange/bigint/bad-range.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ testWithBigIntTypedArrayConstructors(function(TA) {

testWithAtomicsOutOfBoundsIndices(function(IdxGen) {
assert.throws(RangeError, function() {
Atomics.exchange(view, IdxGen(view), 10n, 0n);
Atomics.exchange(view, IdxGen(view), 10n);
});
});
}, null, ["passthrough"]);
16 changes: 16 additions & 0 deletions test/built-ins/Atomics/exchange/revalidate-range.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright (C) 2026 Richard Gibson. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

/*---
esid: sec-atomics.exchange
description: >
Atomics.exchange should revalidate index after argument coercion
includes: [testAtomics.js, testTypedArray.js]
features: [ArrayBuffer, Atomics, TypedArray, resizable-arraybuffer]
---*/

testWithTypedArrayConstructors(function(TA) {
testAtomicsIndexRevalidation(TA, function(view, makeIndex) {
Atomics.exchange(view, { valueOf: makeIndex }, '10');
});
}, nonClampedIntArrayConstructors.concat(bigIntArrayConstructors), ["passthrough"]);
16 changes: 16 additions & 0 deletions test/built-ins/Atomics/load/revalidate-range.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright (C) 2026 Richard Gibson. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

/*---
esid: sec-atomics.load
description: >
Atomics.load should revalidate index after argument coercion
includes: [testAtomics.js, testTypedArray.js]
features: [ArrayBuffer, Atomics, TypedArray, resizable-arraybuffer]
---*/

testWithTypedArrayConstructors(function(TA) {
testAtomicsIndexRevalidation(TA, function(view, makeIndex) {
Atomics.load(view, { valueOf: makeIndex });
});
}, nonClampedIntArrayConstructors.concat(bigIntArrayConstructors), ["passthrough"]);
16 changes: 16 additions & 0 deletions test/built-ins/Atomics/or/revalidate-range.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright (C) 2026 Richard Gibson. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

/*---
esid: sec-atomics.or
description: >
Atomics.or should revalidate index after argument coercion
includes: [testAtomics.js, testTypedArray.js]
features: [ArrayBuffer, Atomics, TypedArray, resizable-arraybuffer]
---*/

testWithTypedArrayConstructors(function(TA) {
testAtomicsIndexRevalidation(TA, function(view, makeIndex) {
Atomics.or(view, { valueOf: makeIndex }, '10');
});
}, nonClampedIntArrayConstructors.concat(bigIntArrayConstructors), ["passthrough"]);
16 changes: 16 additions & 0 deletions test/built-ins/Atomics/store/revalidate-range.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright (C) 2026 Richard Gibson. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

/*---
esid: sec-atomics.store
description: >
Atomics.store should revalidate index after argument coercion
includes: [testAtomics.js, testTypedArray.js]
features: [ArrayBuffer, Atomics, TypedArray, resizable-arraybuffer]
---*/

testWithTypedArrayConstructors(function(TA) {
testAtomicsIndexRevalidation(TA, function(view, makeIndex) {
Atomics.store(view, { valueOf: makeIndex }, '10');
});
}, nonClampedIntArrayConstructors.concat(bigIntArrayConstructors), ["passthrough"]);
16 changes: 16 additions & 0 deletions test/built-ins/Atomics/sub/revalidate-range.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright (C) 2026 Richard Gibson. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

/*---
esid: sec-atomics.sub
description: >
Atomics.sub should revalidate index after argument coercion
includes: [testAtomics.js, testTypedArray.js]
features: [ArrayBuffer, Atomics, TypedArray, resizable-arraybuffer]
---*/

testWithTypedArrayConstructors(function(TA) {
testAtomicsIndexRevalidation(TA, function(view, makeIndex) {
Atomics.sub(view, { valueOf: makeIndex }, '10');
});
}, nonClampedIntArrayConstructors.concat(bigIntArrayConstructors), ["passthrough"]);
16 changes: 16 additions & 0 deletions test/built-ins/Atomics/xor/revalidate-range.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright (C) 2026 Richard Gibson. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

/*---
esid: sec-atomics.xor
description: >
Atomics.xor should revalidate index after argument coercion
includes: [testAtomics.js, testTypedArray.js]
features: [ArrayBuffer, Atomics, TypedArray, resizable-arraybuffer]
---*/

testWithTypedArrayConstructors(function(TA) {
testAtomicsIndexRevalidation(TA, function(view, makeIndex) {
Atomics.xor(view, { valueOf: makeIndex }, '0');
});
}, nonClampedIntArrayConstructors.concat(bigIntArrayConstructors), ["passthrough"]);