diff --git a/README.md b/README.md index 8a206b6..d8b43d3 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,14 @@ -# TypedArray Concatenation +# TypedArray, ArrayBuffer, and SharedArrayBuffer Concatenation -ECMAScript Proposal for TypedArray concatentation +ECMAScript Proposal for TypedArray, ArrayBuffer, and SharedArrayBuffer concatenation This proposal is currently [stage 1](https://github.com/tc39/proposals/blob/master/README.md) of the [process](https://tc39.github.io/process-document/). ## Problem -ECMAScript should provide a native method for concatenating TypedArrays that enables implementations to optimize through strategies that can avoid the current requirement of eagerly allocating and copying data into new buffers +ECMAScript should provide native methods for concatenating TypedArrays and ArrayBuffers that enable implementations to optimize through strategies that can avoid the current requirement of eagerly allocating and copying data into new buffers. -It is common for applications on the web (both browser and server side) to need to concatenate two or more TypedArray instances as part of a data pipeline. Unfortunately, the mechanisms available for concatenation are difficult to optimize for performance. All require additional allocations and copying at inopportune times in the application. +It is common for applications on the web (both browser and server side) to need to concatenate two or more TypedArray or ArrayBuffer instances as part of a data pipeline. Unfortunately, the mechanisms available for concatenation are difficult to optimize for performance. All require additional allocations and copying at inopportune times in the application. A common example is a `WritableStream` instance that collects writes up to a defined threshold before passing those on in a single coalesced chunk. Server-side applications have typically relied on Node.js' `Buffer.concat` API, while browser applications have relied on either browser-compatible polyfills of `Buffer` or `TypedArray.prototype.set`. @@ -18,9 +18,8 @@ let size = 0; new WritableStream({ write(chunk) { buffers.push(chunk); - size += chunks.length; - if (buffer.byteLength >= 4096) { - // Not yet the actual proposed syntax... we have to determine that still + size += chunk.length; + if (size >= 4096) { flushBuffer(concat(buffers, size)); buffers = []; size = 0; @@ -35,45 +34,58 @@ function concat(buffers, size) { dest.set(buffer, offset); offset += buffer.length; } + return dest; } ``` -```js -const buffer1 = Buffer.from('hello'); -const buffer2 = Buffer.from('world'); -const buffer3 = Buffer.concat([buffer1, buffer2]); -``` - -While these approaches work, they end up being difficult to optimize because they require potential expensive allocations and data copying at inopportune times while processing the information. The `TypedArray.prototype.set` method does provide an approach for concatenation that is workable, but the way the algorithm is defined, there is no allowance given for implementation-defined optimization. +While these approaches work, they end up being difficult to optimize because they require potentially expensive allocations and data copying at inopportune times while processing the information. The `TypedArray.prototype.set` method does provide an approach for concatenation that is workable, but the way the algorithm is defined, there is no allowance given for implementation-defined optimization. ## Proposal -This proposal seeks to improve the current state by providing a mechanism that provides an optimizable concatenation path for TypedArrays within the language. +This proposal provides three complementary static methods for concatenation: + +1. **`%TypedArray%.concat(items [, length])`** — element-oriented concatenation of same-type TypedArrays +2. **`ArrayBuffer.concat(items [, options])`** — byte-oriented concatenation returning an ArrayBuffer +3. **`SharedArrayBuffer.concat(items [, options])`** — byte-oriented concatenation returning a SharedArrayBuffer + +All three methods afford implementations the ability to determine the most optimal approach, and optimal timing, for performing the allocations and copies, but no specific optimization is required. + +`%TypedArray%.concat` accepts only TypedArrays of the same type as the constructor (e.g., all `Uint8Array` for `Uint8Array.concat`), though those TypedArrays may be backed by either an ArrayBuffer or a SharedArrayBuffer. `ArrayBuffer.concat` and `SharedArrayBuffer.concat` accept any mix of ArrayBuffer, SharedArrayBuffer, TypedArray, and DataView inputs — the return type is determined by which method is called, not by the input types. -As a stage 1 proposal, the exact mechanism has yet to be defined but the goal would be to achieve a model very similar to Node.js' `Buffer.concat`, where multiple input `TypedArray`s can be given and the implementation can determine the most optimum approach to concatenating those into a single returned `TypedArray` of the same type. +### `%TypedArray%.concat(items [, length])` + +Concatenates multiple TypedArrays of the same type into a new TypedArray. ```js const enc = new TextEncoder(); const u8_1 = enc.encode('Hello '); const u8_2 = enc.encode('World!'); const u8_3 = Uint8Array.concat([u8_1, u8_2]); +// u8_3 contains: Uint8Array [72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33] ``` -A key goal, if a reasonable approach to do so is found, would be to afford implementations the ability to determine the most optimal approach, and optimal timing, for performing the allocations and copies, but no specific optimization would be required. +- `items` — an iterable of TypedArray instances, all of the same type as the constructor. +- `length` (optional) — a non-negative integer specifying the element length of the result. If less than the total, the result is truncated. If greater, the result is zero-filled. Defaults to the sum of all input lengths. -### Differences from `set` +All items must be TypedArrays of the same type as the constructor (e.g., all `Uint8Array` for `Uint8Array.concat`). A `TypeError` is thrown if any item is a different type. Items may be backed by either an ArrayBuffer or a SharedArrayBuffer. -Per the current definition of `TypedArray.prototype.set` in the language specification, the user code is responsible for allocating the destination `TypedArray` in advance along with calculating and updating the offset at which each copied segment should go. Allocations can be expensive and the book keeping can be cumbersome, particularly when the are multiple input `TypedArrays`. The `set` algorithm is also written such that each element of the copied `TypedArray` is copied to the destination one element at a time, with no affordance given to allow the implementation to determine an alternative, more optimal copy strategy. +A `TypeError` is thrown if any item is a detached TypedArray. A `RangeError` is thrown if the total element count exceeds 253 - 1. ```js +// Truncate to 5 elements +const truncated = Uint8Array.concat([u8_1, u8_2], 5); + +// Zero-fill to 20 elements +const padded = Uint8Array.concat([u8_1, u8_2], 20); + +// WritableStream coalescing example let buffers = []; let size = 0; new WritableStream({ write(chunk) { buffers.push(chunk); - size += chunks.length; + size += chunk.length; if (size >= 4096) { - // Not yet the actual proposed syntax... we have to determine that still flushBuffer(Uint8Array.concat(buffers, size)); buffers = []; size = 0; @@ -81,3 +93,138 @@ new WritableStream({ } }); ``` + +The `concat` method is available on all TypedArray constructors: + +```js +// Integer types +Int8Array.concat([new Int8Array([-1, 127]), new Int8Array([0, -128])]); +// → Int8Array [-1, 127, 0, -128] + +Uint8Array.concat([new Uint8Array([0, 255]), new Uint8Array([128])]); +// → Uint8Array [0, 255, 128] + +Uint8ClampedArray.concat([new Uint8ClampedArray([0, 255]), new Uint8ClampedArray([128])]); +// → Uint8ClampedArray [0, 255, 128] + +Int16Array.concat([new Int16Array([-1, 32767]), new Int16Array([0])]); +// → Int16Array [-1, 32767, 0] + +Uint16Array.concat([new Uint16Array([0, 65535]), new Uint16Array([256])]); +// → Uint16Array [0, 65535, 256] + +Int32Array.concat([new Int32Array([-1, 2147483647]), new Int32Array([0])]); +// → Int32Array [-1, 2147483647, 0] + +Uint32Array.concat([new Uint32Array([0, 4294967295]), new Uint32Array([256])]); +// → Uint32Array [0, 4294967295, 256] + +// BigInt types +BigInt64Array.concat([new BigInt64Array([0n, -1n]), new BigInt64Array([9007199254740991n])]); +// → BigInt64Array [0n, -1n, 9007199254740991n] + +BigUint64Array.concat([new BigUint64Array([0n, 1n]), new BigUint64Array([18446744073709551615n])]); +// → BigUint64Array [0n, 1n, 18446744073709551615n] + +// Floating-point types +Float16Array.concat([new Float16Array([1.5, -0]), new Float16Array([Infinity, NaN])]); +// → Float16Array [1.5, -0, Infinity, NaN] + +Float32Array.concat([new Float32Array([1.5, -0]), new Float32Array([Infinity, NaN])]); +// → Float32Array [1.5, -0, Infinity, NaN] + +Float64Array.concat([new Float64Array([1.5, -0]), new Float64Array([Infinity, NaN])]); +// → Float64Array [1.5, -0, Infinity, NaN] +``` + +### `ArrayBuffer.concat(items [, options])` + +Concatenates the byte contents of multiple ArrayBuffers, SharedArrayBuffers, TypedArrays, or DataViews into a new ArrayBuffer. + +```js +const ab1 = new ArrayBuffer(4); +const ab2 = new ArrayBuffer(4); +const ab3 = ArrayBuffer.concat([ab1, ab2]); +// ab3.byteLength === 8 +``` + +- `items` — an iterable of ArrayBuffer, SharedArrayBuffer, TypedArray, or DataView instances. For TypedArray and DataView inputs, only the viewed portion of the underlying buffer is included. +- `options` (optional) — an object with the following properties: + - `length` — a non-negative integer specifying the byte length of the result. If less than the total input bytes, the result is truncated. If greater, the result is zero-filled. Defaults to the sum of all input byte lengths. + - `resizable` — a boolean. If `true`, the result is a resizable ArrayBuffer where `length` specifies the maximum byte length (`maxByteLength`). The actual `byteLength` is the lesser of the total input bytes and `length`. Defaults to `false`. + - `immutable` — a boolean. If `true`, the result is an immutable ArrayBuffer whose contents cannot be changed, resized, or detached. Defaults to `false`. *This option depends on the [Immutable ArrayBuffer proposal](https://github.com/tc39/proposal-immutable-arraybuffer).* + +The `resizable` and `immutable` options are mutually exclusive. A `TypeError` is thrown if both are `true`. + +A `TypeError` is thrown for detached buffers or out-of-bounds DataViews. A `RangeError` is thrown if the total byte count exceeds 253 - 1. + +```js +// Mix of ArrayBuffer, TypedArray, and DataView inputs +const ab = new ArrayBuffer(4); +const u8 = new Uint8Array([1, 2, 3, 4]); +const dv = new DataView(new ArrayBuffer(2)); +const result = ArrayBuffer.concat([ab, u8, dv]); +// result.byteLength === 10 + +// Truncate to 6 bytes +const truncated = ArrayBuffer.concat([ab, u8, dv], { length: 6 }); + +// Zero-fill to 16 bytes +const padded = ArrayBuffer.concat([ab, u8], { length: 16 }); + +// Create a resizable result with room to grow +const resizable = ArrayBuffer.concat([ab, u8], { resizable: true, length: 32 }); +// resizable.byteLength === 8 (actual data) +// resizable.maxByteLength === 32 (can grow up to 32) + +// Create an immutable result (requires Immutable ArrayBuffer proposal) +const immutable = ArrayBuffer.concat([ab, u8], { immutable: true }); +// immutable.byteLength === 8 +// immutable.immutable === true +``` + +### `SharedArrayBuffer.concat(items [, options])` + +Concatenates the byte contents of multiple ArrayBuffers, SharedArrayBuffers, TypedArrays, or DataViews into a new SharedArrayBuffer. + +```js +const sab1 = new SharedArrayBuffer(4); +const sab2 = new SharedArrayBuffer(4); +const sab3 = SharedArrayBuffer.concat([sab1, sab2]); +// sab3.byteLength === 8 +``` + +- `items` — an iterable of ArrayBuffer, SharedArrayBuffer, TypedArray, or DataView instances. For TypedArray and DataView inputs, only the viewed portion of the underlying buffer is included. +- `options` (optional) — an object with the following properties: + - `length` — a non-negative integer specifying the byte length of the result. If less than the total input bytes, the result is truncated. If greater, the result is zero-filled. Defaults to the sum of all input byte lengths. + - `growable` — a boolean. If `true`, the result is a growable SharedArrayBuffer where `length` specifies the maximum byte length (`maxByteLength`). The actual `byteLength` is the lesser of the total input bytes and `length`. Defaults to `false`. + +Note: The `immutable` option is not available for SharedArrayBuffers. + +A `TypeError` is thrown for detached buffers or out-of-bounds DataViews. A `RangeError` is thrown if the total byte count exceeds 253 - 1. + +```js +// Mix of SharedArrayBuffer, TypedArray, and DataView inputs +const sab = new SharedArrayBuffer(4); +const u8 = new Uint8Array([1, 2, 3, 4]); +const dv = new DataView(new ArrayBuffer(2)); +const result = SharedArrayBuffer.concat([sab, u8, dv]); +// result.byteLength === 10 + +// Create a growable result with room to grow +const growable = SharedArrayBuffer.concat([sab, u8], { growable: true, length: 32 }); +// growable.byteLength === 8 (actual data) +// growable.maxByteLength === 32 (can grow up to 32) +``` + +### Differences from `set` + +Per the current definition of `TypedArray.prototype.set` in the language specification, the user code is responsible for allocating the destination `TypedArray` in advance along with calculating and updating the offset at which each copied segment should go. Allocations can be expensive and the book keeping can be cumbersome, particularly when there are multiple input `TypedArrays`. The `set` algorithm is also written such that each element of the copied `TypedArray` is copied to the destination one element at a time, with no affordance given to allow the implementation to determine an alternative, more optimal copy strategy. + +### Why three methods? + +`%TypedArray%.concat` operates at the TypedArray level — it is element-oriented, requires same-type inputs, and returns a TypedArray. This is the right level of abstraction when working with typed data (e.g., concatenating `Uint8Array` chunks in a stream). + +`ArrayBuffer.concat` and `SharedArrayBuffer.concat` operate at the buffer level — they are byte-oriented, accept heterogeneous inputs (ArrayBuffer/SharedArrayBuffer, TypedArray, DataView), and return the appropriate buffer type. This is the right level of abstraction for controlling buffer properties like resizability/growability and immutability, which are concerns of the buffer, not the TypedArray. + +`ArrayBuffer.concat` and `SharedArrayBuffer.concat` are separate methods because the return type differs and the available options differ (`immutable` is only available for ArrayBuffer, `growable` is only available for SharedArrayBuffer). This mirrors the existing separation between the `ArrayBuffer` and `SharedArrayBuffer` constructors in the language. diff --git a/TEST_CASES.md b/TEST_CASES.md new file mode 100644 index 0000000..f5caa30 --- /dev/null +++ b/TEST_CASES.md @@ -0,0 +1,2586 @@ +# Comprehensive Test Cases + +Test cases for `%TypedArray%.concat`, `ArrayBuffer.concat`, and `SharedArrayBuffer.concat`. + +### Identifier Convention + +Each checkbox line has a unique identifier in the form `[section.subsection.number]`, e.g. `[2.1.1]`. + +Where a test case is preceded by a "For each ..." qualifier (e.g. "For each TypedArray type"), each variation is referenced by appending a lowercase letter suffix following the order in which the items appear in the qualifier list. + +--- + +## 1. `%TypedArray%.concat` — Receiver Validation + +### 1.1 Invalid `this` value (not a constructor) + +- [ ] [1.1.1] `%TypedArray%.concat.call(undefined, [])` → TypeError +- [ ] [1.1.2] `%TypedArray%.concat.call(null, [])` → TypeError +- [ ] [1.1.3] `%TypedArray%.concat.call({}, [])` → TypeError +- [ ] [1.1.4] `%TypedArray%.concat.call(42, [])` → TypeError +- [ ] [1.1.5] `%TypedArray%.concat.call('string', [])` → TypeError +- [ ] [1.1.6] `%TypedArray%.concat.call(true, [])` → TypeError +- [ ] [1.1.7] `%TypedArray%.concat.call(Symbol(), [])` → TypeError +- [ ] [1.1.8] `%TypedArray%.concat.call(() => {}, [])` → TypeError (arrow function is callable but IsConstructor is false) + +### 1.2 Constructor without `[[TypedArrayName]]` + +- [ ] [1.2.1] `%TypedArray%.concat.call(Array, [])` → TypeError (Array is a constructor but has no `[[TypedArrayName]]`) +- [ ] [1.2.2] `%TypedArray%.concat.call(Object, [])` → TypeError +- [ ] [1.2.3] `%TypedArray%.concat.call(function(){}, [])` → TypeError (custom constructor, no `[[TypedArrayName]]`) + +### 1.3 Valid TypedArray constructors + +For each TypedArray constructor (`Int8Array`, `Uint8Array`, `Uint8ClampedArray`, `Int16Array`, `Uint16Array`, `Int32Array`, `Uint32Array`, `Float16Array`, `Float32Array`, `Float64Array`, `BigInt64Array`, `BigUint64Array`): + +- [ ] [1.3.1] `.concat([])` does not throw (valid receiver, empty items) + +--- + +## 2. `%TypedArray%.concat` — Items Validation + +### 2.1 Items is not iterable + +- [ ] [2.1.1] `Uint8Array.concat(undefined)` → TypeError +- [ ] [2.1.2] `Uint8Array.concat(null)` → TypeError +- [ ] [2.1.3] `Uint8Array.concat(42)` → TypeError +- [ ] [2.1.4] `Uint8Array.concat({})` → TypeError (plain object, no `Symbol.iterator`) +- [ ] [2.1.5] `Uint8Array.concat('hello')` → TypeError (string is iterable but items should be TypedArrays; the string chars will fail `ValidateTypedArray`) + +### 2.2 Items iterable throws during iteration + +- [ ] [2.2.1] Items iterable whose `Symbol.iterator` method throws → error propagates +- [ ] [2.2.2] Items iterable whose iterator's `next()` throws → error propagates + +### 2.3 Item is not a TypedArray (`ValidateTypedArray` fails) + +- [ ] [2.3.1] `Uint8Array.concat([new ArrayBuffer(4)])` → TypeError +- [ ] [2.3.2] `Uint8Array.concat([new DataView(new ArrayBuffer(4))])` → TypeError +- [ ] [2.3.3] `Uint8Array.concat([{}])` → TypeError +- [ ] [2.3.4] `Uint8Array.concat([42])` → TypeError +- [ ] [2.3.5] `Uint8Array.concat([null])` → TypeError +- [ ] [2.3.6] `Uint8Array.concat([undefined])` → TypeError +- [ ] [2.3.7] `Uint8Array.concat([[1, 2, 3]])` → TypeError (plain Array) + +### 2.4 Item is a TypedArray with a detached buffer + +- [ ] [2.4.1] Single detached TypedArray in items → TypeError from `ValidateTypedArray` + ```js + const a = new Uint8Array(4); + a.buffer.transfer(); + Uint8Array.concat([a]) // → TypeError + ``` +- [ ] [2.4.2] First item valid, second item detached → TypeError on second item + ```js + const a = new Uint8Array([1, 2]); + const b = new Uint8Array(4); + b.buffer.transfer(); + Uint8Array.concat([a, b]) // → TypeError + ``` + +### 2.5 Item is a different TypedArray type (`[[TypedArrayName]]` mismatch) + +- [ ] [2.5.1] `Uint8Array.concat([new Int8Array([1, 2])])` → TypeError +- [ ] [2.5.2] `Uint8Array.concat([new Uint16Array([1, 2])])` → TypeError +- [ ] [2.5.3] `Uint8Array.concat([new Float64Array([1, 2])])` → TypeError +- [ ] [2.5.4] `Uint8Array.concat([new BigInt64Array([1n, 2n])])` → TypeError +- [ ] [2.5.5] `Uint8Array.concat([new Uint8ClampedArray([1, 2])])` → TypeError (Uint8ClampedArray !== Uint8Array) +- [ ] [2.5.6] `Int32Array.concat([new Uint32Array([1, 2])])` → TypeError +- [ ] [2.5.7] `BigInt64Array.concat([new BigUint64Array([1n, 2n])])` → TypeError +- [ ] [2.5.8] Mixed valid and invalid: `Uint8Array.concat([new Uint8Array([1]), new Int16Array([2])])` → TypeError on second item + +### 2.6 Valid same-type items + +For each TypedArray type (`Int8Array`, `Uint8Array`, `Uint8ClampedArray`, `Int16Array`, `Uint16Array`, `Int32Array`, `Uint32Array`, `Float16Array`, `Float32Array`, `Float64Array`): + +- [ ] [2.6.1] `.concat([new ([1, 2]), new ([3, 4])])` → ` [1, 2, 3, 4]` + +For each BigInt type (`BigInt64Array`, `BigUint64Array`): + +- [ ] [2.6.2] `.concat([new ([1n, 2n]), new ([3n, 4n])])` → ` [1n, 2n, 3n, 4n]` + +--- + +## 3. `%TypedArray%.concat` — Length Parameter Validation (`ValidateIntegralNumber`) + +### 3.1 Length not provided (defaults to total) + +- [ ] [3.1.1] `Uint8Array.concat([new Uint8Array([1, 2]), new Uint8Array([3, 4])])` → `Uint8Array [1, 2, 3, 4]` (length 4) +- [ ] [3.1.2] `Uint8Array.concat([])` → `Uint8Array []` (length 0) + +### 3.2 Length is `undefined` (same as not provided) + +- [ ] [3.2.1] `Uint8Array.concat([new Uint8Array([1, 2])], undefined)` → `Uint8Array [1, 2]` + +### 3.3 Length is non-Number → TypeError + +- [ ] [3.3.1] `Uint8Array.concat([], 'hello')` → TypeError +- [ ] [3.3.2] `Uint8Array.concat([], {})` → TypeError +- [ ] [3.3.3] `Uint8Array.concat([], true)` → TypeError +- [ ] [3.3.4] `Uint8Array.concat([], null)` → TypeError +- [ ] [3.3.5] `Uint8Array.concat([], Symbol())` → TypeError +- [ ] [3.3.6] `Uint8Array.concat([], 1n)` → TypeError + +### 3.4 Length is NaN → RangeError + +- [ ] [3.4.1] `Uint8Array.concat([], NaN)` → RangeError + +### 3.5 Length is non-integral → RangeError + +- [ ] [3.5.1] `Uint8Array.concat([], 1.5)` → RangeError +- [ ] [3.5.2] `Uint8Array.concat([], 0.1)` → RangeError +- [ ] [3.5.3] `Uint8Array.concat([], Infinity)` → RangeError +- [ ] [3.5.4] `Uint8Array.concat([], -Infinity)` → RangeError + +### 3.6 Length is negative → RangeError + +- [ ] [3.6.1] `Uint8Array.concat([], -1)` → RangeError +- [ ] [3.6.2] `Uint8Array.concat([], -100)` → RangeError + +### 3.7 Length exceeds 2^53 - 1 → RangeError + +- [ ] [3.7.1] `Uint8Array.concat([], 2 ** 53)` → RangeError +- [ ] [3.7.2] `Uint8Array.concat([], Number.MAX_SAFE_INTEGER + 1)` → RangeError + +### 3.8 Length is -0 (treated as 0) + +- [ ] [3.8.1] `Uint8Array.concat([new Uint8Array([1, 2])], -0)` → `Uint8Array []` (ℝ(-0) is 0, truncates to empty) + +### 3.9 Valid length values + +- [ ] [3.9.1] `Uint8Array.concat([new Uint8Array([1, 2])], 0)` → `Uint8Array []` (truncated to 0) +- [ ] [3.9.2] `Uint8Array.concat([new Uint8Array([1, 2])], 1)` → `Uint8Array [1]` (truncated to 1) +- [ ] [3.9.3] `Uint8Array.concat([new Uint8Array([1, 2])], 2)` → `Uint8Array [1, 2]` (exact) +- [ ] [3.9.4] `Uint8Array.concat([new Uint8Array([1, 2])], 5)` → `Uint8Array [1, 2, 0, 0, 0]` (zero-filled) + +--- + +## 4. `%TypedArray%.concat` — Overflow Check on `totalLength` + +### 4.1 Total length exceeds 2^53 - 1 + +- [ ] [4.1.1] Concatenating items whose combined element lengths exceed 2^53 - 1 → RangeError +- [ ] [4.1.2] Overflow check is per-item (fails as soon as running total exceeds limit, not after entire loop) + +Note: In practice, creating TypedArrays large enough to trigger this is implementation-limited. These tests may need to be adapted based on available memory. + +--- + +## 5. `%TypedArray%.concat` — Basic Concatenation + +### 5.1 Two arrays + +For each non-BigInt type (`Int8Array`, `Uint8Array`, `Uint8ClampedArray`, `Int16Array`, `Uint16Array`, `Int32Array`, `Uint32Array`, `Float16Array`, `Float32Array`, `Float64Array`): + +- [ ] [5.1.1] `.concat([new ([1, 2, 3]), new ([4, 5, 6])])` → ` [1, 2, 3, 4, 5, 6]` + +For each BigInt type (`BigInt64Array`, `BigUint64Array`): + +- [ ] [5.1.2] `.concat([new ([1n, 2n, 3n]), new ([4n, 5n, 6n])])` → ` [1n, 2n, 3n, 4n, 5n, 6n]` + +### 5.2 Three or more arrays + +- [ ] [5.2.1] `Uint8Array.concat([new Uint8Array([1]), new Uint8Array([2]), new Uint8Array([3])])` → `Uint8Array [1, 2, 3]` +- [ ] [5.2.2] `Uint8Array.concat([new Uint8Array([1, 2]), new Uint8Array([3, 4]), new Uint8Array([5, 6]), new Uint8Array([7, 8])])` → `Uint8Array [1, 2, 3, 4, 5, 6, 7, 8]` + +### 5.3 Single array + +- [ ] [5.3.1] `Uint8Array.concat([new Uint8Array([1, 2, 3])])` → `Uint8Array [1, 2, 3]` (copy of the input) +- [ ] [5.3.2] Result is a new TypedArray (not the same object): + ```js + const a = new Uint8Array([1, 2, 3]); + const b = Uint8Array.concat([a]); + b !== a // → true + b.buffer !== a.buffer // → true + ``` + +### 5.4 Empty items list + +- [ ] [5.4.1] `Uint8Array.concat([])` → `Uint8Array []` (length 0) +- [ ] [5.4.2] Result has a fresh ArrayBuffer: + ```js + const result = Uint8Array.concat([]); + result.byteLength === 0 // → true + result.buffer instanceof ArrayBuffer // → true + ``` + +### 5.5 Items containing zero-length TypedArrays + +- [ ] [5.5.1] `Uint8Array.concat([new Uint8Array([]), new Uint8Array([1, 2])])` → `Uint8Array [1, 2]` +- [ ] [5.5.2] `Uint8Array.concat([new Uint8Array([1, 2]), new Uint8Array([])])` → `Uint8Array [1, 2]` +- [ ] [5.5.3] `Uint8Array.concat([new Uint8Array([]), new Uint8Array([])])` → `Uint8Array []` +- [ ] [5.5.4] `Uint8Array.concat([new Uint8Array([1]), new Uint8Array([]), new Uint8Array([2])])` → `Uint8Array [1, 2]` + +### 5.6 Result TypedArray type matches constructor + +- [ ] [5.6.1] `Float64Array.concat([new Float64Array([1.5, 2.5])])` → result is a `Float64Array`, not `Uint8Array` +- [ ] [5.6.2] `BigInt64Array.concat([new BigInt64Array([1n])])` → result is a `BigInt64Array` + +--- + +## 6. `%TypedArray%.concat` — Truncation and Zero-Fill + +### 6.1 Truncation (length < totalLength) + +- [ ] [6.1.1] `Uint8Array.concat([new Uint8Array([1, 2, 3, 4, 5])], 3)` → `Uint8Array [1, 2, 3]` +- [ ] [6.1.2] `Uint8Array.concat([new Uint8Array([1, 2]), new Uint8Array([3, 4])], 3)` → `Uint8Array [1, 2, 3]` (truncates mid-second-array) +- [ ] [6.1.3] `Uint8Array.concat([new Uint8Array([1, 2]), new Uint8Array([3, 4])], 2)` → `Uint8Array [1, 2]` (truncates at boundary) +- [ ] [6.1.4] `Uint8Array.concat([new Uint8Array([1, 2]), new Uint8Array([3, 4]), new Uint8Array([5, 6])], 1)` → `Uint8Array [1]` (third array entirely skipped) +- [ ] [6.1.5] `Uint8Array.concat([new Uint8Array([1, 2, 3])], 0)` → `Uint8Array []` + +### 6.2 Zero-fill (length > totalLength) + +- [ ] [6.2.1] `Uint8Array.concat([new Uint8Array([1, 2])], 5)` → `Uint8Array [1, 2, 0, 0, 0]` +- [ ] [6.2.2] `Uint8Array.concat([], 3)` → `Uint8Array [0, 0, 0]` (no items, all zero-filled) +- [ ] [6.2.3] `Int32Array.concat([new Int32Array([1])], 4)` → `Int32Array [1, 0, 0, 0]` +- [ ] [6.2.4] `Float64Array.concat([new Float64Array([1.5])], 3)` → `Float64Array [1.5, 0, 0]` +- [ ] [6.2.5] `BigInt64Array.concat([new BigInt64Array([1n])], 3)` → `BigInt64Array [1n, 0n, 0n]` + +### 6.3 Exact length + +- [ ] [6.3.1] `Uint8Array.concat([new Uint8Array([1, 2, 3])], 3)` → `Uint8Array [1, 2, 3]` (same as no length) + +--- + +## 7. `%TypedArray%.concat` — Element Value Preservation + +### 7.1 Boundary values per type + +- [ ] [7.1.1] `Int8Array.concat([new Int8Array([-128, 127])])` → `Int8Array [-128, 127]` +- [ ] [7.1.2] `Uint8Array.concat([new Uint8Array([0, 255])])` → `Uint8Array [0, 255]` +- [ ] [7.1.3] `Uint8ClampedArray.concat([new Uint8ClampedArray([0, 255])])` → `Uint8ClampedArray [0, 255]` +- [ ] [7.1.4] `Int16Array.concat([new Int16Array([-32768, 32767])])` → `Int16Array [-32768, 32767]` +- [ ] [7.1.5] `Uint16Array.concat([new Uint16Array([0, 65535])])` → `Uint16Array [0, 65535]` +- [ ] [7.1.6] `Int32Array.concat([new Int32Array([-2147483648, 2147483647])])` → `Int32Array [-2147483648, 2147483647]` +- [ ] [7.1.7] `Uint32Array.concat([new Uint32Array([0, 4294967295])])` → `Uint32Array [0, 4294967295]` +- [ ] [7.1.8] `BigInt64Array.concat([new BigInt64Array([-9223372036854775808n, 9223372036854775807n])])` → preserves values +- [ ] [7.1.9] `BigUint64Array.concat([new BigUint64Array([0n, 18446744073709551615n])])` → preserves values + +### 7.2 Floating-point special values + +- [ ] [7.2.1] `Float64Array.concat([new Float64Array([NaN, Infinity, -Infinity, -0, 0])])` → preserves all special values +- [ ] [7.2.2] `Float32Array.concat([new Float32Array([NaN, Infinity, -Infinity])])` → preserves all special values +- [ ] [7.2.3] `Float64Array.concat([new Float64Array([NaN]), new Float64Array([Infinity])])` → `Float64Array [NaN, Infinity]` + +--- + +## 8. `ArrayBuffer.concat` — Items Validation + +### 8.1 Items is not iterable + +- [ ] [8.1.1] `ArrayBuffer.concat(undefined)` → TypeError +- [ ] [8.1.2] `ArrayBuffer.concat(null)` → TypeError +- [ ] [8.1.3] `ArrayBuffer.concat(42)` → TypeError +- [ ] [8.1.4] `ArrayBuffer.concat({})` → TypeError + +### 8.2 Items iterable throws during iteration + +- [ ] [8.2.1] Items iterable whose `Symbol.iterator` method throws → error propagates +- [ ] [8.2.2] Items iterable whose iterator's `next()` throws → error propagates + +### 8.3 Item is not an ArrayBuffer, TypedArray, or DataView + +- [ ] [8.3.1] `ArrayBuffer.concat([42])` → TypeError +- [ ] [8.3.2] `ArrayBuffer.concat([{}])` → TypeError +- [ ] [8.3.3] `ArrayBuffer.concat(['string'])` → TypeError +- [ ] [8.3.4] `ArrayBuffer.concat([null])` → TypeError +- [ ] [8.3.5] `ArrayBuffer.concat([undefined])` → TypeError +- [ ] [8.3.6] `ArrayBuffer.concat([[1, 2, 3]])` → TypeError (plain Array) + +### 8.4 Item is a SharedArrayBuffer (accepted) + +- [ ] [8.4.1] `ArrayBuffer.concat([new SharedArrayBuffer(4)])` → works (SharedArrayBuffer is accepted; result is an ArrayBuffer) + ```js + const sab = new SharedArrayBuffer(4); + new Uint8Array(sab).set([1, 2, 3, 4]); + const result = ArrayBuffer.concat([sab]); + result.byteLength // → 4 + result instanceof ArrayBuffer // → true + result instanceof SharedArrayBuffer // → false + new Uint8Array(result) // → [1, 2, 3, 4] + ``` +- [ ] [8.4.2] Mix of ArrayBuffer and SharedArrayBuffer: + ```js + const ab = new ArrayBuffer(2); + new Uint8Array(ab).set([1, 2]); + const sab = new SharedArrayBuffer(2); + new Uint8Array(sab).set([3, 4]); + const result = ArrayBuffer.concat([ab, sab]); + result.byteLength // → 4 + result instanceof ArrayBuffer // → true + new Uint8Array(result) // → [1, 2, 3, 4] + ``` + +### 8.5 Item is a detached ArrayBuffer + +- [ ] [8.5.1] Detached ArrayBuffer → TypeError + ```js + const ab = new ArrayBuffer(4); + ab.transfer(); + ArrayBuffer.concat([ab]) // → TypeError + ``` + +### 8.6 Item is a TypedArray with a detached buffer + +- [ ] [8.6.1] TypedArray whose buffer has been detached → TypeError from `ValidateTypedArray` + +### 8.7 Item is a DataView that is out of bounds + +- [ ] [8.7.1] DataView over a resizable ArrayBuffer that has been shrunk below the view's range → TypeError from `IsViewOutOfBounds` + +### 8.8 Mixed valid item types + +- [ ] [8.8.1] `ArrayBuffer.concat([new ArrayBuffer(2), new Uint8Array([1, 2]), new DataView(new ArrayBuffer(3))])` → ArrayBuffer of byteLength 7 +- [ ] [8.8.2] First item valid, second item invalid → TypeError on second item +- [ ] [8.8.3] Mix of all four input types: + ```js + const ab = new ArrayBuffer(2); + new Uint8Array(ab).set([1, 2]); + const sab = new SharedArrayBuffer(2); + new Uint8Array(sab).set([3, 4]); + const u8 = new Uint8Array([5, 6]); + const dv = new DataView(new ArrayBuffer(2)); + new Uint8Array(dv.buffer).set([7, 8]); + const result = ArrayBuffer.concat([ab, sab, u8, dv]); + result.byteLength // → 8 + result instanceof ArrayBuffer // → true + new Uint8Array(result) // → [1, 2, 3, 4, 5, 6, 7, 8] + ``` + +--- + +## 9. `ArrayBuffer.concat` — Options Validation + +### 9.1 Options is `undefined` or not provided + +- [ ] [9.1.1] `ArrayBuffer.concat([new ArrayBuffer(4)])` → works, no options +- [ ] [9.1.2] `ArrayBuffer.concat([new ArrayBuffer(4)], undefined)` → works, same as no options + +### 9.2 Options is not an object + +- [ ] [9.2.1] `ArrayBuffer.concat([], 42)` → TypeError from `GetOptionsObject` +- [ ] [9.2.2] `ArrayBuffer.concat([], 'string')` → TypeError +- [ ] [9.2.3] `ArrayBuffer.concat([], true)` → TypeError +- [ ] [9.2.4] `ArrayBuffer.concat([], null)` → works (null is treated as no options by `GetOptionsObject` — actually this may vary; verify behavior) + +### 9.3 Length option validation + +#### 9.3.1 Non-Number length → TypeError + +- [ ] [9.3.1.1] `ArrayBuffer.concat([], { length: 'hello' })` → TypeError +- [ ] [9.3.1.2] `ArrayBuffer.concat([], { length: {} })` → TypeError +- [ ] [9.3.1.3] `ArrayBuffer.concat([], { length: true })` → TypeError +- [ ] [9.3.1.4] `ArrayBuffer.concat([], { length: Symbol() })` → TypeError +- [ ] [9.3.1.5] `ArrayBuffer.concat([], { length: 1n })` → TypeError + +#### 9.3.2 NaN / non-integral / Infinity → RangeError + +- [ ] [9.3.2.1] `ArrayBuffer.concat([], { length: NaN })` → RangeError +- [ ] [9.3.2.2] `ArrayBuffer.concat([], { length: 1.5 })` → RangeError +- [ ] [9.3.2.3] `ArrayBuffer.concat([], { length: Infinity })` → RangeError +- [ ] [9.3.2.4] `ArrayBuffer.concat([], { length: -Infinity })` → RangeError + +#### 9.3.3 Negative length → RangeError + +- [ ] [9.3.3.1] `ArrayBuffer.concat([], { length: -1 })` → RangeError +- [ ] [9.3.3.2] `ArrayBuffer.concat([], { length: -100 })` → RangeError + +#### 9.3.4 Length exceeds 2^53 - 1 → RangeError + +- [ ] [9.3.4.1] `ArrayBuffer.concat([], { length: 2 ** 53 })` → RangeError +- [ ] [9.3.4.2] `ArrayBuffer.concat([], { length: Number.MAX_SAFE_INTEGER + 1 })` → RangeError + +#### 9.3.5 Length is -0 (treated as 0) + +- [ ] [9.3.5.1] `ArrayBuffer.concat([new ArrayBuffer(4)], { length: -0 })` → ArrayBuffer of byteLength 0 + +#### 9.3.6 Length is `undefined` (same as not provided) + +- [ ] [9.3.6.1] `ArrayBuffer.concat([new ArrayBuffer(4)], { length: undefined })` → byteLength 4 (defaults to total) + +### 9.4 Resizable and immutable options + +- [ ] [9.4.1] `ArrayBuffer.concat([], { resizable: true, immutable: true })` → TypeError (mutually exclusive) +- [ ] [9.4.2] `ArrayBuffer.concat([], { resizable: true, immutable: false })` → works +- [ ] [9.4.3] `ArrayBuffer.concat([], { resizable: false, immutable: true })` → works +- [ ] [9.4.4] `ArrayBuffer.concat([], { resizable: false, immutable: false })` → works (default behavior) + +### 9.5 Resizable and immutable are coerced via `ToBoolean` + +- [ ] [9.5.1] `ArrayBuffer.concat([], { resizable: 1 })` → result is resizable (truthy) +- [ ] [9.5.2] `ArrayBuffer.concat([], { resizable: 0 })` → result is not resizable (falsy) +- [ ] [9.5.3] `ArrayBuffer.concat([], { resizable: '' })` → result is not resizable (falsy) +- [ ] [9.5.4] `ArrayBuffer.concat([], { resizable: 'yes' })` → result is resizable (truthy) +- [ ] [9.5.5] `ArrayBuffer.concat([], { immutable: 1 })` → result is immutable (truthy) +- [ ] [9.5.6] `ArrayBuffer.concat([], { immutable: 0 })` → result is not immutable (falsy) +- [ ] [9.5.7] `ArrayBuffer.concat([], { resizable: null })` → result is not resizable (falsy) +- [ ] [9.5.8] `ArrayBuffer.concat([], { immutable: undefined })` → result is not immutable (falsy) + +--- + +## 10. `ArrayBuffer.concat` — Overflow Check on `totalByteLength` + +### 10.1 Total byte length exceeds 2^53 - 1 + +- [ ] [10.1.1] Concatenating items whose combined byte lengths exceed 2^53 - 1 → RangeError +- [ ] [10.1.2] Overflow check is per-item (fails as soon as running total exceeds limit) +- [ ] [10.1.3] Overflow check applies regardless of which item type triggers it (ArrayBuffer, TypedArray, or DataView) + +Note: As with §4, creating buffers large enough to trigger this may be implementation-limited. + +--- + +## 11. `ArrayBuffer.concat` — Basic Concatenation + +### 11.1 ArrayBuffer inputs + +- [ ] [11.1.1] Two ArrayBuffers: + ```js + const ab1 = new ArrayBuffer(4); + new Uint8Array(ab1).set([1, 2, 3, 4]); + const ab2 = new ArrayBuffer(4); + new Uint8Array(ab2).set([5, 6, 7, 8]); + const result = ArrayBuffer.concat([ab1, ab2]); + // result.byteLength === 8 + // new Uint8Array(result) → [1, 2, 3, 4, 5, 6, 7, 8] + ``` +- [ ] [11.1.2] Single ArrayBuffer → copy of the input (new buffer, not same object) +- [ ] [11.1.3] Three or more ArrayBuffers → concatenated in order + +### 11.2 TypedArray inputs (viewed portion only) + +- [ ] [11.2.1] Full-buffer TypedArray: + ```js + const u8 = new Uint8Array([1, 2, 3, 4]); + const result = ArrayBuffer.concat([u8]); + // result.byteLength === 4 + ``` +- [ ] [11.2.2] TypedArray with byte offset: + ```js + const ab = new ArrayBuffer(8); + new Uint8Array(ab).set([1, 2, 3, 4, 5, 6, 7, 8]); + const view = new Uint8Array(ab, 2, 3); // views bytes [3, 4, 5] + const result = ArrayBuffer.concat([view]); + // result.byteLength === 3 + // new Uint8Array(result) → [3, 4, 5] + ``` +- [ ] [11.2.3] Multi-byte element TypedArray (only viewed portion bytes): + ```js + const i32 = new Int32Array([1, 2]); // 8 bytes + const result = ArrayBuffer.concat([i32]); + // result.byteLength === 8 + ``` +- [ ] [11.2.4] TypedArray with non-zero offset and multi-byte elements: + ```js + const ab = new ArrayBuffer(16); + const view = new Int32Array(ab, 4, 2); // 8 bytes starting at offset 4 + const result = ArrayBuffer.concat([view]); + // result.byteLength === 8 + ``` + +### 11.3 DataView inputs (viewed portion only) + +- [ ] [11.3.1] Full-buffer DataView: + ```js + const ab = new ArrayBuffer(4); + new Uint8Array(ab).set([1, 2, 3, 4]); + const dv = new DataView(ab); + const result = ArrayBuffer.concat([dv]); + // result.byteLength === 4 + ``` +- [ ] [11.3.2] DataView with byte offset and length: + ```js + const ab = new ArrayBuffer(8); + new Uint8Array(ab).set([1, 2, 3, 4, 5, 6, 7, 8]); + const dv = new DataView(ab, 2, 3); // views bytes [3, 4, 5] + const result = ArrayBuffer.concat([dv]); + // result.byteLength === 3 + // new Uint8Array(result) → [3, 4, 5] + ``` + +### 11.4 Mixed input types + +- [ ] [11.4.1] ArrayBuffer + TypedArray + DataView: + ```js + const ab = new ArrayBuffer(2); + new Uint8Array(ab).set([1, 2]); + const u8 = new Uint8Array([3, 4]); + const dv = new DataView(new ArrayBuffer(2)); + new Uint8Array(dv.buffer).set([5, 6]); + const result = ArrayBuffer.concat([ab, u8, dv]); + // result.byteLength === 6 + // new Uint8Array(result) → [1, 2, 3, 4, 5, 6] + ``` + +### 11.5 Empty inputs + +- [ ] [11.5.1] `ArrayBuffer.concat([])` → ArrayBuffer of byteLength 0 +- [ ] [11.5.2] `ArrayBuffer.concat([new ArrayBuffer(0)])` → ArrayBuffer of byteLength 0 +- [ ] [11.5.3] `ArrayBuffer.concat([new ArrayBuffer(0), new ArrayBuffer(0)])` → ArrayBuffer of byteLength 0 +- [ ] [11.5.4] `ArrayBuffer.concat([new ArrayBuffer(0), new Uint8Array([1, 2])])` → byteLength 2 + +### 11.6 Result is always a new ArrayBuffer + +- [ ] [11.6.1] Result is not the same object as any input: + ```js + const ab = new ArrayBuffer(4); + const result = ArrayBuffer.concat([ab]); + result !== ab // → true + ``` + +--- + +## 12. `ArrayBuffer.concat` — Truncation and Zero-Fill + +### 12.1 Truncation (length < totalByteLength) + +- [ ] [12.1.1] `ArrayBuffer.concat([new ArrayBuffer(8)], { length: 4 })` → byteLength 4 +- [ ] [12.1.2] Truncation mid-second-item: + ```js + const ab1 = new ArrayBuffer(4); + new Uint8Array(ab1).set([1, 2, 3, 4]); + const ab2 = new ArrayBuffer(4); + new Uint8Array(ab2).set([5, 6, 7, 8]); + const result = ArrayBuffer.concat([ab1, ab2], { length: 6 }); + // new Uint8Array(result) → [1, 2, 3, 4, 5, 6] + ``` +- [ ] [12.1.3] Truncation to 0: `ArrayBuffer.concat([new ArrayBuffer(4)], { length: 0 })` → byteLength 0 + +### 12.2 Zero-fill (length > totalByteLength) + +- [ ] [12.2.1] `ArrayBuffer.concat([new ArrayBuffer(4)], { length: 8 })` → byteLength 8, last 4 bytes are 0 +- [ ] [12.2.2] `ArrayBuffer.concat([], { length: 4 })` → byteLength 4, all bytes 0 +- [ ] [12.2.3] Verify zero-fill bytes are actually 0: + ```js + const ab = new ArrayBuffer(2); + new Uint8Array(ab).set([0xFF, 0xFF]); + const result = ArrayBuffer.concat([ab], { length: 4 }); + const u8 = new Uint8Array(result); + // u8[0] === 0xFF, u8[1] === 0xFF, u8[2] === 0, u8[3] === 0 + ``` + +### 12.3 Exact length + +- [ ] [12.3.1] `ArrayBuffer.concat([new ArrayBuffer(4)], { length: 4 })` → byteLength 4 (same as no length) + +--- + +## 13. `ArrayBuffer.concat` — Resizable Option + +### 13.1 Basic resizable result + +- [ ] [13.1.1] Resizable with explicit length: + ```js + const result = ArrayBuffer.concat([new ArrayBuffer(4)], { resizable: true, length: 16 }); + result.resizable // → true + result.byteLength // → 4 (actual data) + result.maxByteLength // → 16 + ``` +- [ ] [13.1.2] Resizable result can be grown: + ```js + const result = ArrayBuffer.concat([new ArrayBuffer(4)], { resizable: true, length: 16 }); + result.resize(8); + result.byteLength // → 8 + ``` +- [ ] [13.1.3] Resizable result can be shrunk: + ```js + const result = ArrayBuffer.concat([new ArrayBuffer(4)], { resizable: true, length: 16 }); + result.resize(2); + result.byteLength // → 2 + ``` + +### 13.2 Resizable without explicit length (maxByteLength = totalByteLength) + +- [ ] [13.2.1] `ArrayBuffer.concat([new ArrayBuffer(4)], { resizable: true })` → `byteLength === 4`, `maxByteLength === 4` (buffer already at max) +- [ ] [13.2.2] Can be shrunk but not grown beyond totalByteLength: + ```js + const result = ArrayBuffer.concat([new ArrayBuffer(4)], { resizable: true }); + result.resize(2); // works + result.resize(5); // → RangeError (exceeds maxByteLength) + ``` + +### 13.3 Resizable with length less than totalByteLength + +- [ ] [13.3.1] `maxByteLength` is `length`, `byteLength` is clamped: + ```js + const ab1 = new ArrayBuffer(4); + const ab2 = new ArrayBuffer(4); + const result = ArrayBuffer.concat([ab1, ab2], { resizable: true, length: 6 }); + result.byteLength // → 6 (clamped: min(8, 6)) + result.maxByteLength // → 6 + ``` + +### 13.4 Resizable with length greater than totalByteLength + +- [ ] [13.4.1] `byteLength` equals total data, `maxByteLength` equals `length`: + ```js + const result = ArrayBuffer.concat([new ArrayBuffer(4)], { resizable: true, length: 32 }); + result.byteLength // → 4 (actual data) + result.maxByteLength // → 32 (room to grow) + ``` + +### 13.5 Data integrity in resizable result + +- [ ] [13.5.1] Source data is correctly copied into resizable result: + ```js + const ab = new ArrayBuffer(4); + new Uint8Array(ab).set([1, 2, 3, 4]); + const result = ArrayBuffer.concat([ab], { resizable: true, length: 16 }); + new Uint8Array(result, 0, 4) // → [1, 2, 3, 4] + ``` + +--- + +## 14. `ArrayBuffer.concat` — Immutable Option + +Note: These tests depend on the [Immutable ArrayBuffer proposal](https://github.com/tc39/proposal-immutable-arraybuffer). + +### 14.1 Basic immutable result + +- [ ] [14.1.1] `ArrayBuffer.concat([new ArrayBuffer(4)], { immutable: true })` → result has `immutable === true` +- [ ] [14.1.2] Immutable result cannot be resized → TypeError +- [ ] [14.1.3] Immutable result cannot be detached/transferred → TypeError + +### 14.2 Data integrity in immutable result + +- [ ] [14.2.1] Source data is correctly copied before immutability is applied: + ```js + const ab = new ArrayBuffer(4); + new Uint8Array(ab).set([1, 2, 3, 4]); + const result = ArrayBuffer.concat([ab], { immutable: true }); + new Uint8Array(result) // → [1, 2, 3, 4] + ``` + +### 14.3 Immutable with explicit length + +- [ ] [14.3.1] Truncation: `ArrayBuffer.concat([new ArrayBuffer(8)], { immutable: true, length: 4 })` → immutable, byteLength 4 +- [ ] [14.3.2] Zero-fill: `ArrayBuffer.concat([new ArrayBuffer(4)], { immutable: true, length: 8 })` → immutable, byteLength 8 + +### 14.4 Immutable with empty inputs + +- [ ] [14.4.1] `ArrayBuffer.concat([], { immutable: true })` → immutable ArrayBuffer of byteLength 0 + +--- + +## 15. `ArrayBuffer.concat` — Overflow Check on `totalByteLength` + +Covered in §10. Included here as a cross-reference. + +--- + +## 16. Evaluation Order and Observable Side Effects + +### 16.1 `%TypedArray%.concat` evaluation order + +- [ ] [16.1.1] `this` validation occurs before items iteration: + ```js + let iteratorCalled = false; + const items = { [Symbol.iterator]() { iteratorCalled = true; return [][Symbol.iterator](); } }; + try { %TypedArray%.concat.call(42, items); } catch(e) {} + iteratorCalled // → false + ``` +- [ ] [16.1.2] Items iteration occurs before length validation: + ```js + // Invalid length, but items are iterated first + let iteratorCalled = false; + const items = { [Symbol.iterator]() { iteratorCalled = true; return [][Symbol.iterator](); } }; + try { Uint8Array.concat(items, NaN); } catch(e) {} + iteratorCalled // → true + ``` + + Wait — actually, looking at the spec, items are iterated (step 4) _before_ length is validated (step 6). Let me re-examine... + + Actually no: step 4 iterates items, step 5-7 validates length. So items iteration happens first, then length validation. But the items in the list are _validated_ (step 9) after length validation. So: + +- [ ] [16.1.3] Items are collected via iteration before length validation, but individual items are validated (via `ValidateTypedArray`) _after_ length validation: + ```js + // Invalid length AND invalid item — length error wins because length is checked first (step 6) + // Wait, step 4 iterates, steps 5-7 validate length, steps 8-11 validate items + // So: iterate items first, then validate length, then validate each item + Uint8Array.concat([42], 'bad') // → TypeError from length validation ('bad' is not a Number) + ``` + +- [ ] [16.1.4] Length validation occurs before item type checking: + ```js + Uint8Array.concat([new Int16Array([1])], -1) // → RangeError (from length, not TypeError from type mismatch) + ``` + +### 16.2 `ArrayBuffer.concat` evaluation order + +- [ ] [16.2.1] Items iteration occurs before options processing: + ```js + // Items are iterated at step 1, options are read starting at step 2 + let iteratorCalled = false; + const items = { [Symbol.iterator]() { iteratorCalled = true; return [][Symbol.iterator](); } }; + try { ArrayBuffer.concat(items, 42); } catch(e) {} // options error + iteratorCalled // → true + ``` +- [ ] [16.2.2] Options processing (length, resizable, immutable) occurs before item validation: + ```js + // Invalid options AND invalid items — options error wins + const detached = new ArrayBuffer(4); + detached.transfer(); + ArrayBuffer.concat([detached], { length: 'bad' }) // → TypeError from length validation + ``` +- [ ] [16.2.3] `resizable`/`immutable` mutual exclusion check occurs before item validation: + ```js + const detached = new ArrayBuffer(4); + detached.transfer(); + ArrayBuffer.concat([detached], { resizable: true, immutable: true }) // → TypeError (mutual exclusion) + ``` + +### 16.3 `SharedArrayBuffer.concat` evaluation order + +- [ ] [16.3.1] Items iteration occurs before options processing: + ```js + let iteratorCalled = false; + const items = { [Symbol.iterator]() { iteratorCalled = true; return [][Symbol.iterator](); } }; + try { SharedArrayBuffer.concat(items, 42); } catch(e) {} // options error + iteratorCalled // → true + ``` +- [ ] [16.3.2] Options processing (length, growable) occurs before item validation: + ```js + const detached = new ArrayBuffer(4); + detached.transfer(); + SharedArrayBuffer.concat([detached], { length: 'bad' }) // → TypeError from length validation + ``` + +### 16.4 Items iterator with side effects + +- [ ] [16.4.1] `%TypedArray%.concat`: Custom iterable that tracks iteration count → all items collected before any validation +- [ ] [16.4.2] `ArrayBuffer.concat`: Custom iterable that tracks iteration count → all items collected before any validation +- [ ] [16.4.3] `SharedArrayBuffer.concat`: Custom iterable that tracks iteration count → all items collected before any validation + +--- + +## 17. Property and Prototype + +### 17.1 `%TypedArray%.concat` method existence + +- [ ] [17.1.1] `typeof Uint8Array.concat` → `'function'` +- [ ] [17.1.2] `Uint8Array.concat === Int32Array.concat` → true (shared on %TypedArray%) + +### 17.2 `%TypedArray%.concat` method properties + +- [ ] [17.2.1] `Uint8Array.concat.length` → 1 (one required parameter: `items`) +- [ ] [17.2.2] `Uint8Array.concat.name` → `'concat'` + +### 17.3 `ArrayBuffer.concat` method existence + +- [ ] [17.3.1] `typeof ArrayBuffer.concat` → `'function'` + +### 17.4 `ArrayBuffer.concat` method properties + +- [ ] [17.4.1] `ArrayBuffer.concat.length` → 1 (one required parameter: `items`) +- [ ] [17.4.2] `ArrayBuffer.concat.name` → `'concat'` + +### 17.5 `SharedArrayBuffer.concat` method existence + +- [ ] [17.5.1] `typeof SharedArrayBuffer.concat` → `'function'` + +### 17.6 `SharedArrayBuffer.concat` method properties + +- [ ] [17.6.1] `SharedArrayBuffer.concat.length` → 1 (one required parameter: `items`) +- [ ] [17.6.2] `SharedArrayBuffer.concat.name` → `'concat'` + +### 17.7 Not enumerable + +- [ ] [17.7.1] `Object.getOwnPropertyDescriptor(Uint8Array, 'concat').enumerable` → false +- [ ] [17.7.2] `Object.getOwnPropertyDescriptor(ArrayBuffer, 'concat').enumerable` → false +- [ ] [17.7.3] `Object.getOwnPropertyDescriptor(SharedArrayBuffer, 'concat').enumerable` → false + +### 17.8 Configurable and writable + +- [ ] [17.8.1] `Object.getOwnPropertyDescriptor(Uint8Array, 'concat').configurable` → true +- [ ] [17.8.2] `Object.getOwnPropertyDescriptor(Uint8Array, 'concat').writable` → true +- [ ] [17.8.3] `Object.getOwnPropertyDescriptor(ArrayBuffer, 'concat').configurable` → true +- [ ] [17.8.4] `Object.getOwnPropertyDescriptor(ArrayBuffer, 'concat').writable` → true +- [ ] [17.8.5] `Object.getOwnPropertyDescriptor(SharedArrayBuffer, 'concat').configurable` → true +- [ ] [17.8.6] `Object.getOwnPropertyDescriptor(SharedArrayBuffer, 'concat').writable` → true + +--- + +## 18. Resizable ArrayBuffer Considerations + +The key invariant is that only the **current** `byteLength` of a resizable ArrayBuffer is used, never the `maxByteLength`. For TypedArray views over resizable buffers, only the currently-visible elements are copied. The `maxByteLength` capacity beyond `byteLength` is uninitialized memory and must never be included. + +### 18.1 `%TypedArray%.concat` — Only current elements are copied (not max capacity) + +- [ ] [18.1.1] Auto-length TypedArray over a resizable buffer — copies only current elements, not max capacity: + ```js + const rab = new ArrayBuffer(4, { maxByteLength: 32 }); + const u8 = new Uint8Array(rab); // auto-length, tracks byteLength + u8.set([1, 2, 3, 4]); + const result = Uint8Array.concat([u8]); + result.length // → 4 (not 32) + new Uint8Array(result) // → [1, 2, 3, 4] + ``` +- [ ] [18.1.2] Fixed-length TypedArray over a resizable buffer — copies only the fixed element count: + ```js + const rab = new ArrayBuffer(16, { maxByteLength: 64 }); + new Uint8Array(rab).set([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]); + const u8 = new Uint8Array(rab, 0, 4); // fixed-length: 4 elements + const result = Uint8Array.concat([u8]); + result.length // → 4 (not 16 or 64) + new Uint8Array(result) // → [1, 2, 3, 4] + ``` +- [ ] [18.1.3] Two auto-length TypedArrays over resizable buffers with different current sizes: + ```js + const rab1 = new ArrayBuffer(3, { maxByteLength: 100 }); + const rab2 = new ArrayBuffer(2, { maxByteLength: 200 }); + const a = new Uint8Array(rab1); + const b = new Uint8Array(rab2); + a.set([1, 2, 3]); + b.set([4, 5]); + const result = Uint8Array.concat([a, b]); + result.length // → 5 (3 + 2, not 100 + 200) + new Uint8Array(result) // → [1, 2, 3, 4, 5] + ``` +- [ ] [18.1.4] Auto-length TypedArray after the buffer has been grown — copies only the pre-growth data region: + ```js + const rab = new ArrayBuffer(4, { maxByteLength: 32 }); + const u8 = new Uint8Array(rab); + u8.set([1, 2, 3, 4]); + rab.resize(16); // grow to 16 bytes; u8.length is now 16 + const result = Uint8Array.concat([u8]); + result.length // → 16 (auto-length tracks current byteLength) + // First 4 bytes are [1,2,3,4], remaining 12 are 0 (zero-initialized by resize) + ``` +- [ ] [18.1.5] Auto-length TypedArray after the buffer has been shrunk — copies only the post-shrink elements: + ```js + const rab = new ArrayBuffer(8, { maxByteLength: 32 }); + const u8 = new Uint8Array(rab); + u8.set([1, 2, 3, 4, 5, 6, 7, 8]); + rab.resize(3); // shrink to 3 bytes; u8.length is now 3 + const result = Uint8Array.concat([u8]); + result.length // → 3 + new Uint8Array(result) // → [1, 2, 3] + ``` +- [ ] [18.1.6] Multi-byte element TypedArray (Int32Array) over resizable buffer — element count reflects current byteLength / elementSize: + ```js + const rab = new ArrayBuffer(8, { maxByteLength: 64 }); + const i32 = new Int32Array(rab); // auto-length: 2 elements (8 / 4) + i32.set([100, 200]); + const result = Int32Array.concat([i32]); + result.length // → 2 (not 64 / 4 = 16) + new Int32Array(result) // → [100, 200] + ``` + +### 18.2 `%TypedArray%.concat` — Resizable buffer edge cases + +- [ ] [18.2.1] Fixed-length TypedArray whose resizable buffer has been shrunk below the fixed range → TypeError from `ValidateTypedArray`: + ```js + const rab = new ArrayBuffer(8, { maxByteLength: 16 }); + const u8 = new Uint8Array(rab, 0, 8); // fixed-length: 8 + rab.resize(4); // shrink below the fixed range + Uint8Array.concat([u8]) // → TypeError (TypedArray is out of bounds) + ``` +- [ ] [18.2.2] Fixed-length TypedArray with byteOffset, buffer shrunk below offset → TypeError: + ```js + const rab = new ArrayBuffer(16, { maxByteLength: 32 }); + const u8 = new Uint8Array(rab, 8, 4); // offset 8, length 4 + rab.resize(4); // shrink below the offset + Uint8Array.concat([u8]) // → TypeError (out of bounds) + ``` +- [ ] [18.2.3] Auto-length TypedArray over a buffer resized to 0 → copies zero elements: + ```js + const rab = new ArrayBuffer(8, { maxByteLength: 16 }); + const u8 = new Uint8Array(rab); + u8.set([1, 2, 3, 4, 5, 6, 7, 8]); + rab.resize(0); + const result = Uint8Array.concat([u8]); + result.length // → 0 + ``` + +### 18.3 `ArrayBuffer.concat` — Only current byteLength is copied (not maxByteLength) + +- [ ] [18.3.1] Resizable ArrayBuffer passed directly — copies current byteLength, ignores maxByteLength: + ```js + const rab = new ArrayBuffer(4, { maxByteLength: 64 }); + new Uint8Array(rab).set([1, 2, 3, 4]); + const result = ArrayBuffer.concat([rab]); + result.byteLength // → 4 (not 64) + new Uint8Array(result) // → [1, 2, 3, 4] + ``` +- [ ] [18.3.2] Two resizable ArrayBuffers — total byteLength is sum of current byteLengths: + ```js + const rab1 = new ArrayBuffer(3, { maxByteLength: 100 }); + const rab2 = new ArrayBuffer(2, { maxByteLength: 200 }); + new Uint8Array(rab1).set([1, 2, 3]); + new Uint8Array(rab2).set([4, 5]); + const result = ArrayBuffer.concat([rab1, rab2]); + result.byteLength // → 5 (not 300) + new Uint8Array(result) // → [1, 2, 3, 4, 5] + ``` +- [ ] [18.3.3] Resizable ArrayBuffer after grow — copies the grown size (including zero-initialized region): + ```js + const rab = new ArrayBuffer(4, { maxByteLength: 32 }); + new Uint8Array(rab).set([1, 2, 3, 4]); + rab.resize(8); // grow; bytes 4-7 are zero-initialized + const result = ArrayBuffer.concat([rab]); + result.byteLength // → 8 + new Uint8Array(result) // → [1, 2, 3, 4, 0, 0, 0, 0] + ``` +- [ ] [18.3.4] Resizable ArrayBuffer after shrink — copies only the post-shrink bytes: + ```js + const rab = new ArrayBuffer(8, { maxByteLength: 32 }); + new Uint8Array(rab).set([1, 2, 3, 4, 5, 6, 7, 8]); + rab.resize(3); + const result = ArrayBuffer.concat([rab]); + result.byteLength // → 3 + new Uint8Array(result) // → [1, 2, 3] + ``` +- [ ] [18.3.5] Resizable ArrayBuffer resized to 0 — contributes 0 bytes: + ```js + const rab = new ArrayBuffer(8, { maxByteLength: 16 }); + rab.resize(0); + const result = ArrayBuffer.concat([rab, new ArrayBuffer(2)]); + result.byteLength // → 2 + ``` + +### 18.4 `ArrayBuffer.concat` — TypedArray/DataView over resizable buffers (viewed portion only) + +- [ ] [18.4.1] Auto-length TypedArray over resizable buffer — only current viewed bytes: + ```js + const rab = new ArrayBuffer(4, { maxByteLength: 64 }); + new Uint8Array(rab).set([10, 20, 30, 40]); + const u8 = new Uint8Array(rab); // auto-length + const result = ArrayBuffer.concat([u8]); + result.byteLength // → 4 (not 64) + new Uint8Array(result) // → [10, 20, 30, 40] + ``` +- [ ] [18.4.2] Fixed-length TypedArray with offset over resizable buffer: + ```js + const rab = new ArrayBuffer(16, { maxByteLength: 64 }); + new Uint8Array(rab).set([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]); + const u8 = new Uint8Array(rab, 4, 4); // bytes 4-7: [5, 6, 7, 8] + const result = ArrayBuffer.concat([u8]); + result.byteLength // → 4 (not 64 or 16) + new Uint8Array(result) // → [5, 6, 7, 8] + ``` +- [ ] [18.4.3] Auto-length DataView over resizable buffer: + ```js + const rab = new ArrayBuffer(4, { maxByteLength: 64 }); + new Uint8Array(rab).set([10, 20, 30, 40]); + const dv = new DataView(rab); // auto-length + const result = ArrayBuffer.concat([dv]); + result.byteLength // → 4 (not 64) + new Uint8Array(result) // → [10, 20, 30, 40] + ``` +- [ ] [18.4.4] Fixed-length DataView with offset over resizable buffer: + ```js + const rab = new ArrayBuffer(16, { maxByteLength: 64 }); + new Uint8Array(rab).set([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]); + const dv = new DataView(rab, 4, 4); // bytes 4-7 + const result = ArrayBuffer.concat([dv]); + result.byteLength // → 4 + new Uint8Array(result) // → [5, 6, 7, 8] + ``` +- [ ] [18.4.5] Multi-byte element TypedArray over resizable buffer — byte length is element count × element size: + ```js + const rab = new ArrayBuffer(8, { maxByteLength: 64 }); + const i32 = new Int32Array(rab); // 2 elements, 8 bytes + i32.set([100, 200]); + const result = ArrayBuffer.concat([i32]); + result.byteLength // → 8 (not 64) + ``` +- [ ] [18.4.6] Fixed-length TypedArray whose resizable buffer was shrunk below range → TypeError: + ```js + const rab = new ArrayBuffer(8, { maxByteLength: 16 }); + const u8 = new Uint8Array(rab, 0, 8); + rab.resize(4); + ArrayBuffer.concat([u8]) // → TypeError (ValidateTypedArray: out of bounds) + ``` +- [ ] [18.4.7] Fixed-length DataView whose resizable buffer was shrunk below range → TypeError: + ```js + const rab = new ArrayBuffer(8, { maxByteLength: 16 }); + const dv = new DataView(rab, 0, 8); + rab.resize(4); + ArrayBuffer.concat([dv]) // → TypeError (IsViewOutOfBounds) + ``` + +### 18.5 Mixed resizable and fixed-size inputs + +- [ ] [18.5.1] `%TypedArray%.concat` with a mix of resizable-backed and fixed-backed TypedArrays: + ```js + const rab = new ArrayBuffer(3, { maxByteLength: 100 }); + new Uint8Array(rab).set([1, 2, 3]); + const resizableView = new Uint8Array(rab); + const fixedView = new Uint8Array([4, 5]); + const result = Uint8Array.concat([resizableView, fixedView]); + result.length // → 5 + new Uint8Array(result) // → [1, 2, 3, 4, 5] + ``` +- [ ] [18.5.2] `ArrayBuffer.concat` with resizable ArrayBuffer, fixed ArrayBuffer, and TypedArray over resizable: + ```js + const rab = new ArrayBuffer(2, { maxByteLength: 100 }); + new Uint8Array(rab).set([1, 2]); + const fixed = new ArrayBuffer(2); + new Uint8Array(fixed).set([3, 4]); + const rab2 = new ArrayBuffer(2, { maxByteLength: 200 }); + new Uint8Array(rab2).set([5, 6]); + const view = new Uint8Array(rab2); + const result = ArrayBuffer.concat([rab, fixed, view]); + result.byteLength // → 6 (2 + 2 + 2, not 100 + 2 + 200) + new Uint8Array(result) // → [1, 2, 3, 4, 5, 6] + ``` + +--- + +## 19. Items Iterable Variations + +Both `%TypedArray%.concat` and `ArrayBuffer.concat` accept any iterable for the `items` parameter. + +### 19.1 `%TypedArray%.concat` iterable types + +- [ ] [19.1.1] Plain Array: `Uint8Array.concat([new Uint8Array([1]), new Uint8Array([2])])` → works +- [ ] [19.1.2] Generator: + ```js + function* gen() { yield new Uint8Array([1]); yield new Uint8Array([2]); } + Uint8Array.concat(gen()) // → Uint8Array [1, 2] + ``` +- [ ] [19.1.3] Set: + ```js + const a = new Uint8Array([1]); + const b = new Uint8Array([2]); + Uint8Array.concat(new Set([a, b])) // → Uint8Array [1, 2] + ``` +- [ ] [19.1.4] Custom iterable: + ```js + const items = { + [Symbol.iterator]() { + let i = 0; + const arrays = [new Uint8Array([1]), new Uint8Array([2])]; + return { next() { return i < arrays.length ? { value: arrays[i++], done: false } : { done: true }; } }; + } + }; + Uint8Array.concat(items) // → Uint8Array [1, 2] + ``` + +### 19.2 `ArrayBuffer.concat` iterable types + +- [ ] [19.2.1] Plain Array: `ArrayBuffer.concat([new ArrayBuffer(2), new ArrayBuffer(2)])` → works +- [ ] [19.2.2] Generator: + ```js + function* gen() { yield new ArrayBuffer(2); yield new Uint8Array([1, 2]); } + ArrayBuffer.concat(gen()) // → ArrayBuffer of byteLength 4 + ``` +- [ ] [19.2.3] Set of mixed types: + ```js + ArrayBuffer.concat(new Set([new ArrayBuffer(2), new Uint8Array([1, 2]), new DataView(new ArrayBuffer(2))])) + // → ArrayBuffer of byteLength 6 + ``` + +--- + +## 20. `%TypedArray%.concat` — Copy Loop Edge Cases + +### 20.1 Short-circuit when result is full + +- [ ] [20.1.1] With `length: 0`, no items are copied even if present: + ```js + const a = new Uint8Array([1, 2, 3]); + const result = Uint8Array.concat([a], 0); + result.length // → 0 + ``` +- [ ] [20.1.2] With `length: 2` and items totalling 6 elements, only the first 2 elements are in the result: + ```js + const result = Uint8Array.concat([new Uint8Array([1, 2, 3]), new Uint8Array([4, 5, 6])], 2); + // result → Uint8Array [1, 2] + ``` + +### 20.2 Items spanning the truncation boundary + +- [ ] [20.2.1] Truncation splits an item's elements: + ```js + const result = Uint8Array.concat([new Uint8Array([1, 2]), new Uint8Array([3, 4, 5])], 4); + // result → Uint8Array [1, 2, 3, 4] (second item truncated from 3 elements to 2) + ``` + +### 20.3 Items where some contribute zero elements + +- [ ] [20.3.1] Mix of empty and non-empty items: + ```js + const result = Uint8Array.concat([new Uint8Array([]), new Uint8Array([1]), new Uint8Array([]), new Uint8Array([2])]); + // result → Uint8Array [1, 2] + ``` + +--- + +## 21. `ArrayBuffer.concat` — Copy Loop Edge Cases + +### 21.1 Short-circuit when result is full + +- [ ] [21.1.1] With `length: 0`, no bytes are copied: + ```js + const result = ArrayBuffer.concat([new ArrayBuffer(4)], { length: 0 }); + result.byteLength // → 0 + ``` +- [ ] [21.1.2] With `length: 2` and items totalling 8 bytes, only the first 2 bytes are in the result + +### 21.2 Copy spans multiple items + +- [ ] [21.2.1] Three items, truncation in the middle of the second: + ```js + const ab1 = new ArrayBuffer(3); + new Uint8Array(ab1).set([1, 2, 3]); + const ab2 = new ArrayBuffer(3); + new Uint8Array(ab2).set([4, 5, 6]); + const ab3 = new ArrayBuffer(3); + new Uint8Array(ab3).set([7, 8, 9]); + const result = ArrayBuffer.concat([ab1, ab2, ab3], { length: 5 }); + // new Uint8Array(result) → [1, 2, 3, 4, 5] + ``` + +### 21.3 Zero-length items in the mix + +- [ ] [21.3.1] `ArrayBuffer.concat([new ArrayBuffer(0), new ArrayBuffer(4), new ArrayBuffer(0)])` → byteLength 4, data from the middle item + +--- + +## 22. `ArrayBuffer.concat` — Immutable + Resizable Interaction + +### 22.1 Mutually exclusive + +- [ ] [22.1.1] `{ resizable: true, immutable: true }` → TypeError +- [ ] [22.1.2] `{ resizable: true, immutable: false }` → resizable result (no error) +- [ ] [22.1.3] `{ resizable: false, immutable: true }` → immutable result (no error) +- [ ] [22.1.4] Neither provided → default (non-resizable, non-immutable) + +### 22.2 Truthy combinations via ToBoolean + +- [ ] [22.2.1] `{ resizable: 1, immutable: 1 }` → TypeError (both truthy) +- [ ] [22.2.2] `{ resizable: 'yes', immutable: 'yes' }` → TypeError (both truthy) +- [ ] [22.2.3] `{ resizable: 1, immutable: 0 }` → works (resizable) +- [ ] [22.2.4] `{ resizable: 0, immutable: 1 }` → works (immutable) + +--- + +## 23. Same Underlying Buffer / Overlapping Views + +### 23.1 `%TypedArray%.concat` — Multiple views of the same buffer + +- [ ] [23.1.1] Two TypedArrays backed by the same ArrayBuffer at different offsets: + ```js + const ab = new ArrayBuffer(8); + new Uint8Array(ab).set([1, 2, 3, 4, 5, 6, 7, 8]); + const a = new Uint8Array(ab, 0, 4); // [1, 2, 3, 4] + const b = new Uint8Array(ab, 4, 4); // [5, 6, 7, 8] + Uint8Array.concat([a, b]) // → Uint8Array [1, 2, 3, 4, 5, 6, 7, 8] + ``` +- [ ] [23.1.2] Same TypedArray passed twice: + ```js + const a = new Uint8Array([1, 2]); + Uint8Array.concat([a, a]) // → Uint8Array [1, 2, 1, 2] + ``` +- [ ] [23.1.3] Overlapping views: + ```js + const ab = new ArrayBuffer(6); + new Uint8Array(ab).set([1, 2, 3, 4, 5, 6]); + const a = new Uint8Array(ab, 0, 4); // [1, 2, 3, 4] + const b = new Uint8Array(ab, 2, 4); // [3, 4, 5, 6] + Uint8Array.concat([a, b]) // → Uint8Array [1, 2, 3, 4, 3, 4, 5, 6] + ``` + +### 23.2 `ArrayBuffer.concat` — Same buffer via different views + +- [ ] [23.2.1] Two TypedArrays viewing different parts of the same buffer: + ```js + const ab = new ArrayBuffer(8); + new Uint8Array(ab).set([1, 2, 3, 4, 5, 6, 7, 8]); + const a = new Uint8Array(ab, 0, 4); + const b = new Uint8Array(ab, 4, 4); + const result = ArrayBuffer.concat([a, b]); + // new Uint8Array(result) → [1, 2, 3, 4, 5, 6, 7, 8] + ``` +- [ ] [23.2.2] Same ArrayBuffer passed twice as direct item: + ```js + const ab = new ArrayBuffer(4); + new Uint8Array(ab).set([1, 2, 3, 4]); + const result = ArrayBuffer.concat([ab, ab]); + // new Uint8Array(result) → [1, 2, 3, 4, 1, 2, 3, 4] + ``` +- [ ] [23.2.3] TypedArray and DataView viewing the same buffer: + ```js + const ab = new ArrayBuffer(4); + new Uint8Array(ab).set([1, 2, 3, 4]); + const u8 = new Uint8Array(ab, 0, 2); + const dv = new DataView(ab, 2, 2); + const result = ArrayBuffer.concat([u8, dv]); + // new Uint8Array(result) → [1, 2, 3, 4] + ``` + +--- + +## 24. Large-Scale and Stress Tests + +### 24.1 Many items + +- [ ] [24.1.1] `Uint8Array.concat` with 1000 single-element TypedArrays → result has 1000 elements in correct order +- [ ] [24.1.2] `ArrayBuffer.concat` with 1000 single-byte ArrayBuffers → result has byteLength 1000 + +### 24.2 Large individual items + +- [ ] [24.2.1] `Uint8Array.concat` with a single 1MB TypedArray → result matches +- [ ] [24.2.2] `ArrayBuffer.concat` with two 1MB ArrayBuffers → result has byteLength 2MB, data correct + +### 24.3 Combination of many small and few large + +- [ ] [24.3.1] Mix of 100-byte and 1-byte items → total length and data are correct + +--- + +## 25. Tamper Resistance — Overridden Properties and Prototype Pollution + +The spec algorithms read internal slots (`[[ArrayLength]]`, `[[ByteLength]]`, `[[ByteOffset]]`, `[[ViewedArrayBuffer]]`, `[[TypedArrayName]]`, `[[ArrayBufferData]]`, `[[ArrayBufferByteLength]]`) and use abstract operations (`TypedArrayLength`, `TypedArrayByteLength`, `ValidateTypedArray`, etc.) that bypass JavaScript-visible property access. These tests verify that overriding or shadowing properties has no observable effect on the result. + +### 25.1 `%TypedArray%.concat` — Overridden TypedArray properties + +#### 25.1.1 Overridden `.length` on items + +- [ ] [25.1.1.1] `Object.defineProperty` to increase `.length`: + ```js + const u8 = new Uint8Array([1, 2, 3]); + Object.defineProperty(u8, 'length', { value: 100 }); + const result = Uint8Array.concat([u8]); + result.length // → 3 (uses [[ArrayLength]], not .length) + new Uint8Array(result) // → [1, 2, 3] + ``` +- [ ] [25.1.1.2] `Object.defineProperty` to decrease `.length`: + ```js + const u8 = new Uint8Array([1, 2, 3, 4, 5]); + Object.defineProperty(u8, 'length', { value: 1 }); + const result = Uint8Array.concat([u8]); + result.length // → 5 (uses [[ArrayLength]], not .length) + ``` +- [ ] [25.1.1.3] `.length` set to 0 on non-empty TypedArray: + ```js + const u8 = new Uint8Array([1, 2, 3]); + Object.defineProperty(u8, 'length', { value: 0 }); + const result = Uint8Array.concat([u8]); + result.length // → 3 + ``` + +#### 25.1.2 Overridden `.byteLength` on items + +- [ ] [25.1.2.1] Overridden `.byteLength` on TypedArray item: + ```js + const u8 = new Uint8Array([1, 2, 3]); + Object.defineProperty(u8, 'byteLength', { value: 100 }); + const result = Uint8Array.concat([u8]); + result.length // → 3 (not influenced by fake byteLength) + ``` + +#### 25.1.3 Overridden `.byteOffset` on items + +- [ ] [25.1.3.1] Overridden `.byteOffset` on TypedArray item: + ```js + const ab = new ArrayBuffer(8); + new Uint8Array(ab).set([1, 2, 3, 4, 5, 6, 7, 8]); + const u8 = new Uint8Array(ab, 2, 3); // views [3, 4, 5] + Object.defineProperty(u8, 'byteOffset', { value: 0 }); + const result = Uint8Array.concat([u8]); + // result → Uint8Array [3, 4, 5] (uses [[ByteOffset]], not .byteOffset) + ``` + +#### 25.1.4 Overridden `.buffer` on items + +- [ ] [25.1.4.1] Overridden `.buffer` pointing to a different ArrayBuffer: + ```js + const u8 = new Uint8Array([1, 2, 3]); + const fakeBuffer = new ArrayBuffer(100); + Object.defineProperty(u8, 'buffer', { value: fakeBuffer }); + const result = Uint8Array.concat([u8]); + result.length // → 3 (uses [[ViewedArrayBuffer]], not .buffer) + new Uint8Array(result) // → [1, 2, 3] + ``` + +#### 25.1.5 Overridden `Symbol.iterator` on items array (not on TypedArray items themselves) + +- [ ] [25.1.5.1] Items array with overridden `Symbol.iterator` that yields extra items: + ```js + const a = new Uint8Array([1, 2]); + const items = [a]; + items[Symbol.iterator] = function*() { yield a; yield a; yield a; }; + const result = Uint8Array.concat(items); + result.length // → 6 (iterator is respected for the items iterable) + new Uint8Array(result) // → [1, 2, 1, 2, 1, 2] + ``` + +### 25.2 `%TypedArray%.concat` — Overridden constructor properties + +#### 25.2.1 Overridden `Symbol.species` or `@@species` + +- [ ] [25.2.1.1] The spec uses `_C_` directly (the `this` value) without consulting `Symbol.species`. Overriding `Symbol.species` should have no effect: + ```js + class MyUint8Array extends Uint8Array {} + Object.defineProperty(Uint8Array, Symbol.species, { value: MyUint8Array }); + const result = Uint8Array.concat([new Uint8Array([1, 2])]); + result.constructor // → Uint8Array (not MyUint8Array; species is not consulted) + ``` + +### 25.3 `ArrayBuffer.concat` — Overridden ArrayBuffer properties + +#### 25.3.1 Overridden `.byteLength` on ArrayBuffer items + +- [ ] [25.3.1.1] `Object.defineProperty` to increase `.byteLength`: + ```js + const ab = new ArrayBuffer(4); + new Uint8Array(ab).set([1, 2, 3, 4]); + Object.defineProperty(ab, 'byteLength', { value: 100 }); + const result = ArrayBuffer.concat([ab]); + result.byteLength // → 4 (uses [[ArrayBufferByteLength]], not .byteLength) + ``` +- [ ] [25.3.1.2] `Object.defineProperty` to decrease `.byteLength`: + ```js + const ab = new ArrayBuffer(4); + new Uint8Array(ab).set([1, 2, 3, 4]); + Object.defineProperty(ab, 'byteLength', { value: 1 }); + const result = ArrayBuffer.concat([ab]); + result.byteLength // → 4 + ``` + +#### 25.3.2 Overridden properties on TypedArray items (used in ArrayBuffer.concat) + +- [ ] [25.3.2.1] Overridden `.byteLength` on TypedArray passed to `ArrayBuffer.concat`: + ```js + const u8 = new Uint8Array([1, 2, 3, 4]); + Object.defineProperty(u8, 'byteLength', { value: 100 }); + const result = ArrayBuffer.concat([u8]); + result.byteLength // → 4 (uses TypedArrayByteLength, not .byteLength) + ``` +- [ ] [25.3.2.2] Overridden `.byteOffset` on TypedArray passed to `ArrayBuffer.concat`: + ```js + const ab = new ArrayBuffer(8); + new Uint8Array(ab).set([1, 2, 3, 4, 5, 6, 7, 8]); + const u8 = new Uint8Array(ab, 4, 4); // views [5, 6, 7, 8] + Object.defineProperty(u8, 'byteOffset', { value: 0 }); + const result = ArrayBuffer.concat([u8]); + new Uint8Array(result) // → [5, 6, 7, 8] (uses [[ByteOffset]], not .byteOffset) + ``` +- [ ] [25.3.2.3] Overridden `.buffer` on TypedArray passed to `ArrayBuffer.concat`: + ```js + const u8 = new Uint8Array([1, 2, 3]); + const fakeBuffer = new ArrayBuffer(100); + new Uint8Array(fakeBuffer).fill(0xFF); + Object.defineProperty(u8, 'buffer', { value: fakeBuffer }); + const result = ArrayBuffer.concat([u8]); + new Uint8Array(result) // → [1, 2, 3] (uses [[ViewedArrayBuffer]], not .buffer) + ``` + +#### 25.3.3 Overridden properties on DataView items + +- [ ] [25.3.3.1] Overridden `.byteLength` on DataView: + ```js + const ab = new ArrayBuffer(4); + new Uint8Array(ab).set([1, 2, 3, 4]); + const dv = new DataView(ab); + Object.defineProperty(dv, 'byteLength', { value: 100 }); + const result = ArrayBuffer.concat([dv]); + result.byteLength // → 4 (uses GetViewByteLength, not .byteLength) + ``` +- [ ] [25.3.3.2] Overridden `.byteOffset` on DataView: + ```js + const ab = new ArrayBuffer(8); + new Uint8Array(ab).set([1, 2, 3, 4, 5, 6, 7, 8]); + const dv = new DataView(ab, 4, 4); // views [5, 6, 7, 8] + Object.defineProperty(dv, 'byteOffset', { value: 0 }); + const result = ArrayBuffer.concat([dv]); + new Uint8Array(result) // → [5, 6, 7, 8] (uses [[ByteOffset]], not .byteOffset) + ``` +- [ ] [25.3.3.3] Overridden `.buffer` on DataView: + ```js + const ab = new ArrayBuffer(4); + new Uint8Array(ab).set([1, 2, 3, 4]); + const dv = new DataView(ab); + Object.defineProperty(dv, 'buffer', { value: new ArrayBuffer(100) }); + const result = ArrayBuffer.concat([dv]); + new Uint8Array(result) // → [1, 2, 3, 4] (uses [[ViewedArrayBuffer]], not .buffer) + ``` + +### 25.4 `ArrayBuffer.concat` — Overridden options object accessors + +- [ ] [25.4.1] Options object with getter on `length` that has side effects: + ```js + let callCount = 0; + const opts = { get length() { callCount++; return 4; } }; + ArrayBuffer.concat([new ArrayBuffer(2)], opts); + callCount // → 1 (Get is called once for "length") + ``` +- [ ] [25.4.2] Options object with getter on `resizable` that throws: + ```js + const opts = { get resizable() { throw new Error('boom'); } }; + ArrayBuffer.concat([], opts) // → Error('boom') + ``` +- [ ] [25.4.3] Options object with getter on `immutable` that throws: + ```js + const opts = { resizable: false, get immutable() { throw new Error('boom'); } }; + ArrayBuffer.concat([], opts) // → Error('boom') + ``` +- [ ] [25.4.4] Options with `length` getter that modifies items (items already collected): + ```js + const ab = new ArrayBuffer(4); + new Uint8Array(ab).set([1, 2, 3, 4]); + const opts = { + get length() { + // Items were already collected via IteratorToList before options are read + ab.transfer(); // detach the buffer + return 8; + } + }; + // Items are iterated at step 1, options read at step 2+ + // But item validation happens at step 10+ (after options), so detachment + // will be caught during validation + ArrayBuffer.concat([ab], opts) // → TypeError (detached buffer) + ``` + +### 25.5 Prototype pollution + +- [ ] [25.5.1] Poisoned `TypedArray.prototype.length` does not affect result: + ```js + const origDesc = Object.getOwnPropertyDescriptor(Uint8Array.prototype.__proto__, 'length'); + try { + Object.defineProperty(Uint8Array.prototype.__proto__, 'length', { get() { return 9999; } }); + const result = Uint8Array.concat([new Uint8Array([1, 2, 3])]); + result.length // → 3 (internal slot, not prototype getter) + } finally { + Object.defineProperty(Uint8Array.prototype.__proto__, 'length', origDesc); + } + ``` +- [ ] [25.5.2] Poisoned `ArrayBuffer.prototype.byteLength` does not affect result: + ```js + const origDesc = Object.getOwnPropertyDescriptor(ArrayBuffer.prototype, 'byteLength'); + try { + Object.defineProperty(ArrayBuffer.prototype, 'byteLength', { get() { return 9999; } }); + const ab = new ArrayBuffer(4); + new Uint8Array(ab).set([1, 2, 3, 4]); + const result = ArrayBuffer.concat([ab]); + result.byteLength // → 4 + } finally { + Object.defineProperty(ArrayBuffer.prototype, 'byteLength', origDesc); + } + ``` +- [ ] [25.5.3] Poisoned `Object.prototype[Symbol.iterator]` does not affect items that are already iterable: + ```js + Object.prototype[Symbol.iterator] = function*() { yield new Uint8Array([99]); }; + try { + const result = Uint8Array.concat([new Uint8Array([1, 2])]); + // Array's own Symbol.iterator is used, not Object.prototype's + new Uint8Array(result) // → [1, 2] + } finally { + delete Object.prototype[Symbol.iterator]; + } + ``` + +### 25.6 Subclass shenanigans + +- [ ] [25.6.1] Calling `concat` on a subclass constructor: + ```js + class MyUint8 extends Uint8Array {} + // MyUint8 has [[TypedArrayName]] === "Uint8Array" (inherited) + // But MyUint8 is a valid constructor with the slot + // Behavior depends on whether subclass constructors carry [[TypedArrayName]] + // This is worth testing to understand the actual behavior + ``` +- [ ] [25.6.2] Item is a subclass instance: + ```js + class MyUint8 extends Uint8Array { constructor(...args) { super(...args); } } + const a = new MyUint8([1, 2, 3]); + // a has [[TypedArrayName]] === "Uint8Array" + const result = Uint8Array.concat([a]); + result.length // → 3 (ValidateTypedArray passes, [[TypedArrayName]] matches) + ``` + +--- + +## 26. Missing Arguments and Default Behavior + +### 26.1 `%TypedArray%.concat` called with no arguments + +- [ ] [26.1.1] `Uint8Array.concat()` → TypeError (`items` is `undefined`, which has no `Symbol.iterator`) + +### 26.2 `ArrayBuffer.concat` called with no arguments + +- [ ] [26.2.1] `ArrayBuffer.concat()` → TypeError (`items` is `undefined`) + +### 26.3 `SharedArrayBuffer.concat` called with no arguments + +- [ ] [26.3.1] `SharedArrayBuffer.concat()` → TypeError (`items` is `undefined`) + +### 26.4 Result buffer properties + +- [ ] [26.4.1] `%TypedArray%.concat` result buffer is not resizable: + ```js + const result = Uint8Array.concat([new Uint8Array([1, 2])]); + result.buffer.resizable // → false + ``` +- [ ] [26.4.2] `ArrayBuffer.concat` result is not resizable by default: + ```js + const result = ArrayBuffer.concat([new ArrayBuffer(4)]); + result.resizable // → false + ``` +- [ ] [26.4.3] `ArrayBuffer.concat` result is not immutable by default: + ```js + const result = ArrayBuffer.concat([new ArrayBuffer(4)]); + result.immutable // → false (or undefined, depending on immutable proposal) + ``` +- [ ] [26.4.4] `SharedArrayBuffer.concat` result is not growable by default: + ```js + const result = SharedArrayBuffer.concat([new SharedArrayBuffer(4)]); + result.growable // → false + ``` + +--- + +## 27. Cross-Type Buffer Inputs + +All three methods accept inputs backed by either ArrayBuffer or SharedArrayBuffer. `ArrayBuffer.concat` and `SharedArrayBuffer.concat` accept both ArrayBuffer and SharedArrayBuffer directly. The return type is always determined by which method is called, not by the input types. + +### 27.1 `%TypedArray%.concat` — Items backed by SharedArrayBuffer + +- [ ] [27.1.1] TypedArray over SharedArrayBuffer is a valid item (no TypeError): + ```js + const sab = new SharedArrayBuffer(4); + new Uint8Array(sab).set([1, 2, 3, 4]); + const u8 = new Uint8Array(sab); + const result = Uint8Array.concat([u8]); + result.length // → 4 + new Uint8Array(result) // → [1, 2, 3, 4] (subject to races if another agent writes concurrently) + ``` +- [ ] [27.1.2] Two TypedArrays over the same SharedArrayBuffer: + ```js + const sab = new SharedArrayBuffer(4); + new Uint8Array(sab).set([1, 2, 3, 4]); + const a = new Uint8Array(sab, 0, 2); + const b = new Uint8Array(sab, 2, 2); + const result = Uint8Array.concat([a, b]); + result.length // → 4 + ``` +- [ ] [27.1.3] Result buffer is a regular (non-shared) ArrayBuffer even when inputs are SharedArrayBuffer-backed: + ```js + const sab = new SharedArrayBuffer(4); + const u8 = new Uint8Array(sab); + const result = Uint8Array.concat([u8]); + result.buffer instanceof ArrayBuffer // → true + result.buffer instanceof SharedArrayBuffer // → false + ``` +- [ ] [27.1.4] Mix of ArrayBuffer-backed and SharedArrayBuffer-backed TypedArrays: + ```js + const ab = new ArrayBuffer(2); + new Uint8Array(ab).set([1, 2]); + const sab = new SharedArrayBuffer(2); + new Uint8Array(sab).set([3, 4]); + const result = Uint8Array.concat([new Uint8Array(ab), new Uint8Array(sab)]); + result.length // → 4 + new Uint8Array(result) // → [1, 2, 3, 4] + ``` + +### 27.2 `ArrayBuffer.concat` — SharedArrayBuffer and SAB-backed view inputs + +- [ ] [27.2.1] SharedArrayBuffer directly as item: + ```js + const sab = new SharedArrayBuffer(4); + new Uint8Array(sab).set([10, 20, 30, 40]); + const result = ArrayBuffer.concat([sab]); + result.byteLength // → 4 + result instanceof ArrayBuffer // → true + result instanceof SharedArrayBuffer // → false + new Uint8Array(result) // → [10, 20, 30, 40] + ``` +- [ ] [27.2.2] TypedArray over SharedArrayBuffer as item: + ```js + const sab = new SharedArrayBuffer(4); + new Uint8Array(sab).set([10, 20, 30, 40]); + const u8 = new Uint8Array(sab); + const result = ArrayBuffer.concat([u8]); + result.byteLength // → 4 + new Uint8Array(result) // → [10, 20, 30, 40] + ``` +- [ ] [27.2.3] DataView over SharedArrayBuffer as item: + ```js + const sab = new SharedArrayBuffer(4); + new Uint8Array(sab).set([10, 20, 30, 40]); + const dv = new DataView(sab); + const result = ArrayBuffer.concat([dv]); + result.byteLength // → 4 + new Uint8Array(result) // → [10, 20, 30, 40] + ``` +- [ ] [27.2.4] Mix of ArrayBuffer, SharedArrayBuffer, TypedArray, and DataView: + ```js + const ab = new ArrayBuffer(2); + new Uint8Array(ab).set([1, 2]); + const sab = new SharedArrayBuffer(2); + new Uint8Array(sab).set([3, 4]); + const u8 = new Uint8Array([5, 6]); + const result = ArrayBuffer.concat([ab, sab, u8]); + result.byteLength // → 6 + new Uint8Array(result) // → [1, 2, 3, 4, 5, 6] + ``` +- [ ] [27.2.5] Result is always an ArrayBuffer regardless of input types: + ```js + const sab = new SharedArrayBuffer(4); + const result = ArrayBuffer.concat([sab]); + result instanceof SharedArrayBuffer // → false + result instanceof ArrayBuffer // → true + ``` + +### 27.3 `SharedArrayBuffer.concat` — ArrayBuffer and AB-backed view inputs + +- [ ] [27.3.1] ArrayBuffer directly as item: + ```js + const ab = new ArrayBuffer(4); + new Uint8Array(ab).set([10, 20, 30, 40]); + const result = SharedArrayBuffer.concat([ab]); + result.byteLength // → 4 + result instanceof SharedArrayBuffer // → true + new Uint8Array(result) // → [10, 20, 30, 40] + ``` +- [ ] [27.3.2] TypedArray over ArrayBuffer as item: + ```js + const u8 = new Uint8Array([10, 20, 30, 40]); + const result = SharedArrayBuffer.concat([u8]); + result.byteLength // → 4 + result instanceof SharedArrayBuffer // → true + new Uint8Array(result) // → [10, 20, 30, 40] + ``` +- [ ] [27.3.3] DataView over ArrayBuffer as item: + ```js + const ab = new ArrayBuffer(4); + new Uint8Array(ab).set([10, 20, 30, 40]); + const dv = new DataView(ab); + const result = SharedArrayBuffer.concat([dv]); + result.byteLength // → 4 + result instanceof SharedArrayBuffer // → true + ``` +- [ ] [27.3.4] Mix of SharedArrayBuffer, ArrayBuffer, TypedArray, and DataView: + ```js + const sab = new SharedArrayBuffer(2); + new Uint8Array(sab).set([1, 2]); + const ab = new ArrayBuffer(2); + new Uint8Array(ab).set([3, 4]); + const u8 = new Uint8Array([5, 6]); + const result = SharedArrayBuffer.concat([sab, ab, u8]); + result.byteLength // → 6 + result instanceof SharedArrayBuffer // → true + new Uint8Array(result) // → [1, 2, 3, 4, 5, 6] + ``` +- [ ] [27.3.5] Result is always a SharedArrayBuffer regardless of input types: + ```js + const ab = new ArrayBuffer(4); + const result = SharedArrayBuffer.concat([ab]); + result instanceof SharedArrayBuffer // → true + ``` +- [ ] [27.3.6] Detached ArrayBuffer as direct item → TypeError: + ```js + const ab = new ArrayBuffer(4); + ab.transfer(); + SharedArrayBuffer.concat([ab]) // → TypeError + ``` + +### 27.4 Concurrent modification of SharedArrayBuffer during copy + +`CopyDataBlockBytes` operates on the raw underlying data blocks without snapshotting. When a source is backed by a SharedArrayBuffer, another agent (Worker) may modify the data concurrently during the copy. The result may contain a mix of old and new values. These tests verify that concurrent writes do not cause crashes or undefined behavior, and that the result is a valid buffer containing *some* permutation of the written bytes. + +#### 27.4.1 `%TypedArray%.concat` — Concurrent write from a Worker + +- [ ] [27.4.1.1] Worker writes to SharedArrayBuffer while main thread calls `Uint8Array.concat`: + ```js + // Setup: SharedArrayBuffer with known initial values + const sab = new SharedArrayBuffer(1024); + const u8 = new Uint8Array(sab); + u8.fill(0xAA); + + // Worker that continuously writes 0xBB to the buffer + const workerCode = ` + onmessage = function(e) { + const u8 = new Uint8Array(e.data); + for (let i = 0; i < 1000000; i++) { + for (let j = 0; j < u8.length; j++) u8[j] = 0xBB; + } + postMessage('done'); + }; + `; + const blob = new Blob([workerCode], { type: 'application/javascript' }); + const worker = new Worker(URL.createObjectURL(blob)); + worker.postMessage(sab); + + // Main thread: concat while worker is writing + const result = Uint8Array.concat([u8]); + + // Assertions: + result.length // → 1024 + result.buffer instanceof ArrayBuffer // → true (not SharedArrayBuffer) + result.buffer.resizable // → false + // Each byte in result is either 0xAA or 0xBB (no torn or invalid values for byte-sized elements) + for (const byte of result) { + assert(byte === 0xAA || byte === 0xBB); + } + worker.terminate(); + ``` + +- [ ] [27.4.1.2] Result is a snapshot (modifications to SAB after concat don't affect result): + ```js + const sab = new SharedArrayBuffer(4); + new Uint8Array(sab).set([1, 2, 3, 4]); + const u8 = new Uint8Array(sab); + const result = Uint8Array.concat([u8]); + // Modify SAB after concat + new Uint8Array(sab).fill(0xFF); + new Uint8Array(result) // → [1, 2, 3, 4] (result is independent of SAB) + ``` + +#### 27.4.2 `ArrayBuffer.concat` — Concurrent write from a Worker + +- [ ] [27.4.2.1] Worker writes to SharedArrayBuffer while main thread calls `ArrayBuffer.concat` with a SAB input: + ```js + const sab = new SharedArrayBuffer(1024); + const u8 = new Uint8Array(sab); + u8.fill(0xAA); + + const workerCode = ` + onmessage = function(e) { + const u8 = new Uint8Array(e.data); + for (let i = 0; i < 1000000; i++) { + for (let j = 0; j < u8.length; j++) u8[j] = 0xBB; + } + postMessage('done'); + }; + `; + const blob = new Blob([workerCode], { type: 'application/javascript' }); + const worker = new Worker(URL.createObjectURL(blob)); + worker.postMessage(sab); + + const result = ArrayBuffer.concat([sab]); + + result.byteLength // → 1024 + result instanceof ArrayBuffer // → true + result instanceof SharedArrayBuffer // → false + // Each byte is either 0xAA or 0xBB + const resultView = new Uint8Array(result); + for (const byte of resultView) { + assert(byte === 0xAA || byte === 0xBB); + } + worker.terminate(); + ``` + +- [ ] [27.4.2.2] Worker writes to SharedArrayBuffer while main thread calls `ArrayBuffer.concat` with a DataView over SAB: + ```js + const sab = new SharedArrayBuffer(1024); + new Uint8Array(sab).fill(0xAA); + const dv = new DataView(sab); + + // (Same worker pattern as 27.4.2.1) + // ... + const result = ArrayBuffer.concat([dv]); + result.byteLength // → 1024 + // Each byte is either 0xAA or 0xBB + ``` + +#### 27.4.3 `SharedArrayBuffer.concat` — Concurrent write from a Worker + +- [ ] [27.4.3.1] Worker writes to source SharedArrayBuffer while main thread calls `SharedArrayBuffer.concat`: + ```js + const sab = new SharedArrayBuffer(1024); + new Uint8Array(sab).fill(0xAA); + + const workerCode = ` + onmessage = function(e) { + const u8 = new Uint8Array(e.data); + for (let i = 0; i < 1000000; i++) { + for (let j = 0; j < u8.length; j++) u8[j] = 0xBB; + } + postMessage('done'); + }; + `; + const blob = new Blob([workerCode], { type: 'application/javascript' }); + const worker = new Worker(URL.createObjectURL(blob)); + worker.postMessage(sab); + + const result = SharedArrayBuffer.concat([sab]); + + result.byteLength // → 1024 + result instanceof SharedArrayBuffer // → true + result !== sab // → true (new buffer) + // Each byte is either 0xAA or 0xBB + const resultView = new Uint8Array(result); + for (const byte of resultView) { + assert(byte === 0xAA || byte === 0xBB); + } + worker.terminate(); + ``` + +#### 27.4.4 Multi-byte element tearing + +- [ ] [27.4.4.1] Concurrent write to a SharedArrayBuffer-backed `Int32Array` may produce torn reads for multi-byte elements: + ```js + // CopyDataBlockBytes copies raw bytes. If another agent writes a 4-byte value + // non-atomically (or even atomically) while we copy byte-by-byte, the result + // may contain partial writes (torn values). + const sab = new SharedArrayBuffer(4); + const i32 = new Int32Array(sab); + Atomics.store(i32, 0, 0x11223344); + + // Worker that repeatedly writes 0xAABBCCDD + const workerCode = ` + onmessage = function(e) { + const i32 = new Int32Array(e.data); + for (let i = 0; i < 1000000; i++) { + Atomics.store(i32, 0, 0xAABBCCDD); + Atomics.store(i32, 0, 0x11223344); + } + postMessage('done'); + }; + `; + const blob = new Blob([workerCode], { type: 'application/javascript' }); + const worker = new Worker(URL.createObjectURL(blob)); + worker.postMessage(sab); + + const result = Int32Array.concat([i32]); + result.length // → 1 + // result[0] might be 0x11223344, 0xAABBCCDD, or a torn combination + // The key assertion: no crash, result is a valid Int32Array of length 1 + worker.terminate(); + ``` + +Note: Multi-byte tearing behavior is implementation-defined. The tests above assert structural validity (correct length, correct buffer type) rather than specific data values, since the exact bytes observed during a race are nondeterministic. + +### 27.5 Concurrent grow of growable SharedArrayBuffer during concat + +When a source is a growable SharedArrayBuffer (or a view over one), another agent may call `grow()` concurrently. The byte length is snapshotted during the validation loop with `~seq-cst~` ordering. The copy loop uses the snapshotted length from the `_sources_` record, not a fresh read. The underlying Shared Data Block is always allocated at `maxByteLength`, so `CopyDataBlockBytes` always reads from valid memory regardless of concurrent grows. + +#### 27.5.1 Direct growable SAB input — concurrent grow during concat + +- [ ] [27.5.1.1] Another thread grows the SAB between validation and copy — result uses snapshotted length: + ```js + const gsab = new SharedArrayBuffer(4, { maxByteLength: 1024 }); + new Uint8Array(gsab).set([1, 2, 3, 4]); + + // Worker that grows the buffer + const workerCode = ` + onmessage = function(e) { + const gsab = e.data; + // Spin briefly then grow + for (let i = 0; i < 100; i++) {} + gsab.grow(512); + postMessage('done'); + }; + `; + const blob = new Blob([workerCode], { type: 'application/javascript' }); + const worker = new Worker(URL.createObjectURL(blob)); + worker.postMessage(gsab); + + const result = ArrayBuffer.concat([gsab]); + + // result.byteLength is either 4 (snapshotted before grow) or 512 (snapshotted after grow) + // but is always one consistent value — never partially grown + assert(result.byteLength === 4 || result.byteLength === 512); + // The first 4 bytes are [1, 2, 3, 4] regardless + const view = new Uint8Array(result); + assert(view[0] === 1 && view[1] === 2 && view[2] === 3 && view[3] === 4); + worker.terminate(); + ``` + +- [ ] [27.5.1.2] Same test with `SharedArrayBuffer.concat`: + ```js + const gsab = new SharedArrayBuffer(4, { maxByteLength: 1024 }); + new Uint8Array(gsab).set([1, 2, 3, 4]); + + // (Same worker pattern — grows gsab to 512) + // ... + const result = SharedArrayBuffer.concat([gsab]); + + assert(result.byteLength === 4 || result.byteLength === 512); + result instanceof SharedArrayBuffer // → true + result !== gsab // → true + worker.terminate(); + ``` + +#### 27.5.2 Auto-length TypedArray over growable SAB — concurrent grow + +- [ ] [27.5.2.1] Auto-length TypedArray snapshotted via `ValidateTypedArray` with `~seq-cst~`; concurrent grow may or may not be visible: + ```js + const gsab = new SharedArrayBuffer(4, { maxByteLength: 1024 }); + new Uint8Array(gsab).set([1, 2, 3, 4]); + const u8 = new Uint8Array(gsab); // auto-length, tracks byteLength + + // Worker grows the buffer + // ... + const result = Uint8Array.concat([u8]); + + // result.length is snapshotted — either 4 (before grow) or grown size (after grow) + // No crash, result is a valid Uint8Array + assert(result.length >= 4); + assert(result[0] === 1 && result[1] === 2 && result[2] === 3 && result[3] === 4); + ``` + +#### 27.5.3 Multiple growable SABs — each snapshotted independently + +- [ ] [27.5.3.1] Two growable SABs, one grown concurrently — each length is snapshotted independently at validation time: + ```js + const gsab1 = new SharedArrayBuffer(4, { maxByteLength: 1024 }); + const gsab2 = new SharedArrayBuffer(4, { maxByteLength: 1024 }); + new Uint8Array(gsab1).set([1, 2, 3, 4]); + new Uint8Array(gsab2).set([5, 6, 7, 8]); + + // Worker grows gsab1 to 100 bytes + // ... + + const result = ArrayBuffer.concat([gsab1, gsab2]); + + // gsab1's snapshotted length is either 4 or 100 (depending on race) + // gsab2's snapshotted length is always 4 (not grown) + // Total is either 8 or 104 + assert(result.byteLength === 8 || result.byteLength === 104); + ``` + +#### 27.5.4 Grow does not affect already-snapshotted length + +- [ ] [27.5.4.1] If grow happens after validation but before copy, the snapshotted length is used — no extra bytes are copied: + ```js + // This is a logical assertion about the algorithm, not easily testable with + // precise timing, but structural tests confirm that: + // - The copy loop uses _source_.[[ByteLength]] from the validation snapshot + // - CopyDataBlockBytes reads exactly that many bytes + // - The underlying Shared Data Block memory is valid at any offset < maxByteLength + // - Therefore the copy is always safe, even if grow() is called concurrently + ``` + +Note: The exact length snapshotted depends on thread scheduling and is nondeterministic. Tests assert structural validity (no crash, valid buffer type, consistent byte values within snapshotted range) rather than specific timing outcomes. + +--- + +## 28. DataView with Detached Buffer + +- [ ] [28.1.1] DataView whose underlying buffer has been detached in `ArrayBuffer.concat` → TypeError: + ```js + const ab = new ArrayBuffer(4); + const dv = new DataView(ab); + ab.transfer(); + ArrayBuffer.concat([dv]) // → TypeError (IsViewOutOfBounds returns true for detached buffers) + ``` +- [ ] [28.1.2] DataView whose underlying buffer has been detached in `SharedArrayBuffer.concat` → TypeError: + ```js + const ab = new ArrayBuffer(4); + const dv = new DataView(ab); + ab.transfer(); + SharedArrayBuffer.concat([dv]) // → TypeError + ``` + +--- + +## 29. Immutable ArrayBuffer as Input + +Note: Depends on the [Immutable ArrayBuffer proposal](https://github.com/tc39/proposal-immutable-arraybuffer). + +- [ ] [29.1.1] Immutable ArrayBuffer as direct item in `ArrayBuffer.concat`: + ```js + const ab = new ArrayBuffer(4); + new Uint8Array(ab).set([1, 2, 3, 4]); + const immutableAb = ab.transferToImmutable(); + const result = ArrayBuffer.concat([immutableAb]); + result.byteLength // → 4 + new Uint8Array(result) // → [1, 2, 3, 4] + // immutableAb is not detached, not shared → accepted + ``` +- [ ] [29.1.2] Immutable ArrayBuffer as input, mutable result by default: + ```js + const immutableAb = new ArrayBuffer(4).transferToImmutable(); + const result = ArrayBuffer.concat([immutableAb]); + result.immutable // → false (default, result is mutable) + ``` +- [ ] [29.1.3] TypedArray view over immutable ArrayBuffer as item in `%TypedArray%.concat`: + ```js + // Note: TypedArrays over immutable buffers are read-only but valid + // ValidateTypedArray should pass (buffer is not detached) + ``` +- [ ] [29.1.4] Immutable ArrayBuffer as direct item in `SharedArrayBuffer.concat`: + ```js + const ab = new ArrayBuffer(4); + new Uint8Array(ab).set([1, 2, 3, 4]); + const immutableAb = ab.transferToImmutable(); + const result = SharedArrayBuffer.concat([immutableAb]); + result.byteLength // → 4 + result instanceof SharedArrayBuffer // → true + new Uint8Array(result) // → [1, 2, 3, 4] + ``` + +--- + +## 30. Options Property Access Order + +### 30.1 `ArrayBuffer.concat` options access order + +The spec reads options properties in order: `length` (step 3), `resizable` (step 6), `immutable` (step 7). Observable via getters. + +- [ ] [30.1.1] Options property access order is `length`, then `resizable`, then `immutable`: + ```js + const order = []; + const opts = { + get length() { order.push('length'); return undefined; }, + get resizable() { order.push('resizable'); return false; }, + get immutable() { order.push('immutable'); return false; }, + }; + ArrayBuffer.concat([], opts); + order // → ['length', 'resizable', 'immutable'] + ``` +- [ ] [30.1.2] If `length` getter throws, `resizable` and `immutable` are never accessed: + ```js + const order = []; + const opts = { + get length() { throw new Error('length'); }, + get resizable() { order.push('resizable'); return false; }, + get immutable() { order.push('immutable'); return false; }, + }; + try { ArrayBuffer.concat([], opts); } catch(e) {} + order // → [] (resizable and immutable never read) + ``` +- [ ] [30.1.3] If `resizable` getter throws, `immutable` is never accessed: + ```js + const order = []; + const opts = { + get length() { order.push('length'); return undefined; }, + get resizable() { throw new Error('resizable'); }, + get immutable() { order.push('immutable'); return false; }, + }; + try { ArrayBuffer.concat([], opts); } catch(e) {} + order // → ['length'] (immutable never read) + ``` + +### 30.2 `SharedArrayBuffer.concat` options access order + +The spec reads options properties in order: `length` (step 3), `growable` (step 6). Observable via getters. + +- [ ] [30.2.1] Options property access order is `length`, then `growable`: + ```js + const order = []; + const opts = { + get length() { order.push('length'); return undefined; }, + get growable() { order.push('growable'); return false; }, + }; + SharedArrayBuffer.concat([], opts); + order // → ['length', 'growable'] + ``` +- [ ] [30.2.2] If `length` getter throws, `growable` is never accessed: + ```js + const order = []; + const opts = { + get length() { throw new Error('length'); }, + get growable() { order.push('growable'); return false; }, + }; + try { SharedArrayBuffer.concat([], opts); } catch(e) {} + order // → [] (growable never read) + ``` + +--- + +## 31. Boundary Values for Length / Overflow Checks + +### 31.1 `Number.MAX_SAFE_INTEGER` as length (exactly 2^53 - 1) + +- [ ] [31.1.1] `Uint8Array.concat([], Number.MAX_SAFE_INTEGER)` → accepted (does not throw RangeError from overflow check); will likely throw from `AllocateTypedArrayBuffer` due to implementation memory limits +- [ ] [31.1.2] `ArrayBuffer.concat([], { length: Number.MAX_SAFE_INTEGER })` → accepted (does not throw from overflow check); will likely throw from `AllocateArrayBuffer` +- [ ] [31.1.3] `SharedArrayBuffer.concat([], { length: Number.MAX_SAFE_INTEGER })` → accepted (does not throw from overflow check); will likely throw from `AllocateSharedArrayBuffer` + +### 31.2 Overflow check with explicit length provided + +- [ ] [31.2.1] Explicit `length` is small, but `totalLength` overflows → RangeError from overflow check even though `newLength` is small: + ```js + // If we could create TypedArrays whose combined lengths exceed 2^53 - 1, + // the overflow check on totalLength would fire even with length: 5 + // This is because totalLength is computed unconditionally before newLength is used + ``` + +### 31.3 `length` of exactly 0 with items + +- [ ] [31.3.1] `Uint8Array.concat([new Uint8Array([1, 2, 3])], 0)` → empty TypedArray, items still validated: + ```js + // Even though the result is empty, all items are validated (ValidateTypedArray, type check) + Uint8Array.concat([new Int16Array([1])], 0) // → TypeError (type mismatch, not silently skipped) + ``` +- [ ] [31.3.2] `ArrayBuffer.concat([new SharedArrayBuffer(4)], { length: 0 })` → empty ArrayBuffer (SharedArrayBuffer accepted, result truncated to 0 bytes) + +--- + +## 32. `SharedArrayBuffer.concat` — Items Validation + +### 32.1 Items is not iterable + +- [ ] [32.1.1] `SharedArrayBuffer.concat(undefined)` → TypeError +- [ ] [32.1.2] `SharedArrayBuffer.concat(null)` → TypeError +- [ ] [32.1.3] `SharedArrayBuffer.concat(42)` → TypeError +- [ ] [32.1.4] `SharedArrayBuffer.concat({})` → TypeError + +### 32.2 Items iterable throws during iteration + +- [ ] [32.2.1] Items iterable whose `Symbol.iterator` method throws → error propagates +- [ ] [32.2.2] Items iterable whose iterator's `next()` throws → error propagates + +### 32.3 Item is not an ArrayBuffer, SharedArrayBuffer, TypedArray, or DataView + +- [ ] [32.3.1] `SharedArrayBuffer.concat([42])` → TypeError +- [ ] [32.3.2] `SharedArrayBuffer.concat([{}])` → TypeError +- [ ] [32.3.3] `SharedArrayBuffer.concat(['string'])` → TypeError +- [ ] [32.3.4] `SharedArrayBuffer.concat([null])` → TypeError +- [ ] [32.3.5] `SharedArrayBuffer.concat([undefined])` → TypeError +- [ ] [32.3.6] `SharedArrayBuffer.concat([[1, 2, 3]])` → TypeError (plain Array) + +### 32.4 Item is a detached ArrayBuffer + +- [ ] [32.4.1] Detached ArrayBuffer → TypeError: + ```js + const ab = new ArrayBuffer(4); + ab.transfer(); + SharedArrayBuffer.concat([ab]) // → TypeError + ``` + +### 32.5 Item is a TypedArray with a detached buffer + +- [ ] [32.5.1] TypedArray whose buffer has been detached → TypeError from `ValidateTypedArray` + +### 32.6 Item is a DataView that is out of bounds + +- [ ] [32.6.1] DataView over a resizable ArrayBuffer that has been shrunk below the view's range → TypeError from `IsViewOutOfBounds` + +### 32.7 Mixed valid item types + +- [ ] [32.7.1] `SharedArrayBuffer.concat([new SharedArrayBuffer(2), new Uint8Array([1, 2]), new DataView(new ArrayBuffer(3))])` → SharedArrayBuffer of byteLength 7 +- [ ] [32.7.2] `SharedArrayBuffer.concat([new ArrayBuffer(2), new SharedArrayBuffer(2)])` → SharedArrayBuffer of byteLength 4 +- [ ] [32.7.3] First item valid, second item invalid → TypeError on second item + +--- + +## 33. `SharedArrayBuffer.concat` — Options Validation + +### 33.1 Options is `undefined` or not provided + +- [ ] [33.1.1] `SharedArrayBuffer.concat([new SharedArrayBuffer(4)])` → works, no options +- [ ] [33.1.2] `SharedArrayBuffer.concat([new SharedArrayBuffer(4)], undefined)` → works, same as no options + +### 33.2 Options is not an object + +- [ ] [33.2.1] `SharedArrayBuffer.concat([], 42)` → TypeError from `GetOptionsObject` +- [ ] [33.2.2] `SharedArrayBuffer.concat([], 'string')` → TypeError +- [ ] [33.2.3] `SharedArrayBuffer.concat([], true)` → TypeError + +### 33.3 Length option validation + +#### 33.3.1 Non-Number length → TypeError + +- [ ] [33.3.1.1] `SharedArrayBuffer.concat([], { length: 'hello' })` → TypeError +- [ ] [33.3.1.2] `SharedArrayBuffer.concat([], { length: {} })` → TypeError +- [ ] [33.3.1.3] `SharedArrayBuffer.concat([], { length: true })` → TypeError +- [ ] [33.3.1.4] `SharedArrayBuffer.concat([], { length: Symbol() })` → TypeError +- [ ] [33.3.1.5] `SharedArrayBuffer.concat([], { length: 1n })` → TypeError + +#### 33.3.2 NaN / non-integral / Infinity → RangeError + +- [ ] [33.3.2.1] `SharedArrayBuffer.concat([], { length: NaN })` → RangeError +- [ ] [33.3.2.2] `SharedArrayBuffer.concat([], { length: 1.5 })` → RangeError +- [ ] [33.3.2.3] `SharedArrayBuffer.concat([], { length: Infinity })` → RangeError +- [ ] [33.3.2.4] `SharedArrayBuffer.concat([], { length: -Infinity })` → RangeError + +#### 33.3.3 Negative length → RangeError + +- [ ] [33.3.3.1] `SharedArrayBuffer.concat([], { length: -1 })` → RangeError +- [ ] [33.3.3.2] `SharedArrayBuffer.concat([], { length: -100 })` → RangeError + +#### 33.3.4 Length exceeds 2^53 - 1 → RangeError + +- [ ] [33.3.4.1] `SharedArrayBuffer.concat([], { length: 2 ** 53 })` → RangeError +- [ ] [33.3.4.2] `SharedArrayBuffer.concat([], { length: Number.MAX_SAFE_INTEGER + 1 })` → RangeError + +#### 33.3.5 Length is -0 (treated as 0) + +- [ ] [33.3.5.1] `SharedArrayBuffer.concat([new SharedArrayBuffer(4)], { length: -0 })` → SharedArrayBuffer of byteLength 0 + +#### 33.3.6 Length is `undefined` (same as not provided) + +- [ ] [33.3.6.1] `SharedArrayBuffer.concat([new SharedArrayBuffer(4)], { length: undefined })` → byteLength 4 (defaults to total) + +### 33.4 Growable option + +- [ ] [33.4.1] `SharedArrayBuffer.concat([], { growable: true })` → result is growable +- [ ] [33.4.2] `SharedArrayBuffer.concat([], { growable: false })` → result is not growable (default behavior) + +### 33.5 Growable is coerced via `ToBoolean` + +- [ ] [33.5.1] `SharedArrayBuffer.concat([], { growable: 1 })` → result is growable (truthy) +- [ ] [33.5.2] `SharedArrayBuffer.concat([], { growable: 0 })` → result is not growable (falsy) +- [ ] [33.5.3] `SharedArrayBuffer.concat([], { growable: '' })` → result is not growable (falsy) +- [ ] [33.5.4] `SharedArrayBuffer.concat([], { growable: 'yes' })` → result is growable (truthy) +- [ ] [33.5.5] `SharedArrayBuffer.concat([], { growable: null })` → result is not growable (falsy) +- [ ] [33.5.6] `SharedArrayBuffer.concat([], { growable: undefined })` → result is not growable (falsy) + +### 33.6 No `immutable` option + +- [ ] [33.6.1] `SharedArrayBuffer.concat([], { immutable: true })` → `immutable` option is ignored (SharedArrayBuffer has no immutable concept); result is a normal SharedArrayBuffer + +--- + +## 34. `SharedArrayBuffer.concat` — Overflow Check on `totalByteLength` + +### 34.1 Total byte length exceeds 2^53 - 1 + +- [ ] [34.1.1] Concatenating items whose combined byte lengths exceed 2^53 - 1 → RangeError +- [ ] [34.1.2] Overflow check is per-item (fails as soon as running total exceeds limit) +- [ ] [34.1.3] Overflow check applies regardless of which item type triggers it (SharedArrayBuffer, ArrayBuffer, TypedArray, or DataView) + +Note: As with §4 and §10, creating buffers large enough to trigger this may be implementation-limited. + +--- + +## 35. `SharedArrayBuffer.concat` — Basic Concatenation + +### 35.1 SharedArrayBuffer inputs + +- [ ] [35.1.1] Two SharedArrayBuffers: + ```js + const sab1 = new SharedArrayBuffer(4); + new Uint8Array(sab1).set([1, 2, 3, 4]); + const sab2 = new SharedArrayBuffer(4); + new Uint8Array(sab2).set([5, 6, 7, 8]); + const result = SharedArrayBuffer.concat([sab1, sab2]); + result.byteLength // → 8 + result instanceof SharedArrayBuffer // → true + new Uint8Array(result) // → [1, 2, 3, 4, 5, 6, 7, 8] + ``` +- [ ] [35.1.2] Single SharedArrayBuffer → copy (new buffer, not same object) +- [ ] [35.1.3] Three or more SharedArrayBuffers → concatenated in order + +### 35.2 ArrayBuffer inputs + +- [ ] [35.2.1] ArrayBuffer as input, result is SharedArrayBuffer: + ```js + const ab = new ArrayBuffer(4); + new Uint8Array(ab).set([1, 2, 3, 4]); + const result = SharedArrayBuffer.concat([ab]); + result.byteLength // → 4 + result instanceof SharedArrayBuffer // → true + new Uint8Array(result) // → [1, 2, 3, 4] + ``` + +### 35.3 TypedArray inputs (viewed portion only) + +- [ ] [35.3.1] Full-buffer TypedArray: + ```js + const u8 = new Uint8Array([1, 2, 3, 4]); + const result = SharedArrayBuffer.concat([u8]); + result.byteLength // → 4 + result instanceof SharedArrayBuffer // → true + ``` +- [ ] [35.3.2] TypedArray with byte offset: + ```js + const ab = new ArrayBuffer(8); + new Uint8Array(ab).set([1, 2, 3, 4, 5, 6, 7, 8]); + const view = new Uint8Array(ab, 2, 3); // views bytes [3, 4, 5] + const result = SharedArrayBuffer.concat([view]); + result.byteLength // → 3 + new Uint8Array(result) // → [3, 4, 5] + ``` +- [ ] [35.3.3] Multi-byte element TypedArray: + ```js + const i32 = new Int32Array([1, 2]); // 8 bytes + const result = SharedArrayBuffer.concat([i32]); + result.byteLength // → 8 + ``` + +### 35.4 DataView inputs (viewed portion only) + +- [ ] [35.4.1] Full-buffer DataView: + ```js + const ab = new ArrayBuffer(4); + new Uint8Array(ab).set([1, 2, 3, 4]); + const dv = new DataView(ab); + const result = SharedArrayBuffer.concat([dv]); + result.byteLength // → 4 + result instanceof SharedArrayBuffer // → true + ``` +- [ ] [35.4.2] DataView with byte offset and length: + ```js + const ab = new ArrayBuffer(8); + new Uint8Array(ab).set([1, 2, 3, 4, 5, 6, 7, 8]); + const dv = new DataView(ab, 2, 3); + const result = SharedArrayBuffer.concat([dv]); + result.byteLength // → 3 + new Uint8Array(result) // → [3, 4, 5] + ``` + +### 35.5 Mixed input types + +- [ ] [35.5.1] SharedArrayBuffer + ArrayBuffer + TypedArray + DataView: + ```js + const sab = new SharedArrayBuffer(2); + new Uint8Array(sab).set([1, 2]); + const ab = new ArrayBuffer(2); + new Uint8Array(ab).set([3, 4]); + const u8 = new Uint8Array([5, 6]); + const dv = new DataView(new ArrayBuffer(2)); + new Uint8Array(dv.buffer).set([7, 8]); + const result = SharedArrayBuffer.concat([sab, ab, u8, dv]); + result.byteLength // → 8 + result instanceof SharedArrayBuffer // → true + new Uint8Array(result) // → [1, 2, 3, 4, 5, 6, 7, 8] + ``` + +### 35.6 Empty inputs + +- [ ] [35.6.1] `SharedArrayBuffer.concat([])` → SharedArrayBuffer of byteLength 0 +- [ ] [35.6.2] `SharedArrayBuffer.concat([new SharedArrayBuffer(0)])` → SharedArrayBuffer of byteLength 0 +- [ ] [35.6.3] `SharedArrayBuffer.concat([new SharedArrayBuffer(0), new SharedArrayBuffer(0)])` → SharedArrayBuffer of byteLength 0 + +### 35.7 Result is always a new SharedArrayBuffer + +- [ ] [35.7.1] Result is not the same object as any input: + ```js + const sab = new SharedArrayBuffer(4); + const result = SharedArrayBuffer.concat([sab]); + result !== sab // → true + ``` + +--- + +## 36. `SharedArrayBuffer.concat` — Truncation and Zero-Fill + +### 36.1 Truncation (length < totalByteLength) + +- [ ] [36.1.1] `SharedArrayBuffer.concat([new SharedArrayBuffer(8)], { length: 4 })` → byteLength 4 +- [ ] [36.1.2] Truncation mid-second-item: + ```js + const sab1 = new SharedArrayBuffer(4); + new Uint8Array(sab1).set([1, 2, 3, 4]); + const sab2 = new SharedArrayBuffer(4); + new Uint8Array(sab2).set([5, 6, 7, 8]); + const result = SharedArrayBuffer.concat([sab1, sab2], { length: 6 }); + new Uint8Array(result) // → [1, 2, 3, 4, 5, 6] + ``` +- [ ] [36.1.3] Truncation to 0: `SharedArrayBuffer.concat([new SharedArrayBuffer(4)], { length: 0 })` → byteLength 0 + +### 36.2 Zero-fill (length > totalByteLength) + +- [ ] [36.2.1] `SharedArrayBuffer.concat([new SharedArrayBuffer(4)], { length: 8 })` → byteLength 8, last 4 bytes are 0 +- [ ] [36.2.2] `SharedArrayBuffer.concat([], { length: 4 })` → byteLength 4, all bytes 0 +- [ ] [36.2.3] Verify zero-fill bytes are actually 0: + ```js + const sab = new SharedArrayBuffer(2); + new Uint8Array(sab).set([0xFF, 0xFF]); + const result = SharedArrayBuffer.concat([sab], { length: 4 }); + const u8 = new Uint8Array(result); + // u8[0] === 0xFF, u8[1] === 0xFF, u8[2] === 0, u8[3] === 0 + ``` + +### 36.3 Exact length + +- [ ] [36.3.1] `SharedArrayBuffer.concat([new SharedArrayBuffer(4)], { length: 4 })` → byteLength 4 (same as no length) + +--- + +## 37. `SharedArrayBuffer.concat` — Growable Option + +### 37.1 Basic growable result + +- [ ] [37.1.1] Growable with explicit length: + ```js + const result = SharedArrayBuffer.concat([new SharedArrayBuffer(4)], { growable: true, length: 16 }); + result.growable // → true + result.byteLength // → 4 (actual data) + result.maxByteLength // → 16 + ``` +- [ ] [37.1.2] Growable result can be grown: + ```js + const result = SharedArrayBuffer.concat([new SharedArrayBuffer(4)], { growable: true, length: 16 }); + result.grow(8); + result.byteLength // → 8 + ``` + +### 37.2 Growable without explicit length (maxByteLength = totalByteLength) + +- [ ] [37.2.1] `SharedArrayBuffer.concat([new SharedArrayBuffer(4)], { growable: true })` → `byteLength === 4`, `maxByteLength === 4` (buffer already at max) + +### 37.3 Growable with length less than totalByteLength + +- [ ] [37.3.1] `maxByteLength` is `length`, `byteLength` is clamped: + ```js + const sab1 = new SharedArrayBuffer(4); + const sab2 = new SharedArrayBuffer(4); + const result = SharedArrayBuffer.concat([sab1, sab2], { growable: true, length: 6 }); + result.byteLength // → 6 (clamped: min(8, 6)) + result.maxByteLength // → 6 + ``` + +### 37.4 Growable with length greater than totalByteLength + +- [ ] [37.4.1] `byteLength` equals total data, `maxByteLength` equals `length`: + ```js + const result = SharedArrayBuffer.concat([new SharedArrayBuffer(4)], { growable: true, length: 32 }); + result.byteLength // → 4 (actual data) + result.maxByteLength // → 32 (room to grow) + ``` + +### 37.5 Data integrity in growable result + +- [ ] [37.5.1] Source data is correctly copied into growable result: + ```js + const sab = new SharedArrayBuffer(4); + new Uint8Array(sab).set([1, 2, 3, 4]); + const result = SharedArrayBuffer.concat([sab], { growable: true, length: 16 }); + new Uint8Array(result, 0, 4) // → [1, 2, 3, 4] + ``` + +--- + +## 38. `SharedArrayBuffer.concat` — Growable SharedArrayBuffer Inputs + +### 38.1 Only current byteLength is copied (not maxByteLength) + +- [ ] [38.1.1] Growable SharedArrayBuffer passed directly — copies current byteLength, ignores maxByteLength: + ```js + const gsab = new SharedArrayBuffer(4, { maxByteLength: 64 }); + new Uint8Array(gsab).set([1, 2, 3, 4]); + const result = SharedArrayBuffer.concat([gsab]); + result.byteLength // → 4 (not 64) + new Uint8Array(result) // → [1, 2, 3, 4] + ``` +- [ ] [38.1.2] Two growable SharedArrayBuffers — total is sum of current byteLengths: + ```js + const gsab1 = new SharedArrayBuffer(3, { maxByteLength: 100 }); + const gsab2 = new SharedArrayBuffer(2, { maxByteLength: 200 }); + new Uint8Array(gsab1).set([1, 2, 3]); + new Uint8Array(gsab2).set([4, 5]); + const result = SharedArrayBuffer.concat([gsab1, gsab2]); + result.byteLength // → 5 (not 300) + new Uint8Array(result) // → [1, 2, 3, 4, 5] + ``` + +### 38.2 Growable SharedArrayBuffer after grow + +- [ ] [38.2.1] After grow — copies the grown size (including zero-initialized region): + ```js + const gsab = new SharedArrayBuffer(4, { maxByteLength: 32 }); + new Uint8Array(gsab).set([1, 2, 3, 4]); + gsab.grow(8); + const result = SharedArrayBuffer.concat([gsab]); + result.byteLength // → 8 + new Uint8Array(result) // → [1, 2, 3, 4, 0, 0, 0, 0] + ``` + +### 38.3 TypedArray/DataView over growable SharedArrayBuffer + +- [ ] [38.3.1] Auto-length TypedArray over growable SharedArrayBuffer: + ```js + const gsab = new SharedArrayBuffer(4, { maxByteLength: 64 }); + new Uint8Array(gsab).set([10, 20, 30, 40]); + const u8 = new Uint8Array(gsab); // auto-length + const result = SharedArrayBuffer.concat([u8]); + result.byteLength // → 4 (not 64) + ``` +- [ ] [38.3.2] Fixed-length TypedArray over growable SharedArrayBuffer: + ```js + const gsab = new SharedArrayBuffer(16, { maxByteLength: 64 }); + const u8 = new Uint8Array(gsab, 0, 4); // fixed-length: 4 elements + new Uint8Array(gsab).set([1, 2, 3, 4]); + const result = SharedArrayBuffer.concat([u8]); + result.byteLength // → 4 + ``` + +--- + +## 39. `SharedArrayBuffer.concat` — Copy Loop Edge Cases + +### 39.1 Short-circuit when result is full + +- [ ] [39.1.1] With `length: 0`, no bytes are copied: + ```js + const result = SharedArrayBuffer.concat([new SharedArrayBuffer(4)], { length: 0 }); + result.byteLength // → 0 + ``` +- [ ] [39.1.2] With `length: 2` and items totalling 8 bytes, only the first 2 bytes are in the result + +### 39.2 Copy spans multiple items + +- [ ] [39.2.1] Three items, truncation in the middle of the second: + ```js + const sab1 = new SharedArrayBuffer(3); + new Uint8Array(sab1).set([1, 2, 3]); + const sab2 = new SharedArrayBuffer(3); + new Uint8Array(sab2).set([4, 5, 6]); + const sab3 = new SharedArrayBuffer(3); + new Uint8Array(sab3).set([7, 8, 9]); + const result = SharedArrayBuffer.concat([sab1, sab2, sab3], { length: 5 }); + new Uint8Array(result) // → [1, 2, 3, 4, 5] + ``` + +### 39.3 Zero-length items in the mix + +- [ ] [39.3.1] `SharedArrayBuffer.concat([new SharedArrayBuffer(0), new SharedArrayBuffer(4), new SharedArrayBuffer(0)])` → byteLength 4 + +--- + +## 40. `SharedArrayBuffer.concat` — Tamper Resistance + +### 40.1 Overridden `.byteLength` on SharedArrayBuffer items + +- [ ] [40.1.1] `Object.defineProperty` to increase `.byteLength`: + ```js + const sab = new SharedArrayBuffer(4); + new Uint8Array(sab).set([1, 2, 3, 4]); + Object.defineProperty(sab, 'byteLength', { value: 100 }); + const result = SharedArrayBuffer.concat([sab]); + result.byteLength // → 4 (uses [[ArrayBufferByteLength]], not .byteLength) + ``` + +### 40.2 Overridden properties on TypedArray items + +- [ ] [40.2.1] Overridden `.byteLength` on TypedArray passed to `SharedArrayBuffer.concat`: + ```js + const u8 = new Uint8Array([1, 2, 3, 4]); + Object.defineProperty(u8, 'byteLength', { value: 100 }); + const result = SharedArrayBuffer.concat([u8]); + result.byteLength // → 4 + ``` + +### 40.3 Overridden options object accessors + +- [ ] [40.3.1] Options object with getter on `length` that has side effects: + ```js + let callCount = 0; + const opts = { get length() { callCount++; return 4; } }; + SharedArrayBuffer.concat([new SharedArrayBuffer(2)], opts); + callCount // → 1 + ``` +- [ ] [40.3.2] Options object with getter on `growable` that throws: + ```js + const opts = { get growable() { throw new Error('boom'); } }; + SharedArrayBuffer.concat([], opts) // → Error('boom') + ``` + +### 40.4 Prototype pollution + +- [ ] [40.4.1] Poisoned `SharedArrayBuffer.prototype.byteLength` does not affect result: + ```js + const origDesc = Object.getOwnPropertyDescriptor(SharedArrayBuffer.prototype, 'byteLength'); + try { + Object.defineProperty(SharedArrayBuffer.prototype, 'byteLength', { get() { return 9999; } }); + const sab = new SharedArrayBuffer(4); + new Uint8Array(sab).set([1, 2, 3, 4]); + const result = SharedArrayBuffer.concat([sab]); + result.byteLength // → 4 + } finally { + Object.defineProperty(SharedArrayBuffer.prototype, 'byteLength', origDesc); + } + ``` + +--- + +## 41. `SharedArrayBuffer.concat` — Items Iterable Variations + +- [ ] [41.1.1] Plain Array: `SharedArrayBuffer.concat([new SharedArrayBuffer(2), new SharedArrayBuffer(2)])` → works +- [ ] [41.1.2] Generator: + ```js + function* gen() { yield new SharedArrayBuffer(2); yield new Uint8Array([1, 2]); } + SharedArrayBuffer.concat(gen()) // → SharedArrayBuffer of byteLength 4 + ``` +- [ ] [41.1.3] Set of mixed types: + ```js + SharedArrayBuffer.concat(new Set([new SharedArrayBuffer(2), new Uint8Array([1, 2]), new DataView(new ArrayBuffer(2))])) + // → SharedArrayBuffer of byteLength 6 + ``` diff --git a/spec.emu b/spec.emu index 5d05685..28dabb1 100644 --- a/spec.emu +++ b/spec.emu @@ -4,36 +4,285 @@ - -

