From 3a27a92cb4549ce6cbe58f89efcac9ff8b7b6cb9 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Tue, 20 Oct 2026 18:22:43 -0700 Subject: [PATCH] Forward at most four emit arguments through EventEmitter method values The dispatch instantiates one bridged emit per argument count and emitter class; capping it at four keeps programs that only read emitter members through computed keys from growing by the full table. Co-Authored-By: Claude Opus 5.5 --- docs/content/limitations/builtin-apis/eventemitter.mdx | 2 +- .../compiler/src/frontend/lowering/emitter-method-values.ts | 2 +- tests/corpus/6600-emitter-method-values-untyped.ts | 2 +- tests/corpus/6601-emitter-method-values.cjs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/content/limitations/builtin-apis/eventemitter.mdx b/docs/content/limitations/builtin-apis/eventemitter.mdx index e6c140cde..8539b814f 100644 --- a/docs/content/limitations/builtin-apis/eventemitter.mdx +++ b/docs/content/limitations/builtin-apis/eventemitter.mdx @@ -6,7 +6,7 @@ Through an untyped value, `listeners()` and `rawListeners()` of an event with a listener that has typed parameters are unsupported, and `emit('error')` of an object that is not an Error with no `error` listener is unsupported. Untyped `newListener` and `removeListener` listeners receive `undefined` in place of a listener registered with typed parameters. An untyped `emit` on, or an untyped read of `emit` from, an instance of a class that overrides `emit` is unsupported. - Calling `EventEmitter.prototype.emit` as a function value on a compiled emitter with more than 16 arguments after the event name is unsupported. + Calling `EventEmitter.prototype.emit` as a function value on a compiled emitter with more than 4 arguments after the event name is unsupported. Calling EventEmitter.prototype's methods with an instance of a compiled class that does not extend EventEmitter as the receiver throws a `TypeError`. Through an untyped value, a stream's `emit` and listener-introspection methods read as `undefined`. `events.once` accepts EventEmitter and stream instances with a literal event name and an optional object-literal `{ signal }`; a stream's `data` event, other options values, and EventTarget emitters are unsupported. diff --git a/packages/compiler/src/frontend/lowering/emitter-method-values.ts b/packages/compiler/src/frontend/lowering/emitter-method-values.ts index f04957e11..7b4992bee 100644 --- a/packages/compiler/src/frontend/lowering/emitter-method-values.ts +++ b/packages/compiler/src/frontend/lowering/emitter-method-values.ts @@ -40,7 +40,7 @@ export const EMITTER_PROTOTYPE_METHODS: ReadonlySet = new Set([ /** `emit` forwards this many arguments after the event name; a call with * more fences. */ -const EMIT_ARGUMENTS = 16; +const EMIT_ARGUMENTS = 4; /** The runtime's SCR_EE_M_* order: each method's name and the argument * counts its call forwards. Node's methods that look at diff --git a/tests/corpus/6600-emitter-method-values-untyped.ts b/tests/corpus/6600-emitter-method-values-untyped.ts index 1675c97d6..f25be85d9 100644 --- a/tests/corpus/6600-emitter-method-values-untyped.ts +++ b/tests/corpus/6600-emitter-method-values-untyped.ts @@ -40,7 +40,7 @@ console.log(o.removeAllListeners.call(o, "c") === o, o.eventNames()); o.removeAllListeners.call(o); console.log(o.eventNames()); o.on("many", (...args: unknown[]) => console.log("many:", args.length, args.join(","))); -console.log(emit("many", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)); +console.log(emit("many", 1, 2, 3, 4)); console.log(o.setMaxListeners.call(o, 3) === o, o.getMaxListeners.call(o)); // off through a detached method value diff --git a/tests/corpus/6601-emitter-method-values.cjs b/tests/corpus/6601-emitter-method-values.cjs index 7873b5f8e..e462bda8e 100644 --- a/tests/corpus/6601-emitter-method-values.cjs +++ b/tests/corpus/6601-emitter-method-values.cjs @@ -38,7 +38,7 @@ const plain = new EventEmitter(); const emit = plain.emit; plain.on("hi", (a, b, c) => console.log("hi", a, b, c)); console.log(emit.call(plain, "hi", 1, "two", [3])); -console.log(emit.call(plain, "hi", 1, 2, 3, 4, 5, 6, 7, 8, 9)); +console.log(emit.call(plain, "hi", 1, 2, 3, 4)); const fn = () => console.log("never"); plain.once("x", fn); const off = plain.off;