From ee20ddf209f05ce037a6fa3df7de3fce388833f4 Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Sun, 14 Jun 2026 11:08:22 +0200 Subject: [PATCH 1/2] stream: only pass the expected number of parameters to callbacks Signed-off-by: Antoine du Hamel Co-authored-by: Matteo Collina --- lib/internal/webstreams/readablestream.js | 10 ++++----- lib/internal/webstreams/transformstream.js | 9 ++++---- lib/internal/webstreams/util.js | 24 +++++++++++++++++++--- lib/internal/webstreams/writablestream.js | 10 +++++---- 4 files changed, 37 insertions(+), 16 deletions(-) diff --git a/lib/internal/webstreams/readablestream.js b/lib/internal/webstreams/readablestream.js index 8ae3fff11abbf1..4f7eb5ce95b56e 100644 --- a/lib/internal/webstreams/readablestream.js +++ b/lib/internal/webstreams/readablestream.js @@ -104,7 +104,7 @@ const { canCopyArrayBuffer, cloneAsUint8Array, copyArrayBuffer, - createPromiseCallback, + createPromiseCallback1Param, customInspect, dequeueValue, enqueueValueWithSize, @@ -2582,10 +2582,10 @@ function setupReadableStreamDefaultControllerFromSource( FunctionPrototypeBind(start, source, controller) : nonOpStart; const pullAlgorithm = pull ? - createPromiseCallback('source.pull', pull, source) : + createPromiseCallback1Param('source.pull', pull, source) : nonOpPull; const cancelAlgorithm = cancel ? - createPromiseCallback('source.cancel', cancel, source) : + createPromiseCallback1Param('source.cancel', cancel, source) : nonOpCancel; setupReadableStreamDefaultController( @@ -3405,10 +3405,10 @@ function setupReadableByteStreamControllerFromSource( FunctionPrototypeBind(start, source, controller) : nonOpStart; const pullAlgorithm = pull ? - createPromiseCallback('source.pull', pull, source, controller) : + createPromiseCallback1Param('source.pull', pull, source) : nonOpPull; const cancelAlgorithm = cancel ? - createPromiseCallback('source.cancel', cancel, source) : + createPromiseCallback1Param('source.cancel', cancel, source) : nonOpCancel; if (autoAllocateChunkSize === 0) { diff --git a/lib/internal/webstreams/transformstream.js b/lib/internal/webstreams/transformstream.js index 96d48b438d479c..5b1be9e3aa7a65 100644 --- a/lib/internal/webstreams/transformstream.js +++ b/lib/internal/webstreams/transformstream.js @@ -43,7 +43,8 @@ const { } = require('internal/worker/js_transferable'); const { - createPromiseCallback, + createPromiseCallback1Param, + createPromiseCallback2Params, customInspect, extractHighWaterMark, extractSizeAlgorithm, @@ -462,13 +463,13 @@ function setupTransformStreamDefaultControllerFromTransformer( const flush = transformer?.flush; const cancel = transformer?.cancel; const transformAlgorithm = transform ? - createPromiseCallback('transformer.transform', transform, transformer) : + createPromiseCallback2Params('transformer.transform', transform, transformer) : defaultTransformAlgorithm; const flushAlgorithm = flush ? - createPromiseCallback('transformer.flush', flush, transformer) : + createPromiseCallback1Param('transformer.flush', flush, transformer) : nonOpFlush; const cancelAlgorithm = cancel ? - createPromiseCallback('transformer.cancel', cancel, transformer) : + createPromiseCallback1Param('transformer.cancel', cancel, transformer) : nonOpCancel; setupTransformStreamDefaultController( diff --git a/lib/internal/webstreams/util.js b/lib/internal/webstreams/util.js index 808b0b069e57f7..fa9b8e7ed24cdb 100644 --- a/lib/internal/webstreams/util.js +++ b/lib/internal/webstreams/util.js @@ -169,9 +169,25 @@ function enqueueValueWithSize(controller, value, size) { controller[kState].queueTotalSize += size; } -function createPromiseCallback(name, fn, thisArg) { +// Arity-specialized variants of the promise-callback wrapper. The generic +// rest-parameter + ReflectApply form allocated an arguments array on every +// invocation; these run on per-chunk hot paths (pull/write/transform), so +// each known call-site arity gets its own wrapper. The exact number of +// arguments passed through to the user callback is observable and must be +// preserved. +function createPromiseCallbackNoParams(name, fn, thisArg) { validateFunction(fn, name); - return async (...args) => ReflectApply(fn, thisArg, args); + return async () => FunctionPrototypeCall(fn, thisArg); +} + +function createPromiseCallback1Param(name, fn, thisArg) { + validateFunction(fn, name); + return async (arg) => FunctionPrototypeCall(fn, thisArg, arg); +} + +function createPromiseCallback2Params(name, fn, thisArg) { + validateFunction(fn, name); + return async (arg1, arg2) => FunctionPrototypeCall(fn, thisArg, arg1, arg2); } function isPromisePending(promise) { @@ -213,7 +229,9 @@ module.exports = { canCopyArrayBuffer, cloneAsUint8Array, copyArrayBuffer, - createPromiseCallback, + createPromiseCallbackNoParams, + createPromiseCallback1Param, + createPromiseCallback2Params, customInspect, dequeueValue, enqueueValueWithSize, diff --git a/lib/internal/webstreams/writablestream.js b/lib/internal/webstreams/writablestream.js index 8f62a199d5b301..7e5f7296e28b36 100644 --- a/lib/internal/webstreams/writablestream.js +++ b/lib/internal/webstreams/writablestream.js @@ -55,7 +55,9 @@ const { } = require('internal/worker/js_transferable'); const { - createPromiseCallback, + createPromiseCallbackNoParams, + createPromiseCallback1Param, + createPromiseCallback2Params, customInspect, dequeueValue, enqueueValueWithSize, @@ -1272,13 +1274,13 @@ function setupWritableStreamDefaultControllerFromSink( FunctionPrototypeBind(start, sink, controller) : nonOpStart; const writeAlgorithm = write ? - createPromiseCallback('sink.write', write, sink) : + createPromiseCallback2Params('sink.write', write, sink) : nonOpWrite; const closeAlgorithm = close ? - createPromiseCallback('sink.close', close, sink) : + createPromiseCallbackNoParams('sink.close', close, sink) : nonOpCancel; const abortAlgorithm = abort ? - createPromiseCallback('sink.abort', abort, sink) : + createPromiseCallback1Param('sink.abort', abort, sink) : nonOpCancel; setupWritableStreamDefaultController( stream, From 42e0af05081bd60e8eabc09810adbe41336a92af Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Sun, 14 Jun 2026 11:22:37 +0200 Subject: [PATCH 2/2] fixup! stream: only pass the expected number of parameters to callbacks --- lib/internal/webstreams/util.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/internal/webstreams/util.js b/lib/internal/webstreams/util.js index fa9b8e7ed24cdb..1f6d648ada9eed 100644 --- a/lib/internal/webstreams/util.js +++ b/lib/internal/webstreams/util.js @@ -7,10 +7,10 @@ const { ArrayPrototypePush, ArrayPrototypeShift, AsyncIteratorPrototype, + FunctionPrototypeCall, MathMax, NumberIsNaN, PromisePrototypeThen, - ReflectApply, ReflectGet, Symbol, Uint8Array,