This is an emu-clause

-

This is an algorithm:

- - 1. Let _proposal_ be *undefined*. - 1. If IsAccepted(_proposal_) is *true*, then - 1. Let _stage_ be *0*. - 1. Else, - 1. Let _stage_ be *-1*. - 1. Return ? ToString(_stage_). - + +

TypedArray Objects

+ + +

The %TypedArray% Intrinsic Object

+ + +

Abstract Operations for TypedArray Objects

+ + +

+ ValidateIntegralNumber ( + _value_: an ECMAScript language value, + _default_: an integer, + ): either a normal completion containing an integer or a throw completion +

+
+
description
+
It validates that _value_ is either *undefined* or an integral Number, returning the corresponding mathematical integer. If _value_ is *undefined*, the _default_ is returned.
+
+ + 1. If _value_ is *undefined*, return _default_. + 1. If _value_ is not a Number, throw a *TypeError* exception. + 1. If _value_ is *NaN*, throw a *RangeError* exception. + 1. If truncate(ℝ(_value_)) is not ℝ(_value_), throw a *RangeError* exception. + 1. Return ℝ(_value_). + +
+
+ + +

Properties of the %TypedArray% Intrinsic Object

+ + +

%TypedArray%.concat ( _items_ [ , _length_ ] )

+

This method concatenates the elements of multiple TypedArrays of the same type into a new TypedArray. It performs the following steps when called:

+ + 1. Let _constructor_ be the *this* value. + 1. If IsConstructor(_constructor_) is *false*, throw a *TypeError* exception. + 1. If _constructor_ does not have a [[TypedArrayName]] internal slot, throw a *TypeError* exception. + 1. Let _arrayList_ be ? IteratorToList(? GetIteratorFromMethod(_items_, ? GetMethod(_items_, %Symbol.iterator%))). + 1. Let _newLength_ be ~empty~. + 1. If _length_ is present and _length_ is not *undefined*, then + 1. Set _newLength_ to ? ValidateIntegralNumber(_length_, 0). + 1. If _newLength_ < 0, throw a *RangeError* exception. + 1. If _newLength_ > 253 - 1, throw a *RangeError* exception. + 1. Let _totalLength_ be 0. + 1. Let _constructorName_ be _constructor_.[[TypedArrayName]]. + 1. For each element _item_ of _arrayList_, do + 1. Let _taRecord_ be ? ValidateTypedArray(_item_, ~seq-cst~). + 1. If _item_.[[TypedArrayName]] is not _constructorName_, throw a *TypeError* exception. + 1. Let _itemLength_ be TypedArrayLength(_taRecord_). + 1. Set _totalLength_ to _totalLength_ + _itemLength_. + 1. If _totalLength_ > 253 - 1, throw a *RangeError* exception. + 1. If _newLength_ is ~empty~, then + 1. Set _newLength_ to _totalLength_. + 1. Let _defaultProto_ be the String value of the Prototype column of the row in for _constructorName_. + 1. Let _result_ be ? AllocateTypedArray(_constructorName_, _constructor_, _defaultProto_). + 1. Perform ? AllocateTypedArrayBuffer(_result_, _newLength_). + 1. Let _elementSize_ be TypedArrayElementSize(_result_). + 1. [id="step-ta-copy-start"] Let _writeOffset_ be 0. + 1. For each element _item_ of _arrayList_, do + 1. Let _remaining_ be _newLength_ - _writeOffset_. + 1. If _remaining_ > 0, then + 1. Let _taRecord_ be MakeTypedArrayWithBufferWitnessRecord(_item_, ~seq-cst~). + 1. Let _itemLength_ be TypedArrayLength(_taRecord_). + 1. Let _count_ be the result of clamping _itemLength_ between 0 and _remaining_. + 1. Let _srcByteOffset_ be _item_.[[ByteOffset]]. + 1. Let _srcByteLength_ be _count_ × _elementSize_. + 1. Perform CopyDataBlockBytes(_result_.[[ViewedArrayBuffer]].[[ArrayBufferData]], _writeOffset_ × _elementSize_, _item_.[[ViewedArrayBuffer]].[[ArrayBufferData]], _srcByteOffset_, _srcByteLength_). + 1. [id="step-ta-copy-done"] Set _writeOffset_ to _writeOffset_ + _count_. + 1. Return _result_. + + +

