Skip to content
This repository has been archived by the owner on Jan 1, 2025. It is now read-only.

Implement custom error handling #230

Closed
wants to merge 2 commits into from
Closed
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 59 additions & 2 deletions lib/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,8 @@ class VM extends EventEmitter {
sandbox: options.sandbox,
compiler: options.compiler || 'javascript',
eval: options.eval === false ? false : true,
wasm: options.wasm === false ? false : true
wasm: options.wasm === false ? false : true,
prettyErrors: options.prettyErrors === false ? false : true
};

const host = {
Expand Down Expand Up @@ -215,12 +216,68 @@ class VM extends EventEmitter {
const script = code instanceof VMScript ? code : new VMScript(code);
script.compile();

let errorHandler;
if (this.options.prettyErrors) {
errorHandler = function(e) {
// Handler for non-Error values
if ((typeof e != "object") || !("stack" in e)) {
// Do not remove the comment below! It is what users will see.
throw new Error(e); // A vm2 script threw a value, and we couldn't fetch a stack trace
CapacitorSet marked this conversation as resolved.
Show resolved Hide resolved
}
// Handler for non-vm2 errors
if (!e.stack.includes("/node_modules/vm2/")) {
console.log(e);
return;
}
const lines = e.stack.split("\n")
const header = lines[0]; // eg. "TypeError: cannot..."
const topFrame = lines[1];
const [_, filename, line, position] = topFrame.match(/^ +at .+ \((.+):(\d+):(\d+)\)/);
const offendingLine = script.code.split("\n")[line-1];
let positionString = "";
// Tabs count as 1 for `position` purposes, but they take >1 column on most terminals.
// This piece of code fixes that.
const tabs = offendingLine.match(/^(\t*)/)[1];
for (let i = 0; i < tabs.length; i++)
positionString += "\t";
for (let i = tabs.length; i < position; i++)
positionString += " ";
positionString += "^";
const oldStack = lines.slice(1);
const newStack = [];
for (const line of oldStack) {
// Discard frames from internal code
if (line.includes("internal/modules/cjs/loader.js"))
continue;
if (line.includes("/node_modules/vm2/"))
continue;
if (line.includes("at Script.runInContext"))
continue;
// Replace the default filename with the user-provided one
const patchedLine = line.replace("(vm.js:", "(" + script.filename + ":");
newStack.push(patchedLine);
}
console.log(filename + ":" + line);
console.log(offendingLine);
console.log(positionString);
console.log("");
console.log(header);
console.log(newStack.join("\n"));
}
// Note the ".once": the handler will be removed automatically after an exception occurs
// However, we must still remove it manually if no exception occurs
process.once("uncaughtException", errorHandler);
}

try {
return this._internal.Decontextify.value(script._compiled.runInContext(this._context, {
const ret = this._internal.Decontextify.value(script._compiled.runInContext(this._context, {
filename: script.filename,
displayErrors: false,
timeout: this.options.timeout
}));
if (this.options.prettyErrors)
process.removeListener("uncaughtException", errorHandler);
return ret;
} catch (e) {
throw this._internal.Decontextify.value(e);
}
Expand Down