diff --git a/lib/nodevm.js b/lib/nodevm.js index b1236ee..81891b1 100644 --- a/lib/nodevm.js +++ b/lib/nodevm.js @@ -59,7 +59,8 @@ const HOST = Object.freeze({ setImmediate, clearTimeout, clearInterval, - clearImmediate + clearImmediate, + Promise }); /** diff --git a/lib/setup-node-sandbox.js b/lib/setup-node-sandbox.js index aecf2f6..e0cdf2c 100644 --- a/lib/setup-node-sandbox.js +++ b/lib/setup-node-sandbox.js @@ -208,7 +208,11 @@ global.setTimeout = function setTimeout(callback, delay, ...args) { if (typeof callback !== 'function') throw new LocalTypeError('"callback" argument must be a function'); const obj = new Timeout(callback, args); const cb = () => { - localReflectApply(callback, null, args); + try { + localReflectApply(callback, null, args); + } catch (err) { + vm.emit('uncaughtException', err); + } }; const tmr = host.setTimeout(cb, delay); @@ -227,7 +231,11 @@ global.setInterval = function setInterval(callback, interval, ...args) { if (typeof callback !== 'function') throw new LocalTypeError('"callback" argument must be a function'); const obj = new Interval(); const cb = () => { - localReflectApply(callback, null, args); + try { + localReflectApply(callback, null, args); + } catch (err) { + vm.emit('uncaughtException', err); + } }; const tmr = host.setInterval(cb, interval); @@ -246,7 +254,11 @@ global.setImmediate = function setImmediate(callback, ...args) { if (typeof callback !== 'function') throw new LocalTypeError('"callback" argument must be a function'); const obj = new Immediate(); const cb = () => { - localReflectApply(callback, null, args); + try { + localReflectApply(callback, null, args); + } catch (err) { + vm.emit('uncaughtException', err); + } }; const tmr = host.setImmediate(cb); @@ -275,6 +287,33 @@ global.clearImmediate = function clearImmediate(immediate) { clearTimer(immediate); }; +global.Promise = function _Promise(fn) { + const wrapFn = (resolve, reject) => { + const _reject = reason => { + try { + reject(reason); + } catch (err) { + vm.emit('unhandledRejection', err); + } + }; + try { + fn(resolve, _reject); + } catch (err) { + vm.emit('unhandledRejection', err); + } + }; + const p = new host.Promise(wrapFn); + // prevents unhandledRejection errors from propagating, + // but we don't know if it is ever properly handled or not + p.catch(err => vm.emit('promiseRejected', err, p)); + return p; +}; +global.Promise.prototype = host.Promise.prototype; +global.Promise.all = host.Promise.all; +global.Promise.race = host.Promise.race; +global.Promise.reject = host.Promise.reject; +global.Promise.resolve = host.Promise.resolve; + const localProcess = host.process; function vmEmitArgs(event, args) { diff --git a/test/nodevm.js b/test/nodevm.js index 9d450d2..b038601 100644 --- a/test/nodevm.js +++ b/test/nodevm.js @@ -73,6 +73,35 @@ describe('NodeVM', () => { }); }); +describe('error events', () => { + it('async errors', done => { + const vm = new NodeVM; + vm.on('uncaughtException', err => { + assert.equal(err.message, 'fail'); + done(); + }); + vm.run('setTimeout(function() { throw new Error("fail"); })'); + }); + + it('promise errors', done => { + const vm = new NodeVM; + vm.on('unhandledRejection', err => { + assert.equal(err.message, 'fail'); + done(); + }); + vm.run('new Promise(function() { throw new Error("fail"); })'); + }); + + it('rejected promises', done => { + const vm = new NodeVM; + vm.on('promiseRejected', err => { + assert.equal(err.message, 'fail'); + done(); + }); + vm.run('new Promise(function(resolve, reject) { reject(new Error("fail")); })'); + }); +}); + describe('modules', () => { it('require json', () => { const vm = new NodeVM({