The algorithm from through is specified with explicit allocation and byte-copying steps for clarity. Because all items share the same element type and the copy operates on the underlying data blocks, the result of the concatenation is not observable to user code during construction. Implementations may therefore use any technique — such as bulk memory copy, deferred allocation, or platform-specific optimizations — provided the observable result is equivalent.

+
+
+
+
+
+ + +

Abstract Operations for Buffer Concatenation

+ + +

+ GetConcatenationSources ( + _arrayList_: a List of ECMAScript language values, + ): either a normal completion containing a Record with fields [[Sources]] (a List of Records) and [[TotalByteLength]] (a non-negative integer), or a throw completion +

+
+
description
+
It validates each element of _arrayList_ as an ArrayBuffer, SharedArrayBuffer, TypedArray, or DataView, and returns a Record containing a List of source Records (each with [[Buffer]], [[ByteOffset]], and [[ByteLength]] fields) and the total byte length of all sources.
+
+ + 1. Let _totalByteLength_ be 0. + 1. Let _sources_ be a new empty List. + 1. For each element _item_ of _arrayList_, do + 1. If _item_ has a [[TypedArrayName]] internal slot, then + 1. Let _taRecord_ be ? ValidateTypedArray(_item_, ~seq-cst~). + 1. Let _byteLen_ be TypedArrayByteLength(_taRecord_). + 1. Append the Record { [[Buffer]]: _item_.[[ViewedArrayBuffer]], [[ByteOffset]]: _item_.[[ByteOffset]], [[ByteLength]]: _byteLen_ } to _sources_. + 1. Set _totalByteLength_ to _totalByteLength_ + _byteLen_. + 1. If _totalByteLength_ > 253 - 1, throw a *RangeError* exception. + 1. Else if _item_ has a [[DataView]] internal slot, then + 1. Let _dvRecord_ be MakeDataViewWithBufferWitnessRecord(_item_, ~seq-cst~). + 1. If IsViewOutOfBounds(_dvRecord_) is *true*, throw a *TypeError* exception. + 1. Let _byteLen_ be GetViewByteLength(_dvRecord_). + 1. Append the Record { [[Buffer]]: _item_.[[ViewedArrayBuffer]], [[ByteOffset]]: _item_.[[ByteOffset]], [[ByteLength]]: _byteLen_ } to _sources_. + 1. Set _totalByteLength_ to _totalByteLength_ + _byteLen_. + 1. If _totalByteLength_ > 253 - 1, throw a *RangeError* exception. + 1. Else if _item_ has an [[ArrayBufferData]] internal slot, then + 1. If IsDetachedBuffer(_item_) is *true*, throw a *TypeError* exception. + 1. Let _byteLen_ be ArrayBufferByteLength(_item_, ~seq-cst~). + 1. Append the Record { [[Buffer]]: _item_, [[ByteOffset]]: 0, [[ByteLength]]: _byteLen_ } to _sources_. + 1. Set _totalByteLength_ to _totalByteLength_ + _byteLen_. + 1. If _totalByteLength_ > 253 - 1, throw a *RangeError* exception. + 1. Else, + 1. Throw a *TypeError* exception. + 1. Return the Record { [[Sources]]: _sources_, [[TotalByteLength]]: _totalByteLength_ }. + + +

