TypedArray.subarray byteOffset with detached buffers

Wondering if not setting byteOffset=0 in the TypedArray.subarray for detached buffers is a spec omission or a deliberate design decision.

I had noticed a new test262 for it in built-ins/TypedArray/prototype/subarray/byteoffset-with-detached-buffer.js and indeed the spec seems to require reading the byteOffset even if the buffer is detached.

If the array is detached spec says 6. If IsTypedArrayOutOfBounds(srcRecord) is true, then Let srcLength be 0.. IsTypedArrayOutOfBounds() considers a detached array out of bounds, so that takes effect and srcLength=0, however further below we don't do the same with the srcByteOffset. That seems inconsistent at first sight. Why does it make sense to preserve the byteOffset from a detached buffer; is it deliberate or it's a spec omission?

It would seem that If IsTypedArrayOutOfBounds(srcRecord) is true, then Let srcByteOffset be 0 would be more reasonable to have.

Maybe a simple example can help. This assumes we run in a test environment which has the ability to detach a buffer. The example is from the linked test262 test but a bit shorter.

const elementSize = Float64Array.BYTES_PER_ELEMENT; // 8
let buffer = new ArrayBuffer(2 * elementSize);
let typedArray = new Float64Array(buffer, elementSize, 1);

typedArray.constructor = {
    [Symbol.species]: function(_, offset, _) {
        print("elementSize: " + elementSize);
        print("offset: " + offset);
        return new Float64Array(1);
    }
};

let end = {valueOf() {$262.detachArrayBuffer(buffer); return 0;}};
typedArray.subarray(1, end);

Currently we print the offset = 16, but I wonder if we detached the buffer if it makes sense to still access the offset from it.

Once detached, there shouldn't be any information left in it, including the offset.

2 Likes

Once detached, there shouldn't be any information left in it, including the offset.

That was my idea as well and figured it might be an issue with the spec ECMAScript® 2026 Language Specification

Maybe line 13. Let srcByteOffset be O.[[ByteOffset]]. should check if the buffer is detached and set ByteOffset to 0 or raise an error?

After reading your code sample I think the test is correct.

According to the spec text for subarray the buffer isn't yet detached in step 6, so it's fine to obtain the offset in step 13. The buffer only becomes detached in step 16.a, as a side effect of ToIntegerOrInfinity.

1 Like

According to the spec text for subarray the buffer isn't yet detached in step 6, so it's fine to obtain the offset in step 13. The buffer only becomes detached in step 16.a, as a side effect of ToIntegerOrInfinity.

Oh, now I see it! That makes perfect sense. Thank you for explaining, @ptomato

Sorry for reviving an old thread. It seems there is still a case when the spec allows reading ByteOffset from a detached buffer:

If instead of the end it’s the start that has the valueOf() {$DETACHBUFFER(ab); return ...} logic (like the test does), in 8. Let relativeStart be ? ToIntegerOrInfinity(start). the buffer would be detached and in 13. Let srcByteOffset be O.[[ByteOffset]].we’d be reading the offset from a detached buffer.


1. Let O be the this value.
2. Perform ? RequireInternalSlot(O, [[TypedArrayName]]).
3. Assert: O has a [[ViewedArrayBuffer]] internal slot.
4. Let buffer be O.[[ViewedArrayBuffer]].
5. Let srcRecord be MakeTypedArrayWithBufferWitnessRecord(O, seq-cst).
6. If IsTypedArrayOutOfBounds(srcRecord) is true, then
   a. Let srcLength be 0.
7. Else,
   a. Let srcLength be TypedArrayLength(srcRecord).
8. Let relativeStart be ? ToIntegerOrInfinity(start).
9. If relativeStart = -∞, let startIndex be 0.
10. Else if relativeStart < 0, let startIndex be max(srcLength + relativeStart, 0).
11. Else, let startIndex be min(relativeStart, srcLength).
12. Let elementSize be TypedArrayElementSize(O).
13. Let srcByteOffset be O.[[ByteOffset]].
14. Let beginByteOffset be srcByteOffset + (startIndex × elementSize).
15. If O.[[ArrayLength]] is auto and end is undefined, then
    a. Let argumentsList be « buffer, 𝔽(beginByteOffset) ».
16. Else,
    a. If end is undefined, let relativeEnd be srcLength; else let relativeEnd be ? ToIntegerOrInfinity(end).
    b. If relativeEnd = -∞, let endIndex be 0.
    c. Else if relativeEnd < 0, let endIndex be max(srcLength + relativeEnd, 0).
    d. Else, let endIndex be min(relativeEnd, srcLength).
    e. Let newLength be max(endIndex - startIndex, 0).
    f. Let argumentsList be « buffer, 𝔽(beginByteOffset), 𝔽(newLength) ».
17. Return ? TypedArraySpeciesCreate(O, argumentsList).


In %TypedArray%.prototype.subarray, O is not an ArrayBuffer but rather a TypedArray, and reading its original-valued [[ByteOffset]] slot after detachment of its [[ViewedArrayBuffer]] doesn’t seem to incur any issues—beginByteOffset will be derived from combining that base with the value from coercing start, and then « buffer, 𝔽(beginByteOffset), … » arguments for a TypedArray constructor (via TypedArraySpeciesCreate) will be routed to InitializeTypedArrayFromArrayBuffer, which detects when buffer is already detached and throws a TypeError in that case.

Thanks for the clarification. So there is nothing inherently wrong in accessing the ByteOffset from the TypedArray even if the buffer is detached. InitializeTypedArrayFromArrayBufferwould throw an exception anyway. In the test the constructor is overridden and instead returns a new TypeArray altogether.