Browse Source

events: Don't crash on events named __proto__

This prefixes all event names internally with 'ev'.
v0.9.7-release
isaacs 12 years ago
parent
commit
b48e303af0
  1. 67
      lib/events.js
  2. 14
      test/simple/test-event-emitter-check-listener-leaks.js
  3. 12
      test/simple/test-event-emitter-listeners-side-effects.js
  4. 37
      test/simple/test-event-emitter-prototype-property.js

67
lib/events.js

@ -53,8 +53,8 @@ var PROCESS;
EventEmitter.prototype.emit = function(type) { EventEmitter.prototype.emit = function(type) {
// If there is no 'error' event listener then throw. // If there is no 'error' event listener then throw.
if (type === 'error') { if (type === 'error') {
if (!this._events || !this._events.error || if (!this._events || !this._events.everror ||
(isArray(this._events.error) && !this._events.error.length)) (isArray(this._events.everror) && !this._events.everror.length))
{ {
if (this.domain) { if (this.domain) {
var er = arguments[1]; var er = arguments[1];
@ -75,7 +75,8 @@ EventEmitter.prototype.emit = function(type) {
} }
if (!this._events) return false; if (!this._events) return false;
var handler = this._events[type]; var evtype = 'ev' + type;
var handler = this._events[evtype];
if (!handler) return false; if (!handler) return false;
if (typeof handler == 'function') { if (typeof handler == 'function') {
@ -142,36 +143,37 @@ EventEmitter.prototype.addListener = function(type, listener) {
// To avoid recursion in the case that type == "newListener"! Before // To avoid recursion in the case that type == "newListener"! Before
// adding it to the listeners, first emit "newListener". // adding it to the listeners, first emit "newListener".
if (this._events.newListener) { if (this._events.evnewListener) {
this.emit('newListener', type, typeof listener.listener === 'function' ? this.emit('newListener', type, typeof listener.listener === 'function' ?
listener.listener : listener); listener.listener : listener);
} }
if (!this._events[type]) { var evtype = 'ev' + type;
if (!this._events[evtype]) {
// Optimize the case of one listener. Don't need the extra array object. // Optimize the case of one listener. Don't need the extra array object.
this._events[type] = listener; this._events[evtype] = listener;
} else if (isArray(this._events[type])) { } else if (isArray(this._events[evtype])) {
// If we've already got an array, just append. // If we've already got an array, just append.
this._events[type].push(listener); this._events[evtype].push(listener);
} else { } else {
// Adding the second element, need to change to array. // Adding the second element, need to change to array.
this._events[type] = [this._events[type], listener]; this._events[evtype] = [this._events[evtype], listener];
} }
// Check for listener leak // Check for listener leak
if (isArray(this._events[type]) && !this._events[type].warned) { if (isArray(this._events[evtype]) && !this._events[evtype].warned) {
var m; var m;
m = this._maxListeners; m = this._maxListeners;
if (m && m > 0 && this._events[type].length > m) { if (m && m > 0 && this._events[evtype].length > m) {
this._events[type].warned = true; this._events[evtype].warned = true;
console.error('(node) warning: possible EventEmitter memory ' + console.error('(node) warning: possible EventEmitter memory ' +
'leak detected. %d listeners added. ' + 'leak detected. %d listeners added. ' +
'Use emitter.setMaxListeners() to increase limit.', 'Use emitter.setMaxListeners() to increase limit.',
this._events[type].length); this._events[evtype].length);
console.trace(); console.trace();
} }
} }
@ -204,10 +206,11 @@ EventEmitter.prototype.removeListener = function(type, listener) {
throw new Error('removeListener only takes instances of Function'); throw new Error('removeListener only takes instances of Function');
} }
// does not use listeners(), so no side effect of creating _events[type] var evtype = 'ev' + type;
if (!this._events || !this._events[type]) return this; // does not use listeners(), so no side effect of creating _events[evtype]
if (!this._events || !this._events[evtype]) return this;
var list = this._events[type]; var list = this._events[evtype];
if (isArray(list)) { if (isArray(list)) {
var position = -1; var position = -1;
@ -223,17 +226,17 @@ EventEmitter.prototype.removeListener = function(type, listener) {
if (position < 0) return this; if (position < 0) return this;
list.splice(position, 1); list.splice(position, 1);
if (list.length == 0) if (list.length == 0)
this._events[type] = null; this._events[evtype] = null;
if (this._events.removeListener) { if (this._events.evremoveListener) {
this.emit('removeListener', type, listener); this.emit('removeListener', type, listener);
} }
} else if (list === listener || } else if (list === listener ||
(list.listener && list.listener === listener)) (list.listener && list.listener === listener))
{ {
this._events[type] = null; this._events[evtype] = null;
if (this._events.removeListener) { if (this._events.evremoveListener) {
this.emit('removeListener', type, listener); this.emit('removeListener', type, listener);
} }
} }
@ -245,11 +248,11 @@ EventEmitter.prototype.removeAllListeners = function(type) {
if (!this._events) return this; if (!this._events) return this;
// fast path // fast path
if (!this._events.removeListener) { if (!this._events.evremoveListener) {
if (arguments.length === 0) { if (arguments.length === 0) {
this._events = {}; this._events = {};
} else if (type && this._events && this._events[type]) { } else if (type && this._events && this._events['ev' + type]) {
this._events[type] = null; this._events['ev' + type] = null;
} }
return this; return this;
} }
@ -257,15 +260,16 @@ EventEmitter.prototype.removeAllListeners = function(type) {
// slow(ish) path, emit 'removeListener' events for all removals // slow(ish) path, emit 'removeListener' events for all removals
if (arguments.length === 0) { if (arguments.length === 0) {
for (var key in this._events) { for (var key in this._events) {
if (key === 'removeListener') continue; if (key === 'evremoveListener') continue;
this.removeAllListeners(key); this.removeAllListeners(key.slice(2));
} }
this.removeAllListeners('removeListener'); this.removeAllListeners('removeListener');
this._events = {}; this._events = {};
return this; return this;
} }
var listeners = this._events[type]; var evtype = 'ev' + type;
var listeners = this._events[evtype];
if (isArray(listeners)) { if (isArray(listeners)) {
while (listeners.length) { while (listeners.length) {
// LIFO order // LIFO order
@ -274,15 +278,16 @@ EventEmitter.prototype.removeAllListeners = function(type) {
} else if (listeners) { } else if (listeners) {
this.removeListener(type, listeners); this.removeListener(type, listeners);
} }
this._events[type] = null; this._events[evtype] = null;
return this; return this;
}; };
EventEmitter.prototype.listeners = function(type) { EventEmitter.prototype.listeners = function(type) {
if (!this._events || !this._events[type]) return []; var evtype = 'ev' + type;
if (!isArray(this._events[type])) { if (!this._events || !this._events[evtype]) return [];
return [this._events[type]]; if (!isArray(this._events[evtype])) {
return [this._events[evtype]];
} }
return this._events[type].slice(0); return this._events[evtype].slice(0);
}; };

14
test/simple/test-event-emitter-check-listener-leaks.js

@ -29,30 +29,30 @@ var e = new events.EventEmitter();
for (var i = 0; i < 10; i++) { for (var i = 0; i < 10; i++) {
e.on('default', function() {}); e.on('default', function() {});
} }
assert.ok(!e._events['default'].hasOwnProperty('warned')); assert.ok(!e._events.evdefault.hasOwnProperty('warned'));
e.on('default', function() {}); e.on('default', function() {});
assert.ok(e._events['default'].warned); assert.ok(e._events.evdefault.warned);
// specific // specific
e.setMaxListeners(5); e.setMaxListeners(5);
for (var i = 0; i < 5; i++) { for (var i = 0; i < 5; i++) {
e.on('specific', function() {}); e.on('specific', function() {});
} }
assert.ok(!e._events['specific'].hasOwnProperty('warned')); assert.ok(!e._events.evspecific.hasOwnProperty('warned'));
e.on('specific', function() {}); e.on('specific', function() {});
assert.ok(e._events['specific'].warned); assert.ok(e._events.evspecific.warned);
// only one // only one
e.setMaxListeners(1); e.setMaxListeners(1);
e.on('only one', function() {}); e.on('only one', function() {});
assert.ok(!e._events['only one'].hasOwnProperty('warned')); assert.ok(!e._events['evonly one'].hasOwnProperty('warned'));
e.on('only one', function() {}); e.on('only one', function() {});
assert.ok(e._events['only one'].hasOwnProperty('warned')); assert.ok(e._events['evonly one'].hasOwnProperty('warned'));
// unlimited // unlimited
e.setMaxListeners(0); e.setMaxListeners(0);
for (var i = 0; i < 1000; i++) { for (var i = 0; i < 1000; i++) {
e.on('unlimited', function() {}); e.on('unlimited', function() {});
} }
assert.ok(!e._events['unlimited'].hasOwnProperty('warned')); assert.ok(!e._events['evunlimited'].hasOwnProperty('warned'));

12
test/simple/test-event-emitter-listeners-side-effects.js

@ -37,21 +37,21 @@ assert.equal(e._events, null);
e.on('foo', assert.fail); e.on('foo', assert.fail);
fl = e.listeners('foo'); fl = e.listeners('foo');
assert(e._events.foo === assert.fail); assert(e._events.evfoo === assert.fail);
assert(Array.isArray(fl)); assert(Array.isArray(fl));
assert(fl.length === 1); 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')); assert(!e._events.hasOwnProperty('evbar'));
e.on('foo', assert.ok); e.on('foo', assert.ok);
fl = e.listeners('foo'); fl = e.listeners('foo');
assert(Array.isArray(e._events.foo)); assert(Array.isArray(e._events.evfoo));
assert(e._events.foo.length === 2); assert(e._events.evfoo.length === 2);
assert(e._events.foo[0] === assert.fail); assert(e._events.evfoo[0] === assert.fail);
assert(e._events.foo[1] === assert.ok); assert(e._events.evfoo[1] === assert.ok);
assert(Array.isArray(fl)); assert(Array.isArray(fl));
assert(fl.length === 2); assert(fl.length === 2);

37
test/simple/test-event-emitter-prototype-property.js

@ -0,0 +1,37 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
var common = require('../common');
var assert = require('assert');
var events = require('events');
var e = new events.EventEmitter();
var emited = false;
e.on('__proto__', function() {
emited = true;
});
e.emit('__proto__');
process.on('exit', function() {
assert.equal(true, emited);
});
Loading…
Cancel
Save