For TypedArray and DataView inputs, only the viewed portion of the underlying buffer is included. The byte lengths are snapshotted with ~seq-cst~ ordering at the time each item is validated.

+
+
+ + +

+ CopySourcesToBuffer ( + _result_: an ArrayBuffer or SharedArrayBuffer, + _sources_: a List of Records with fields [[Buffer]], [[ByteOffset]], and [[ByteLength]], + ): ~unused~ +

+
+
description
+
It copies byte data from _sources_ into _result_ in order, stopping when _result_ is full.
+
+ + 1. [id="step-copy-start"] Let _writeOffset_ be 0. + 1. For each element _source_ of _sources_, do + 1. Let _remaining_ be _result_.[[ArrayBufferByteLength]] - _writeOffset_. + 1. If _remaining_ > 0, then + 1. Let _sourceBuffer_ be _source_.[[Buffer]]. + 1. Let _sourceByteOffset_ be _source_.[[ByteOffset]]. + 1. Let _sourceByteLength_ be _source_.[[ByteLength]]. + 1. Let _count_ be the result of clamping _sourceByteLength_ between 0 and _remaining_. + 1. Perform CopyDataBlockBytes(_result_.[[ArrayBufferData]], _writeOffset_, _sourceBuffer_.[[ArrayBufferData]], _sourceByteOffset_, _count_). + 1. [id="step-copy-done"] Set _writeOffset_ to _writeOffset_ + _count_. + 1. Return ~unused~. + + +

