Unify error handling

This commit is contained in:
Snarling
2022-08-29 02:41:17 -04:00
parent 5798c4c7d3
commit 572c68738f
23 changed files with 156 additions and 255 deletions
+42 -63
View File
@@ -12,7 +12,7 @@ import { generateNextPid } from "./Netscript/Pid";
import { CONSTANTS } from "./Constants";
import { Interpreter } from "./ThirdParty/JSInterpreter";
import { NetscriptFunctions } from "./NetscriptFunctions";
import { executeJSScript, Node } from "./NetscriptJSEvaluator";
import { compile, Node } from "./NetscriptJSEvaluator";
import { IPort } from "./NetscriptPort";
import { RunningScript } from "./Script/RunningScript";
import { getRamUsageFromRunningScript } from "./Script/RunningScriptHelpers";
@@ -31,7 +31,6 @@ import { roundToTwo } from "./utils/helpers/roundToTwo";
import { parse } from "acorn";
import { simple as walksimple } from "acorn-walk";
import { areFilesEqual } from "./Terminal/DirectoryHelpers";
import { Player } from "./Player";
import { Terminal } from "./Terminal";
import { ScriptArg } from "./Netscript/ScriptArg";
@@ -49,14 +48,26 @@ export function prestigeWorkerScripts(): void {
workerScripts.clear();
}
// JS script promises need a little massaging to have the same guarantees as netscript
// promises. This does said massaging and kicks the script off. It returns a promise
// that resolves or rejects when the corresponding worker script is done.
const startNetscript2Script = (workerScript: WorkerScript): Promise<void> =>
executeJSScript(Player, workerScript.getServer().scripts, workerScript);
async function startNetscript2Script(workerScript: WorkerScript): Promise<void> {
const scripts = workerScript.getServer().scripts;
const script = workerScript.getScript();
if (script === null) throw "workerScript had no associated script. This is a bug.";
const loadedModule = await compile(script, scripts);
workerScript.ramUsage = script.ramUsage;
const ns = workerScript.env.vars;
function startNetscript1Script(workerScript: WorkerScript): Promise<void> {
if (!loadedModule) throw `${script.filename} cannot be run because the script module won't load`;
// TODO: Better error for "unexpected reserved word" when using await in non-async function?
if (typeof loadedModule.main !== "function")
throw `${script.filename} cannot be run because it does not have a main function.`;
if (!ns) throw `${script.filename} cannot be run because the NS object hasn't been constructed properly.`;
await loadedModule.main(ns);
return;
}
async function startNetscript1Script(workerScript: WorkerScript): Promise<void> {
const code = workerScript.code;
let errorToThrow: unknown;
//Process imports
let codeWithImports, codeLineOffset;
@@ -65,10 +76,7 @@ function startNetscript1Script(workerScript: WorkerScript): Promise<void> {
codeWithImports = importProcessingRes.code;
codeLineOffset = importProcessingRes.lineOffset;
} catch (e: unknown) {
dialogBoxCreate(`Error processing Imports in ${workerScript.name} on ${workerScript.hostname}:\n\n${e}`);
workerScript.env.stopFlag = true;
killWorkerScript(workerScript);
return Promise.resolve();
throw `Error processing Imports in ${workerScript.name}@${workerScript.hostname}:\n\n${e}`;
}
interface BasicObject {
@@ -77,20 +85,14 @@ function startNetscript1Script(workerScript: WorkerScript): Promise<void> {
function wrapNS1Layer(int: Interpreter, intLayer: unknown, nsLayer = workerScript.env.vars as BasicObject) {
for (const [name, entry] of Object.entries(nsLayer)) {
if (typeof entry === "function") {
// Async functions need to be wrapped. See JS-Interpreter documentation
const wrapper = async (...args: unknown[]) => {
// This async wrapper is sent a resolver function as an extra arg.
// See JSInterpreter.js:3209
try {
// Sent a resolver function as an extra arg. See createAsyncFunction JSInterpreter.js:3209
const callback = args.pop() as (value: unknown) => void;
const result = await entry(...args.map((arg) => int.pseudoToNative(arg)));
return callback(int.nativeToPseudo(result));
} catch (e: unknown) {
// NS1 interpreter doesn't cleanly handle throwing. Need to show dialog here.
errorDialog(e, `RUNTIME ERROR:\n${workerScript.name}@${workerScript.hostname}\n\n`);
workerScript.env.stopFlag = true;
killWorkerScript(workerScript);
return;
errorToThrow = e;
}
};
int.setProperty(intLayer, name, int.createAsyncFunction(wrapper));
@@ -109,31 +111,16 @@ function startNetscript1Script(workerScript: WorkerScript): Promise<void> {
try {
interpreter = new Interpreter(codeWithImports, wrapNS1Layer, codeLineOffset);
} catch (e: unknown) {
dialogBoxCreate(`Syntax ERROR in ${workerScript.name} on ${workerScript.hostname}:\n\n${String(e)}`);
workerScript.env.stopFlag = true;
killWorkerScript(workerScript);
return Promise.resolve();
throw `Syntax ERROR in ${workerScript.name}@${workerScript.hostname}:\n\n${String(e)}`;
}
return new Promise((resolve) => {
function runInterpreter(): void {
if (workerScript.env.stopFlag) resolve();
let more = true;
let i = 0;
while (i < 3 && more) {
more = more && interpreter.step();
i++;
}
if (more) {
setTimeout(runInterpreter, Settings.CodeInstructionRunTime);
} else {
resolve();
}
}
runInterpreter();
});
let more = true;
while (more) {
if (errorToThrow) throw errorToThrow;
if (workerScript.env.stopFlag) return;
for (let i = 0; more && i < 3; i++) more = interpreter.step();
if (more) await new Promise((r) => setTimeout(r, Settings.CodeInstructionRunTime));
}
}
/* Since the JS Interpreter used for Netscript 1.0 only supports ES5, the keyword
@@ -326,7 +313,7 @@ function createAndAddWorkerScript(runningScriptObj: RunningScript, server: BaseS
return false;
}
server.updateRamUsed(roundToTwo(server.ramUsed + ramUsage), Player);
server.updateRamUsed(roundToTwo(server.ramUsed + ramUsage));
// Get the pid
const pid = generateNextPid();
@@ -346,20 +333,10 @@ function createAndAddWorkerScript(runningScriptObj: RunningScript, server: BaseS
workerScripts.set(pid, workerScript);
WorkerScriptStartStopEventEmitter.emit();
// Start the script's execution
let scriptExecution: Promise<void> | null = null; // Script's resulting promise
if (workerScript.name.endsWith(".js")) {
scriptExecution = startNetscript2Script(workerScript);
} else {
scriptExecution = startNetscript1Script(workerScript);
if (!(scriptExecution instanceof Promise)) {
return false;
}
}
// Once the code finishes (either resolved or rejected, doesnt matter), set its
// running status to false
scriptExecution
// Start the script's execution using the correct function for file type
(workerScript.name.endsWith(".js") ? startNetscript2Script : startNetscript1Script)(workerScript)
// Once the code finishes (either resolved or rejected, doesnt matter), set its
// running status to false
.then(function () {
// On natural death, the earnings are transfered to the parent if it still exists.
if (parent && !parent.env.stopFlag) {
@@ -370,13 +347,15 @@ function createAndAddWorkerScript(runningScriptObj: RunningScript, server: BaseS
workerScript.log("", () => "Script finished running");
})
.catch(function (e) {
errorDialog(e, `RUNTIME ERROR\n${workerScript.name}@${workerScript.hostname} (PID - ${workerScript.pid})\n\n`);
let logText = "Script crashed due to an error.";
if (e instanceof ScriptDeath) logText = "Script killed.";
workerScript.log("", () => logText);
let initialText = `ERROR\n${workerScript.name}@${workerScript.hostname} (PID - ${workerScript.pid})\n\n`;
if (e instanceof SyntaxError) e = `SYNTAX ${initialText}${e.message} (sorry we can't be more helpful)`;
else if (e instanceof Error) {
e = `RUNTIME ${initialText}${e.message}${e.stack ? `\nstack:\n${e.stack.toString()}` : ""}`;
}
errorDialog(e, typeof e === "string" && e.includes(initialText) ? "" : initialText);
workerScript.log("", () => (e instanceof ScriptDeath ? "Script killed." : "Script crashed due to an error."));
killWorkerScript(workerScript);
});
return true;
}