Skip to content
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

Handle errors thrown in async functions #421

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion lib/nodevm.js
Expand Up @@ -59,7 +59,8 @@ const HOST = Object.freeze({
setImmediate,
clearTimeout,
clearInterval,
clearImmediate
clearImmediate,
Promise
});

/**
Expand Down
45 changes: 42 additions & 3 deletions lib/setup-node-sandbox.js
Expand Up @@ -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);

Expand All @@ -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);

Expand All @@ -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);

Expand Down Expand Up @@ -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) {
Expand Down
29 changes: 29 additions & 0 deletions test/nodevm.js
Expand Up @@ -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({
Expand Down