The algorithm from through is specified with explicit byte-copying steps for clarity. Because the copy operates on the underlying data blocks, the result of the concatenation is not observable to user code during construction. Implementations may therefore use any technique — such as bulk memory copy, deferred allocation, or platform-specific optimizations — provided the observable result is equivalent.

+
+
+
+ + +

ArrayBuffer Objects

+ + +

+ MakeArrayBufferImmutable ( + _arrayBuffer_: an ArrayBuffer, + ): ~unused~ +

+
+
description
+
It transitions _arrayBuffer_ to an immutable state whose contents cannot be changed, resized, or detached.
+
+ + 1. Assert: IsDetachedBuffer(_arrayBuffer_) is *false*. + 1. NOTE: The concrete steps for this abstract operation will be provided by the Immutable ArrayBuffer proposal. + 1. Return ~unused~. + + +

This is a placeholder abstract operation. The actual mechanism for transitioning an ArrayBuffer to an immutable state will be defined by the Immutable ArrayBuffer proposal (currently at stage 2.7).

+
+
+ + +

Properties of the ArrayBuffer Constructor

+ + +

ArrayBuffer.concat ( _items_ [ , _options_ ] )

+

This method concatenates the byte contents of multiple ArrayBuffers, SharedArrayBuffers, TypedArrays, or DataViews into a new ArrayBuffer. It performs the following steps when called:

