Browse Source

events: don't inherit from Object.prototype

This commit safely allows event names that are named the same as
properties that are ordinarily inherited from Object.prototype such
as __proto__.

Fixes: https://github.com/nodejs/node/issues/728
PR-URL: https://github.com/nodejs/node/pull/6092
Reviewed-By: Evan Lucas <evanlucas@me.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
process-exit-stdio-flushing
Brian White 9 years ago
parent
commit
e38bade828
No known key found for this signature in database GPG Key ID: 606D7358F94DA209
  1. 20
      lib/events.js
  2. 4
      src/node.cc
  3. 12
      test/known_issues/test-events-known-properties.js
  4. 1
      test/parallel/test-event-emitter-listeners-side-effects.js
  5. 36
      test/parallel/test-event-emitter-special-event-names.js

20
lib/events.js

@ -2,6 +2,12 @@
var domain; var domain;
// This constructor is used to store event handlers. Instantiating this is
// faster than explicitly calling `Object.create(null)` to get a "clean" empty
// object (tested with v8 v4.9).
function EventHandlers() {}
EventHandlers.prototype = Object.create(null);
function EventEmitter() { function EventEmitter() {
EventEmitter.init.call(this); EventEmitter.init.call(this);
} }
@ -44,7 +50,7 @@ EventEmitter.init = function() {
} }
if (!this._events || this._events === Object.getPrototypeOf(this)._events) { if (!this._events || this._events === Object.getPrototypeOf(this)._events) {
this._events = {}; this._events = new EventHandlers();
this._eventsCount = 0; this._eventsCount = 0;
} }
@ -211,7 +217,7 @@ EventEmitter.prototype.addListener = function addListener(type, listener) {
events = this._events; events = this._events;
if (!events) { if (!events) {
events = this._events = {}; events = this._events = new EventHandlers();
this._eventsCount = 0; this._eventsCount = 0;
} else { } else {
// To avoid recursion in the case that type === "newListener"! Before // To avoid recursion in the case that type === "newListener"! Before
@ -296,7 +302,7 @@ EventEmitter.prototype.removeListener =
if (list === listener || (list.listener && list.listener === listener)) { if (list === listener || (list.listener && list.listener === listener)) {
if (--this._eventsCount === 0) if (--this._eventsCount === 0)
this._events = {}; this._events = new EventHandlers();
else { else {
delete events[type]; delete events[type];
if (events.removeListener) if (events.removeListener)
@ -319,7 +325,7 @@ EventEmitter.prototype.removeListener =
if (list.length === 1) { if (list.length === 1) {
list[0] = undefined; list[0] = undefined;
if (--this._eventsCount === 0) { if (--this._eventsCount === 0) {
this._events = {}; this._events = new EventHandlers();
return this; return this;
} else { } else {
delete events[type]; delete events[type];
@ -346,11 +352,11 @@ EventEmitter.prototype.removeAllListeners =
// not listening for removeListener, no need to emit // not listening for removeListener, no need to emit
if (!events.removeListener) { if (!events.removeListener) {
if (arguments.length === 0) { if (arguments.length === 0) {
this._events = {}; this._events = new EventHandlers();
this._eventsCount = 0; this._eventsCount = 0;
} else if (events[type]) { } else if (events[type]) {
if (--this._eventsCount === 0) if (--this._eventsCount === 0)
this._events = {}; this._events = new EventHandlers();
else else
delete events[type]; delete events[type];
} }
@ -366,7 +372,7 @@ EventEmitter.prototype.removeAllListeners =
this.removeAllListeners(key); this.removeAllListeners(key);
} }
this.removeAllListeners('removeListener'); this.removeAllListeners('removeListener');
this._events = {}; this._events = new EventHandlers();
this._eventsCount = 0; this._eventsCount = 0;
return this; return this;
} }

4
src/node.cc

@ -3228,7 +3228,9 @@ void SetupProcessObject(Environment* env,
env->SetMethod(process, "_setupDomainUse", SetupDomainUse); env->SetMethod(process, "_setupDomainUse", SetupDomainUse);
// pre-set _events object for faster emit checks // pre-set _events object for faster emit checks
process->Set(env->events_string(), Object::New(env->isolate())); Local<Object> events_obj = Object::New(env->isolate());
events_obj->SetPrototype(env->context(), Null(env->isolate()));
process->Set(env->events_string(), events_obj);
} }

12
test/known_issues/test-events-known-properties.js

@ -1,12 +0,0 @@
'use strict';
// Refs: https://github.com/nodejs/node/issues/728
const common = require('../common');
const assert = require('assert');
const EventEmitter = require('events');
const ee = new EventEmitter();
ee.on('__proto__', common.mustCall((data) => {
assert.strictEqual(data, 42);
}));
ee.emit('__proto__', 42);

1
test/parallel/test-event-emitter-listeners-side-effects.js

@ -21,7 +21,6 @@ assert(fl.length === 1);
assert(fl[0] === assert.fail); assert(fl[0] === assert.fail);
e.listeners('bar'); e.listeners('bar');
assert(!e._events.hasOwnProperty('bar'));
e.on('foo', assert.ok); e.on('foo', assert.ok);
fl = e.listeners('foo'); fl = e.listeners('foo');

36
test/parallel/test-event-emitter-special-event-names.js

@ -0,0 +1,36 @@
'use strict';
const common = require('../common');
const EventEmitter = require('events');
const assert = require('assert');
const ee = new EventEmitter();
const handler = () => {};
assert.strictEqual(ee._events.hasOwnProperty, undefined);
assert.strictEqual(ee._events.toString, undefined);
ee.on('__proto__', handler);
ee.on('__defineGetter__', handler);
ee.on('toString', handler);
assert.deepStrictEqual(ee.eventNames(), [
'__proto__',
'__defineGetter__',
'toString'
]);
assert.deepStrictEqual(ee.listeners('__proto__'), [handler]);
assert.deepStrictEqual(ee.listeners('__defineGetter__'), [handler]);
assert.deepStrictEqual(ee.listeners('toString'), [handler]);
ee.on('__proto__', common.mustCall(function(val) {
assert.strictEqual(val, 1);
}));
ee.emit('__proto__', 1);
process.on('__proto__', common.mustCall(function(val) {
assert.strictEqual(val, 1);
}));
process.emit('__proto__', 1);
Loading…
Cancel
Save