From 70fa4cee50e7900f322b4c077b5fa0da0875b1a4 Mon Sep 17 00:00:00 2001 From: "Kamat, Trivikram" <16024985+trivikr@users.noreply.github.com> Date: Wed, 8 Jul 2026 20:57:10 -0700 Subject: [PATCH] stream: copy SAB-backed chunks in iter consumers Ensure bytes() copies single chunks backed by SharedArrayBuffer so arrayBuffer() and arrayBufferSync() return ArrayBuffer instances as specified. Signed-off-by: Kamat, Trivikram <16024985+trivikr@users.noreply.github.com> Assisted-by: openai:gpt-5.5 --- lib/internal/streams/iter/consumers.js | 14 ++++++-------- lib/internal/streams/iter/utils.js | 19 ++++++++++++------- .../test-stream-iter-sharedarraybuffer.js | 6 ++++-- 3 files changed, 22 insertions(+), 17 deletions(-) diff --git a/lib/internal/streams/iter/consumers.js b/lib/internal/streams/iter/consumers.js index 7a2d5719b9b97b..8e18c1b5b930ea 100644 --- a/lib/internal/streams/iter/consumers.js +++ b/lib/internal/streams/iter/consumers.js @@ -22,6 +22,8 @@ const { TypedArrayPrototypeGetBuffer, TypedArrayPrototypeGetByteLength, TypedArrayPrototypeGetByteOffset, + TypedArrayPrototypeSet, + Uint8Array, } = primordials; const { @@ -164,21 +166,17 @@ async function collectAsync(source, signal, limit) { /** * Convert a Uint8Array to its backing ArrayBuffer, slicing if necessary. - * Handles both ArrayBuffer and SharedArrayBuffer backing stores. * @param {Uint8Array} data - * @returns {ArrayBuffer|SharedArrayBuffer} + * @returns {ArrayBuffer} */ function toArrayBuffer(data) { const byteOffset = TypedArrayPrototypeGetByteOffset(data); const byteLength = TypedArrayPrototypeGetByteLength(data); const buffer = TypedArrayPrototypeGetBuffer(data); - // SharedArrayBuffer is not available in primordials, so use - // direct property access for its byteLength and slice. if (isSharedArrayBuffer(buffer)) { - if (byteOffset === 0 && byteLength === buffer.byteLength) { - return buffer; - } - return buffer.slice(byteOffset, byteOffset + byteLength); + const copy = new Uint8Array(byteLength); + TypedArrayPrototypeSet(copy, data); + return TypedArrayPrototypeGetBuffer(copy); } if (byteOffset === 0 && byteLength === ArrayBufferPrototypeGetByteLength(buffer)) { diff --git a/lib/internal/streams/iter/utils.js b/lib/internal/streams/iter/utils.js index ca3d2531d6f9e0..c52393493bc0a8 100644 --- a/lib/internal/streams/iter/utils.js +++ b/lib/internal/streams/iter/utils.js @@ -203,6 +203,12 @@ function allUint8Array(chunks) { return true; } +function copyBytes(chunk) { + const copy = new Uint8Array(TypedArrayPrototypeGetByteLength(chunk)); + TypedArrayPrototypeSet(copy, chunk); + return copy; +} + /** * Concatenate multiple Uint8Arrays into a single Uint8Array. * @param {Uint8Array[]} chunks @@ -220,16 +226,15 @@ function concatBytes(chunks) { // If non-zero offset, skip the remaining buffer checks. if (TypedArrayPrototypeGetByteOffset(chunk) === 0) { const buf = TypedArrayPrototypeGetBuffer(chunk); - // SharedArrayBuffer is not available in primordials, so use - // direct property access for its byteLength. - const bufByteLength = isSharedArrayBuffer(buf) ? - buf.byteLength : - ArrayBufferPrototypeGetByteLength(buf); - if (TypedArrayPrototypeGetByteLength(chunk) === bufByteLength) { + if ( + !isSharedArrayBuffer(buf) && + TypedArrayPrototypeGetByteLength(chunk) === + ArrayBufferPrototypeGetByteLength(buf) + ) { return chunk; } } - return new Uint8Array(chunk); + return copyBytes(chunk); } // Multiple chunks: concatenate let totalByteLength = 0; diff --git a/test/parallel/test-stream-iter-sharedarraybuffer.js b/test/parallel/test-stream-iter-sharedarraybuffer.js index 7aff205bf7664a..de0f8d6f3fc636 100644 --- a/test/parallel/test-stream-iter-sharedarraybuffer.js +++ b/test/parallel/test-stream-iter-sharedarraybuffer.js @@ -52,6 +52,7 @@ function testBytesSyncSAB() { const sab = new SharedArrayBuffer(5); new Uint8Array(sab).set([104, 101, 108, 108, 111]); // 'hello' const data = bytesSync(fromSync(sab)); + assert.ok(data.buffer instanceof ArrayBuffer); assert.deepStrictEqual(data, new Uint8Array([104, 101, 108, 108, 111])); } @@ -59,6 +60,7 @@ async function testBytesAsyncSAB() { const sab = new SharedArrayBuffer(5); new Uint8Array(sab).set([104, 101, 108, 108, 111]); // 'hello' const data = await bytes(from(sab)); + assert.ok(data.buffer instanceof ArrayBuffer); assert.deepStrictEqual(data, new Uint8Array([104, 101, 108, 108, 111])); } @@ -80,7 +82,7 @@ function testArrayBufferSyncSAB() { const sab = new SharedArrayBuffer(4); new Uint8Array(sab).set([1, 2, 3, 4]); const result = arrayBufferSync(fromSync(sab)); - assert.ok(result instanceof SharedArrayBuffer || result instanceof ArrayBuffer); + assert.ok(result instanceof ArrayBuffer); assert.deepStrictEqual(new Uint8Array(result), new Uint8Array([1, 2, 3, 4])); } @@ -88,7 +90,7 @@ async function testArrayBufferAsyncSAB() { const sab = new SharedArrayBuffer(4); new Uint8Array(sab).set([1, 2, 3, 4]); const result = await arrayBuffer(from(sab)); - assert.ok(result instanceof SharedArrayBuffer || result instanceof ArrayBuffer); + assert.ok(result instanceof ArrayBuffer); assert.deepStrictEqual(new Uint8Array(result), new Uint8Array([1, 2, 3, 4])); }