+ + 1. Let _arrayList_ be ? IteratorToList(? GetIteratorFromMethod(_items_, ? GetMethod(_items_, %Symbol.iterator%))). + 1. If _options_ is *undefined*, then + 1. Let _optionsObj_ be OrdinaryObjectCreate(*null*). + 1. Else if _options_ is an Object, then + 1. Let _optionsObj_ be _options_. + 1. Else, + 1. Throw a *TypeError* exception. + 1. Let _lengthOption_ be ? Get(_optionsObj_, *"length"*). + 1. Let _newByteLength_ be ~empty~. + 1. If _lengthOption_ is not *undefined*, then + 1. Set _newByteLength_ to ? ValidateIntegralNumber(_lengthOption_, 0). + 1. If _newByteLength_ < 0, throw a *RangeError* exception. + 1. If _newByteLength_ > 253 - 1, throw a *RangeError* exception. + 1. Let _resizable_ be ToBoolean(? Get(_optionsObj_, *"resizable"*)). + 1. Let _immutable_ be ToBoolean(? Get(_optionsObj_, *"immutable"*)). + 1. If _resizable_ is *true* and _immutable_ is *true*, throw a *TypeError* exception. + 1. Let _concatenation_ be ? GetConcatenationSources(_arrayList_). + 1. Let _sources_ be _concatenation_.[[Sources]]. + 1. Let _totalByteLength_ be _concatenation_.[[TotalByteLength]]. + 1. If _newByteLength_ is ~empty~, then + 1. Set _newByteLength_ to _totalByteLength_. + 1. If _resizable_ is *true*, then + 1. Let _maxByteLength_ be _newByteLength_. + 1. Let _byteLength_ be the result of clamping _totalByteLength_ between 0 and _maxByteLength_. + 1. Let _result_ be ? AllocateArrayBuffer(%ArrayBuffer%, _byteLength_, _maxByteLength_). + 1. Else, + 1. Let _result_ be ? AllocateArrayBuffer(%ArrayBuffer%, _newByteLength_). + 1. Perform CopySourcesToBuffer(_result_, _sources_). + 1. If _immutable_ is *true*, then + 1. Perform MakeArrayBufferImmutable(_result_). + 1. Return _result_. + + +

