-
Notifications
You must be signed in to change notification settings - Fork 330
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Potential arguments leakage? #39
Comments
Another point of interest is: https://github.com/spoike/refluxjs/blob/ff67e1d996f44cf87e38d45a855f3b4291572d01/src/createAction.js#L17 |
Thanks for the heads up @dashed Only skimmed through the issues and it is true that the emitters are "hot" code, depending on how often they are really emitted. So it might be a problem in some instances. We should probably add some benchmark test as a grunt task, a lot like what the node-thunkify patch talks about. |
I never liked how I think we should do something like the following in // /src/createAction.js
// ...
// uses Function.prototype.bind() that will need to be shimmed for IE8
functor = function() {
var args = arguments;
_.nextTick(function() {
functor.preEmit.apply(functor, args);
if (functor.shouldEmit.apply(functor, args)) {
action.emit.bind(action, eventLabel).apply(action, args);
}
});
};
// explicit use of Array.prototype.slice()
functor = function() {
var args = arguments;
_.nextTick(function() {
functor.preEmit.apply(functor, args);
if (functor.shouldEmit.apply(functor, args)) {
var _args = Array.prototype.slice.call(args, 0);
_args.unshift(eventLabel);
action.emit.apply(action, _args);
}
});
};
functor.listen = function(callback, bindContext) {
var eventHandler = function() {
callback.apply(bindContext, arguments);
};
action.addListener(eventLabel, eventHandler);
return function() {
action.removeListener(eventLabel, eventHandler);
};
};
// ... |
Another reference with regards of converting I'm not intimately familiar with V8, but Crankshaft is mentioned; and a new compiler called Turbofan is mentioned in the wild: https://news.ycombinator.com/item?id=8117091 Here's hoping this type of use case becomes a non-issue in Turbofan =P. But alas, we should take care of it now. |
Nice catch @dashed. The sad thing about this is that we cannot put this logic (arguments to js array) into Is there some kind of Grunt task (or browserify transformer) that we can use to turn the |
The npm package isn't using the browserified build but is instead using the source directly. As far as I've read about it seems to be an issue in V8 so it needs to work both on the browser (Chrome) and server (Node). I don't know if we should point towards |
I don't think it is a good idea to point to |
Made a benchmark: "use strict";
/**
* Deps:
* npm install benchmark EventEmitter3
*/
var EventEmitter = require('EventEmitter3').EventEmitter;
var bus = new EventEmitter();
function nextTick(callback) {
// setTimeout(callback, 0);
callback();
};
function getOptmizationStatus(fn) {
switch(%GetOptimizationStatus(fn)) {
case 1: return("Function is optimized"); break;
case 2: return("Function is not optimized"); break;
case 3: return("Function is always optimized"); break;
case 4: return("Function is never optimized"); break;
case 6: return("Function is maybe deoptimized"); break;
}
};
/**
*
* Test functions
*/
var slice = [].slice;
var preEmit = function() {};
var shouldEmit = function() {return true};
function original() {
var args = arguments;
nextTick(function(){
preEmit.apply(null, args);
if (shouldEmit.apply(null, args)) {
bus.emit('action', args);
}
});
};
function originalBind() {
var args = arguments;
nextTick(function(){
preEmit.apply(null, args);
if (shouldEmit.apply(null, args)) {
bus.emit.bind(bus, 'action').apply(bus, args);
}
});
};
var emit = bus.emit.bind(bus, 'action');
function originalApply() {
var args = arguments;
nextTick(function(){
preEmit.apply(null, args);
if (shouldEmit.apply(null, args)) {
emit.apply(emit, args);
}
});
};
function sliceArr() {
var args = [].slice.call(arguments);
args.unshift('action');
nextTick(function(){
preEmit.apply(null, args);
if (shouldEmit.apply(null, args)) {
bus.emit.apply(bus, args);
}
});
};
function arrayApply() {
var args = arguments.length === 1 ? [arguments[0]] : Array.apply(null, arguments);
args.unshift('action');
nextTick(function(){
preEmit.apply(null, args);
if (shouldEmit.apply(null, args)) {
bus.emit.apply(bus, args);
}
});
};
function manualMap() {
var l = arguments.length
var args = new Array(l)
for (var i = 0; i < l; i ++) args[i] = arguments[i]
args.unshift('action');
nextTick(function(){
preEmit.apply(null, args);
if (shouldEmit.apply(null, args)) {
bus.emit.apply(bus, args);
}
});
};
/**
* Set up test cases
*/
var tests = [];
tests.push(original);
tests.push(originalBind);
tests.push(originalApply);
tests.push(sliceArr);
tests.push(arrayApply);
tests.push(manualMap);
//
// Optimize check
//
tests.forEach(function(fn) {
fn();
fn();
fn();
%OptimizeFunctionOnNextCall(fn);
fn();
console.log("STATUS OF " + fn.name + ": " + getOptmizationStatus(fn));
});
console.log();
// benchmark
var Benchmark = require('benchmark');
var suite = new Benchmark.Suite;
tests.forEach(function(fn) {
suite.add(fn.name, function() {
fn(Math.random());
fn(Math.random(), fn(Math.random()));
});
});
suite
.on('cycle', function(event) {
console.log(String(event.target));
})
.on('complete', function() {
console.log('\nFastest is ' + this.filter('fastest').pluck('name'));
})
.run();
/*
$ node --allow-natives-syntax bench.js
STATUS OF original: Function is not optimized
STATUS OF originalBind: Function is not optimized
STATUS OF originalApply: Function is not optimized
STATUS OF sliceArr: Function is not optimized
STATUS OF arrayApply: Function is optimized
STATUS OF manualMap: Function is optimized
original x 587,560 ops/sec ±3.91% (86 runs sampled)
originalBind x 21,084 ops/sec ±7.65% (68 runs sampled)
originalApply x 266,257 ops/sec ±2.43% (95 runs sampled)
sliceArr x 123,433 ops/sec ±2.48% (93 runs sampled)
arrayApply x 407,646 ops/sec ±2.08% (93 runs sampled)
manualMap x 422,805 ops/sec ±2.34% (92 runs sampled)
Fastest is original
*/ |
Thanks @dashed, nice stuff. My data for comparison:
|
Even though the original is the fastet, it might not be the best solution. In the current situation we are passing an arguments object to the event bus. Users of reflux therefore need to know that they are not getting an Array and thus cannot use
Results:
|
Just created a JSPerf test as we should also take Firefox and Internet Explorer into account: |
Woah I "optimized" everything! 😆 https://gist.github.com/Dashed/77ee677c46879d2919eb#file-oldbench-js |
My previous benchmark is meant to show that
@bripkens I recommend Node 0.10.28: STATUS OF original: Function is not optimized
STATUS OF originalBind: Function is not optimized
STATUS OF originalApply: Function is optimized
STATUS OF sliceArr: Function is not optimized
STATUS OF arrayApply: Function is optimized
STATUS OF manualMap: Function is optimized
STATUS OF manualMap2: Function is optimized
STATUS OF utilToArray: Function is optimized
STATUS OF utilToArray2: Function is optimized
STATUS OF utilToArray3: Function is optimized
STATUS OF utilToArray4: Function is optimized
original x 930,629 ops/sec ±4.61% (90 runs sampled)
originalBind x 42,216 ops/sec ±3.26% (88 runs sampled)
originalApply x 602,979 ops/sec ±3.59% (90 runs sampled)
sliceArr x 214,171 ops/sec ±0.97% (96 runs sampled)
arrayApply x 194,816 ops/sec ±2.49% (89 runs sampled)
manualMap x 132,307 ops/sec ±3.35% (88 runs sampled)
manualMap2 x 1,179,469 ops/sec ±2.05% (93 runs sampled)
utilToArray x 133,083 ops/sec ±3.77% (87 runs sampled)
utilToArray2 x 1,076,526 ops/sec ±2.10% (93 runs sampled)
utilToArray3 x 183,454 ops/sec ±21.06% (74 runs sampled)
utilToArray4 x 1,051,558 ops/sec ±0.88% (93 runs sampled)
Fastest is manualMap2 |
For reference, my implementation of It creates an array copy of arguments but with a number of empty element spaces to the right of the array. This number of empty element spaces is provided via the this context, which is by default 0 if it's not a Number. The callee is supposed to fill the empty spaces of the returned array. function toArray4() {
var n = !(this instanceof Number) ? 0 : +(this);
var m = arguments.length + n;
var args = new Array(m);
while (m-- > n) {
args[m] = arguments[m - n];
}
return args;
}
function utilToArray4() {
preEmit.apply(null, arguments);
if (shouldEmit.apply(null, arguments)) {
var args = toArray4.apply(1, arguments);
args[0] = 'action';
bus.emit.apply(bus, args);
}
}; |
I'm busy this weekend so I'm unable test this out and comment in a coherent matter but --> 👍 nice work y'guys, I appreciate it. |
I don't think this is really a problem anymore after looking at master. |
Saw this in another issue: pixijs/pixijs#853
Other sources on this:
May be relevant in reflux where
arguments
is potentially leaked. AFAIK, this is something for V8.Points of interest:
Leak at line 44, which is caught in line 34. We shouldn't trust what happens in
EventEmitter.emit
.line 58: classic pattern. Use patch mentioned here: Make this an optimizable function tj/node-thunkify#12
/cc @spoike @bripkens
The text was updated successfully, but these errors were encountered: