fix(node): use primordials more consistently in _events.mjs (#29930)
Some checks failed
ci / pre-build (push) Has been cancelled
ci / lint debug windows-x86_64 (push) Has been cancelled
ci / test debug linux-x86_64 (push) Has been cancelled
ci / test release linux-x86_64 (push) Has been cancelled
ci / test debug macos-x86_64 (push) Has been cancelled
ci / test release macos-x86_64 (push) Has been cancelled
ci / test debug windows-x86_64 (push) Has been cancelled
ci / test release windows-x86_64 (push) Has been cancelled
ci / build libs (push) Has been cancelled
ci / test debug linux-aarch64 (push) Has been cancelled
ci / test release linux-aarch64 (push) Has been cancelled
ci / test debug macos-aarch64 (push) Has been cancelled
ci / test release macos-aarch64 (push) Has been cancelled
ci / bench release linux-x86_64 (push) Has been cancelled
ci / lint debug linux-x86_64 (push) Has been cancelled
ci / lint debug macos-x86_64 (push) Has been cancelled
ci / publish canary (push) Has been cancelled

Fixes #29929

---------

Signed-off-by: Nicholas Berlette <11234104+nberlette@users.noreply.github.com>
Co-authored-by: Bartek Iwańczuk <biwanczuk@gmail.com>
This commit is contained in:
Nicholas Berlette 2025-06-28 10:03:21 -07:00 committed by GitHub
parent 8fcbb0fa43
commit a70f1cfab4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 121 additions and 37 deletions

View file

@ -29,13 +29,27 @@ import { primordials } from "ext:core/mod.js";
const { const {
ArrayPrototypeMap, ArrayPrototypeMap,
ArrayPrototypeFilter, ArrayPrototypeFilter,
ArrayPrototypePush,
ArrayPrototypeShift,
ArrayPrototypeUnshift,
Error,
ErrorCaptureStackTrace,
FunctionPrototypeCall,
FunctionPrototypeApply,
ObjectCreate,
ObjectDefineProperty, ObjectDefineProperty,
ObjectEntries, ObjectEntries,
ObjectGetPrototypeOf,
ObjectSetPrototypeOf,
ReflectOwnKeys,
SafeMap, SafeMap,
SafeSet, SafeSet,
Symbol,
SymbolFor,
SymbolAsyncIterator,
} = primordials; } = primordials;
const kRejection = Symbol.for("nodejs.rejection"); const kRejection = SymbolFor("nodejs.rejection");
export const kEvents = Symbol("kEvents"); export const kEvents = Symbol("kEvents");
import { inspect } from "ext:deno_node/internal/util/inspect.mjs"; import { inspect } from "ext:deno_node/internal/util/inspect.mjs";
@ -78,7 +92,7 @@ const kMaxEventTargetListenersWarned = Symbol(
* @returns {EventEmitter} * @returns {EventEmitter}
*/ */
export function EventEmitter(opts) { export function EventEmitter(opts) {
EventEmitter.init.call(this, opts); FunctionPrototypeCall(EventEmitter.init, this, opts);
} }
export default EventEmitter; export default EventEmitter;
EventEmitter.on = on; EventEmitter.on = on;
@ -95,7 +109,7 @@ EventEmitter.captureRejectionSymbol = kRejection;
export const captureRejectionSymbol = EventEmitter.captureRejectionSymbol; export const captureRejectionSymbol = EventEmitter.captureRejectionSymbol;
export const errorMonitor = EventEmitter.errorMonitor; export const errorMonitor = EventEmitter.errorMonitor;
Object.defineProperty(EventEmitter, "captureRejections", { ObjectDefineProperty(EventEmitter, "captureRejections", {
get() { get() {
return EventEmitter.prototype[kCapture]; return EventEmitter.prototype[kCapture];
}, },
@ -110,7 +124,7 @@ Object.defineProperty(EventEmitter, "captureRejections", {
EventEmitter.errorMonitor = kErrorMonitor; EventEmitter.errorMonitor = kErrorMonitor;
// The default for captureRejections is false // The default for captureRejections is false
Object.defineProperty(EventEmitter.prototype, kCapture, { ObjectDefineProperty(EventEmitter.prototype, kCapture, {
value: false, value: false,
writable: true, writable: true,
enumerable: false, enumerable: false,
@ -128,7 +142,7 @@ function checkListener(listener) {
validateFunction(listener, "listener"); validateFunction(listener, "listener");
} }
Object.defineProperty(EventEmitter, "defaultMaxListeners", { ObjectDefineProperty(EventEmitter, "defaultMaxListeners", {
enumerable: true, enumerable: true,
get: function () { get: function () {
return defaultMaxListeners; return defaultMaxListeners;
@ -189,9 +203,9 @@ export function setMaxListeners(
EventEmitter.init = function (opts) { EventEmitter.init = function (opts) {
if ( if (
this._events === undefined || this._events === undefined ||
this._events === Object.getPrototypeOf(this)._events this._events === ObjectGetPrototypeOf(this)._events
) { ) {
this._events = Object.create(null); this._events = ObjectCreate(null);
this._eventsCount = 0; this._eventsCount = 0;
} }
@ -218,7 +232,7 @@ function addCatch(that, promise, type, args) {
const then = promise.then; const then = promise.then;
if (typeof then === "function") { if (typeof then === "function") {
then.call(promise, undefined, function (err) { FunctionPrototypeCall(then, promise, undefined, function (err) {
// The callback is called with nextTick to avoid a follow-up // The callback is called with nextTick to avoid a follow-up
// rejection from this promise. // rejection from this promise.
nextTick(emitUnhandledRejectionOrErr, that, err, type, args); nextTick(emitUnhandledRejectionOrErr, that, err, type, args);
@ -385,8 +399,8 @@ EventEmitter.prototype.emit = function emit(type, ...args) {
if (er instanceof Error) { if (er instanceof Error) {
try { try {
const capture = {}; const capture = {};
Error.captureStackTrace(capture, EventEmitter.prototype.emit); ErrorCaptureStackTrace(capture, EventEmitter.prototype.emit);
// Object.defineProperty(er, kEnhanceStackBeforeInspector, { // ObjectDefineProperty(er, kEnhanceStackBeforeInspector, {
// value: enhanceStackTrace.bind(this, er, capture), // value: enhanceStackTrace.bind(this, er, capture),
// configurable: true // configurable: true
// }); // });
@ -419,7 +433,7 @@ EventEmitter.prototype.emit = function emit(type, ...args) {
} }
if (typeof handler === "function") { if (typeof handler === "function") {
const result = handler.apply(this, args); const result = FunctionPrototypeApply(handler, this, args);
// We check if result is undefined first because that // We check if result is undefined first because that
// is the most common case so we do not pay any perf // is the most common case so we do not pay any perf
@ -431,7 +445,7 @@ EventEmitter.prototype.emit = function emit(type, ...args) {
const len = handler.length; const len = handler.length;
const listeners = arrayClone(handler); const listeners = arrayClone(handler);
for (let i = 0; i < len; ++i) { for (let i = 0; i < len; ++i) {
const result = listeners[i].apply(this, args); const result = FunctionPrototypeApply(listeners[i], this, args);
// We check if result is undefined first because that // We check if result is undefined first because that
// is the most common case so we do not pay any perf // is the most common case so we do not pay any perf
@ -456,7 +470,7 @@ function _addListener(target, type, listener, prepend) {
events = target._events; events = target._events;
if (events === undefined) { if (events === undefined) {
events = target._events = Object.create(null); events = target._events = ObjectCreate(null);
target._eventsCount = 0; target._eventsCount = 0;
} else { } else {
// To avoid recursion in the case that type === "newListener"! Before // To avoid recursion in the case that type === "newListener"! Before
@ -483,9 +497,9 @@ function _addListener(target, type, listener, prepend) {
: [existing, listener]; : [existing, listener];
// If we've already got an array, just append. // If we've already got an array, just append.
} else if (prepend) { } else if (prepend) {
existing.unshift(listener); ArrayPrototypeUnshift(existing, listener);
} else { } else {
existing.push(listener); ArrayPrototypePush(existing, listener);
} }
// Check for listener leak // Check for listener leak
@ -542,9 +556,9 @@ function onceWrapper() {
this.target.removeListener(this.type, this.wrapFn); this.target.removeListener(this.type, this.wrapFn);
this.fired = true; this.fired = true;
if (arguments.length === 0) { if (arguments.length === 0) {
return this.listener.call(this.target); return FunctionPrototypeCall(this.listener, this.target);
} }
return this.listener.apply(this.target, arguments); return FunctionPrototypeApply(this.listener, this.target, arguments);
} }
} }
@ -610,7 +624,7 @@ EventEmitter.prototype.removeListener = function removeListener(
if (list === listener || list.listener === listener) { if (list === listener || list.listener === listener) {
if (--this._eventsCount === 0) { if (--this._eventsCount === 0) {
this._events = Object.create(null); this._events = ObjectCreate(null);
} else { } else {
delete events[type]; delete events[type];
if (events.removeListener) { if (events.removeListener) {
@ -632,7 +646,7 @@ EventEmitter.prototype.removeListener = function removeListener(
} }
if (position === 0) { if (position === 0) {
list.shift(); ArrayPrototypeShift(list);
} else { } else {
spliceOne(list, position); spliceOne(list, position);
} }
@ -667,11 +681,11 @@ EventEmitter.prototype.removeAllListeners = function removeAllListeners(type) {
// Not listening for removeListener, no need to emit // Not listening for removeListener, no need to emit
if (events.removeListener === undefined) { if (events.removeListener === undefined) {
if (arguments.length === 0) { if (arguments.length === 0) {
this._events = Object.create(null); this._events = ObjectCreate(null);
this._eventsCount = 0; this._eventsCount = 0;
} else if (events[type] !== undefined) { } else if (events[type] !== undefined) {
if (--this._eventsCount === 0) { if (--this._eventsCount === 0) {
this._events = Object.create(null); this._events = ObjectCreate(null);
} else { } else {
delete events[type]; delete events[type];
} }
@ -681,12 +695,12 @@ EventEmitter.prototype.removeAllListeners = function removeAllListeners(type) {
// Emit removeListener for all listeners on all events // Emit removeListener for all listeners on all events
if (arguments.length === 0) { if (arguments.length === 0) {
for (const key of Reflect.ownKeys(events)) { for (const key of ReflectOwnKeys(events)) {
if (key === "removeListener") continue; if (key === "removeListener") continue;
this.removeAllListeners(key); this.removeAllListeners(key);
} }
this.removeAllListeners("removeListener"); this.removeAllListeners("removeListener");
this._events = Object.create(null); this._events = ObjectCreate(null);
this._eventsCount = 0; this._eventsCount = 0;
return this; return this;
} }
@ -799,7 +813,7 @@ export function listenerCount(emitter, type) {
if (typeof emitter.listenerCount === "function") { if (typeof emitter.listenerCount === "function") {
return emitter.listenerCount(type); return emitter.listenerCount(type);
} }
return _listenerCount.call(emitter, type); return FunctionPrototypeCall(_listenerCount, emitter, type);
} }
/** /**
@ -808,7 +822,7 @@ export function listenerCount(emitter, type) {
* @returns {any[]} * @returns {any[]}
*/ */
EventEmitter.prototype.eventNames = function eventNames() { EventEmitter.prototype.eventNames = function eventNames() {
return this._eventsCount > 0 ? Reflect.ownKeys(this._events) : []; return this._eventsCount > 0 ? ReflectOwnKeys(this._events) : [];
}; };
function arrayClone(arr) { function arrayClone(arr) {
@ -926,8 +940,8 @@ export async function once(emitter, name, options = kEmptyObject) {
}); });
} }
const AsyncIteratorPrototype = Object.getPrototypeOf( const AsyncIteratorPrototype = ObjectGetPrototypeOf(
Object.getPrototypeOf(async function* () {}).prototype, ObjectGetPrototypeOf(async function* () {}).prototype,
); );
function createIterResult(value, done) { function createIterResult(value, done) {
@ -1006,10 +1020,10 @@ export function on(emitter, event, options = kEmptyObject) {
let error = null; let error = null;
let finished = false; let finished = false;
const iterator = Object.setPrototypeOf({ const iterator = ObjectSetPrototypeOf({
next() { next() {
// First, we consume all unread events // First, we consume all unread events
const value = unconsumedEvents.shift(); const value = ArrayPrototypeShift(unconsumedEvents);
if (value) { if (value) {
return Promise.resolve(createIterResult(value, false)); return Promise.resolve(createIterResult(value, false));
} }
@ -1031,7 +1045,7 @@ export function on(emitter, event, options = kEmptyObject) {
// Wait until an event happens // Wait until an event happens
return new Promise(function (resolve, reject) { return new Promise(function (resolve, reject) {
unconsumedPromises.push({ resolve, reject }); ArrayPrototypePush(unconsumedPromises, { resolve, reject });
}); });
}, },
@ -1077,7 +1091,7 @@ export function on(emitter, event, options = kEmptyObject) {
} }
}, },
[Symbol.asyncIterator]() { [SymbolAsyncIterator]() {
return this; return this;
}, },
}, AsyncIteratorPrototype); }, AsyncIteratorPrototype);
@ -1103,18 +1117,18 @@ export function on(emitter, event, options = kEmptyObject) {
} }
function eventHandler(...args) { function eventHandler(...args) {
const promise = unconsumedPromises.shift(); const promise = ArrayPrototypeShift(unconsumedPromises);
if (promise) { if (promise) {
promise.resolve(createIterResult(args, false)); promise.resolve(createIterResult(args, false));
} else { } else {
unconsumedEvents.push(args); ArrayPrototypePush(unconsumedEvents, args);
} }
} }
function errorHandler(err) { function errorHandler(err) {
finished = true; finished = true;
const toError = unconsumedPromises.shift(); const toError = ArrayPrototypeShift(unconsumedPromises);
if (toError) { if (toError) {
toError.reject(err); toError.reject(err);
@ -1193,8 +1207,12 @@ export class EventEmitterAsyncResource extends EventEmitter {
throw new ERR_INVALID_THIS("EventEmitterAsyncResource"); throw new ERR_INVALID_THIS("EventEmitterAsyncResource");
} }
const { asyncResource } = this; const { asyncResource } = this;
args.unshift(super.emit, this, event); ArrayPrototypeUnshift(args, super.emit, this, event);
return asyncResource.runInAsyncScope.apply(asyncResource, args); return FunctionPrototypeApply(
asyncResource.runInAsyncScope,
asyncResource,
args,
);
} }
/** /**

View file

@ -18,7 +18,7 @@ Deno.test("regression #20441", async () => {
return p; return p;
}); });
ee.on("error", function (_) { ee.on("error", function (_: unknown) {
resolve(); resolve();
}); });
@ -44,3 +44,69 @@ Deno.test("addAbortListener", async () => {
abortController.abort(); abortController.abort();
await promise; await promise;
}); });
Deno.test("EventEmitter works when Object.create is deleted (#29929)", () => {
const ObjectCreate = Object.create;
Object.create = undefined!;
try {
const emitter = new EventEmitter();
let called = false;
emitter.on("foo", () => {
called = true;
});
emitter.emit("foo");
if (!called) throw new Error("Listener was not called");
} finally {
Object.create = ObjectCreate;
}
});
Deno.test("EventEmitter works if Array.prototype.unshift is deleted", () => {
const ArrayPrototypeUnshift = Array.prototype.unshift;
// @ts-ignore -- this is fine for testing purposes
delete Array.prototype.unshift;
try {
const emitter = new EventEmitter();
let called = false;
emitter.on("bar", () => {
called = true;
});
emitter.emit("bar");
if (!called) throw new Error("Listener was not called");
} finally {
Array.prototype.unshift = ArrayPrototypeUnshift;
}
});
Deno.test("EventEmitter works if Array.prototype.push is deleted", () => {
const ArrayPrototypePush = Array.prototype.push;
// @ts-ignore -- this is fine for testing purposes
delete Array.prototype.push;
try {
const emitter = new EventEmitter();
let called = false;
emitter.on("baz", () => {
called = true;
});
emitter.emit("baz");
if (!called) throw new Error("Listener was not called");
} finally {
Array.prototype.push = ArrayPrototypePush;
}
});
Deno.test("EventEmitter works if Object.setPrototypeOf is deleted", () => {
const ObjectSetPrototypeOf = Object.setPrototypeOf;
Object.setPrototypeOf = undefined!;
try {
const emitter = new EventEmitter();
let called = false;
emitter.on("zap", () => {
called = true;
});
emitter.emit("zap");
if (!called) throw new Error("Listener was not called");
} finally {
Object.setPrototypeOf = ObjectSetPrototypeOf;
}
});