The _immutable_ option depends on the Immutable ArrayBuffer proposal (currently at stage 2.7). MakeArrayBufferImmutable is a placeholder for the abstract operation that will be defined by that proposal.

+
+ +

The result is always a (non-shared) ArrayBuffer regardless of the input types.

+

If the optional _options_ object is provided, the *"length"* property controls the byte length of the result. If *"length"* is less than the total byte length of the inputs, the result is truncated. If *"length"* is greater, the result is zero-filled beyond the copied bytes.

+

If the *"resizable"* option is *true*, the result is a resizable ArrayBuffer where *"length"* specifies the maximum byte length. The actual byte length is the lesser of the total input bytes and *"length"*.

+

If the *"immutable"* option is *true*, the result is an immutable ArrayBuffer whose contents cannot be changed, resized, or detached. The *"resizable"* and *"immutable"* options are mutually exclusive.

+
+
+
- -

- IsAccepted ( - _proposal_: an ECMAScript language value - ): a Boolean -

-
-
description
-
Tells you if the proposal was accepted
-
- - 1. If _proposal_ is not a String, or is not accepted, return *false*. - 1. Return *true*. - + +

SharedArrayBuffer Objects

+ + +

Properties of the SharedArrayBuffer Constructor

+ + +

SharedArrayBuffer.concat ( _items_ [ , _options_ ] )

+

This method concatenates the byte contents of multiple ArrayBuffers, SharedArrayBuffers, TypedArrays, or DataViews into a new SharedArrayBuffer. It performs the following steps when called:

+ + 1. Let _arrayList_ be ? IteratorToList(? GetIteratorFromMethod(_items_, ? GetMethod(_items_, %Symbol.iterator%))). + 1. If _options_ is *undefined*, then + 1. Let _optionsObj_ be OrdinaryObjectCreate(*null*). + 1. Else if _options_ is an Object, then + 1. Let _optionsObj_ be _options_. + 1. Else, + 1. Throw a *TypeError* exception. + 1. Let _lengthOption_ be ? Get(_optionsObj_, *"length"*). + 1. Let _newByteLength_ be ~empty~. + 1. If _lengthOption_ is not *undefined*, then + 1. Set _newByteLength_ to ? ValidateIntegralNumber(_lengthOption_, 0). + 1. If _newByteLength_ < 0, throw a *RangeError* exception. + 1. If _newByteLength_ > 253 - 1, throw a *RangeError* exception. + 1. Let _growable_ be ToBoolean(? Get(_optionsObj_, *"growable"*)). + 1. Let _concatenation_ be ? GetConcatenationSources(_arrayList_). + 1. Let _sources_ be _concatenation_.[[Sources]]. + 1. Let _totalByteLength_ be _concatenation_.[[TotalByteLength]]. + 1. If _newByteLength_ is ~empty~, then + 1. Set _newByteLength_ to _totalByteLength_. + 1. If _growable_ is *true*, then + 1. Let _maxByteLength_ be _newByteLength_. + 1. Let _byteLength_ be the result of clamping _totalByteLength_ between 0 and _maxByteLength_. + 1. Let _result_ be ? AllocateSharedArrayBuffer(%SharedArrayBuffer%, _byteLength_, _maxByteLength_). + 1. Else, + 1. Let _result_ be ? AllocateSharedArrayBuffer(%SharedArrayBuffer%, _newByteLength_). + 1. Perform CopySourcesToBuffer(_result_, _sources_). + 1. Return _result_. + + +

The result is always a SharedArrayBuffer regardless of the input types.

+

If the optional _options_ object is provided, the *"length"* property controls the byte length of the result. If *"length"* is less than the total byte length of the inputs, the result is truncated. If *"length"* is greater, the result is zero-filled beyond the copied bytes.

+

If the *"growable"* option is *true*, the result is a growable SharedArrayBuffer where *"length"* specifies the maximum byte length. The actual byte length is the lesser of the total input bytes and *"length"*.

+
+
+