From 7d3773605873201a6f556abd02be46c5dbd34bc0 Mon Sep 17 00:00:00 2001 From: Snarling <84951833+Snarling@users.noreply.github.com> Date: Mon, 5 Sep 2022 09:55:57 -0400 Subject: [PATCH] unified errors --- src/Netscript/NetscriptHelpers.ts | 41 ++++++++++++++++++++++++++----- src/Netscript/killWorkerScript.ts | 25 +++++-------------- src/NetscriptWorker.ts | 15 +++-------- src/UncaughtPromiseHandler.ts | 12 ++++++--- src/ui/React/DialogBox.tsx | 18 -------------- 5 files changed, 52 insertions(+), 59 deletions(-) diff --git a/src/Netscript/NetscriptHelpers.ts b/src/Netscript/NetscriptHelpers.ts index 6b06926e2..909c2d60d 100644 --- a/src/Netscript/NetscriptHelpers.ts +++ b/src/Netscript/NetscriptHelpers.ts @@ -33,6 +33,7 @@ import { RunningScript as IRunningScript } from "../ScriptEditor/NetscriptDefini import { arrayToString } from "../utils/helpers/arrayToString"; import { HacknetServer } from "../Hacknet/HacknetServer"; import { BaseServer } from "../Server/BaseServer"; +import { dialogBoxCreate } from "../ui/React/DialogBox"; export const helpers = { string, @@ -131,11 +132,13 @@ function argsToString(args: unknown[]): string { } /** Creates an error message string containing hostname, scriptname, and the error message msg */ -function makeBasicErrorMsg(workerScript: WorkerScript, msg: string, type = "RUNTIME"): string { - for (const scriptUrl of workerScript.scriptRef.dependencies) { - msg = msg.replace(new RegExp(scriptUrl.url, "g"), scriptUrl.filename); +function makeBasicErrorMsg(ws: WorkerScript | ScriptDeath, msg: string, type = "RUNTIME"): string { + if (ws instanceof WorkerScript) { + for (const scriptUrl of ws.scriptRef.dependencies) { + msg = msg.replace(new RegExp(scriptUrl.url, "g"), scriptUrl.filename); + } } - return `${type} ERROR\n${workerScript.name}@${workerScript.hostname} (PID - ${workerScript.pid})\n\n${msg}`; + return `${type} ERROR\n${ws.name}@${ws.hostname} (PID - ${ws.pid})\n\n${msg}`; } /** Creates an error message string with a stack trace. */ @@ -250,8 +253,7 @@ function checkEnvFlags(ctx: NetscriptContext): void { throw new ScriptDeath(ws); } if (ws.env.runningFn && ctx.function !== "asleep") { - //This one has no error message so it will not create a dialog - if (ws.delayReject) ws.delayReject(new ScriptDeath(ws)); + ws.delayReject?.(new ScriptDeath(ws)); ws.env.stopFlag = true; log(ctx, () => "Failed to run due to failed concurrency check."); throw makeRuntimeErrorMsg( @@ -692,3 +694,30 @@ function failOnHacknetServer(ctx: NetscriptContext, server: BaseServer, callingF return false; } } + +/** Generate an error dialog when workerscript is known */ +export function handleUnknownError(e: unknown, ws: WorkerScript | ScriptDeath | null = null, initialText = "") { + if (e instanceof ScriptDeath) { + //No dialog for an empty ScriptDeath + if (e.errorMessage === "") return; + if (!ws) { + ws = e; + e = ws.errorMessage; + } + } + if (ws && typeof e === "string") { + const headerText = makeBasicErrorMsg(ws, "", ""); + if (!e.includes(headerText)) e = makeBasicErrorMsg(ws, e); + } else if (e instanceof SyntaxError) { + const msg = `${e.message} (sorry we can't be more helpful)`; + e = ws ? makeBasicErrorMsg(ws, msg, "SYNTAX") : `SYNTAX ERROR:\n\n${msg}`; + } else if (e instanceof Error) { + const msg = `${e.message}${e.stack ? `\nstack:\n${e.stack.toString()}` : ""}`; + e = ws ? makeBasicErrorMsg(ws, msg) : `RUNTIME ERROR:\n\n${msg}`; + } + if (typeof e !== "string") { + console.error("Unexpected error type:", e); + e = "Unexpected type of error thrown. See console output."; + } + dialogBoxCreate(initialText + e); +} diff --git a/src/Netscript/killWorkerScript.ts b/src/Netscript/killWorkerScript.ts index c70455124..84224ca49 100644 --- a/src/Netscript/killWorkerScript.ts +++ b/src/Netscript/killWorkerScript.ts @@ -10,10 +10,10 @@ import { WorkerScriptStartStopEventEmitter } from "./WorkerScriptStartStopEventE import { RunningScript } from "../Script/RunningScript"; import { GetServer } from "../Server/AllServers"; -import { errorDialog } from "../ui/React/DialogBox"; import { AddRecentScript } from "./RecentScripts"; import { ITutorial } from "../InteractiveTutorial"; import { AlertEvents } from "../ui/React/AlertManager"; +import { handleUnknownError } from "./NetscriptHelpers"; export type killScriptParams = WorkerScript | number | { runningScript: RunningScript; hostname: string }; @@ -59,13 +59,16 @@ function killWorkerScriptByPid(pid: number): boolean { } function stopAndCleanUpWorkerScript(ws: WorkerScript): void { - killNetscriptDelay(ws); + //Clean up any ongoing netscriptDelay + if (ws.delay) clearTimeout(ws.delay); + ws.delayReject?.(new ScriptDeath(ws)); + if (typeof ws.atExit === "function") { try { ws.env.stopFlag = false; ws.atExit(); } catch (e: unknown) { - errorDialog(e, `Error during atExit ${ws.name}@${ws.hostname} (PID - ${ws.pid}\n\n`); + handleUnknownError(e, ws, "Error running atExit function.\n\n"); } ws.atExit = undefined; } @@ -115,19 +118,3 @@ function removeWorkerScript(workerScript: WorkerScript): void { WorkerScriptStartStopEventEmitter.emit(); } - -/** - * Helper function that interrupts a script's delay if it is in the middle of a - * timed, blocked operation (like hack(), sleep(), etc.). This allows scripts to - * be killed immediately even if they're in the middle of one of those long operations - */ -function killNetscriptDelay(workerScript: WorkerScript): void { - if (workerScript instanceof WorkerScript) { - if (workerScript.delay) { - clearTimeout(workerScript.delay); - if (workerScript.delayReject) { - workerScript.delayReject(new ScriptDeath(workerScript)); - } - } - } -} diff --git a/src/NetscriptWorker.ts b/src/NetscriptWorker.ts index d75402576..a69548656 100644 --- a/src/NetscriptWorker.ts +++ b/src/NetscriptWorker.ts @@ -24,7 +24,7 @@ import { Settings } from "./Settings/Settings"; import { generate } from "escodegen"; -import { dialogBoxCreate, errorDialog } from "./ui/React/DialogBox"; +import { dialogBoxCreate } from "./ui/React/DialogBox"; import { arrayToString } from "./utils/helpers/arrayToString"; import { roundToTwo } from "./utils/helpers/roundToTwo"; @@ -33,7 +33,7 @@ import { simple as walksimple } from "acorn-walk"; import { areFilesEqual } from "./Terminal/DirectoryHelpers"; import { Terminal } from "./Terminal"; import { ScriptArg } from "./Netscript/ScriptArg"; -import { helpers } from "./Netscript/NetscriptHelpers"; +import { handleUnknownError } from "./Netscript/NetscriptHelpers"; export const NetscriptPorts: Map = new Map(); @@ -347,16 +347,7 @@ function createAndAddWorkerScript(runningScriptObj: RunningScript, server: BaseS workerScript.log("", () => "Script finished running"); }) .catch(function (e) { - if (typeof e === "string") { - const headerText = helpers.makeBasicErrorMsg(workerScript, "", ""); - //Add header info if it is not present; - if (!e.includes(headerText)) e = helpers.makeBasicErrorMsg(workerScript, e); - } else if (e instanceof SyntaxError) { - e = helpers.makeBasicErrorMsg(workerScript, `${e.message} (sorry we can't be more helpful)`, "SYNTAX"); - } else if (e instanceof Error) { - e = helpers.makeBasicErrorMsg(workerScript, `${e.message}${e.stack ? `\nstack:\n${e.stack.toString()}` : ""}`); - } - errorDialog(e); + handleUnknownError(e, workerScript); workerScript.log("", () => (e instanceof ScriptDeath ? "Script killed." : "Script crashed due to an error.")); killWorkerScript(workerScript); }); diff --git a/src/UncaughtPromiseHandler.ts b/src/UncaughtPromiseHandler.ts index a786c384a..d9ddc29d6 100644 --- a/src/UncaughtPromiseHandler.ts +++ b/src/UncaughtPromiseHandler.ts @@ -1,7 +1,11 @@ -import { errorDialog } from "./ui/React/DialogBox"; +import { handleUnknownError } from "./Netscript/NetscriptHelpers"; export function setupUncaughtPromiseHandler(): void { - window.addEventListener("unhandledrejection", (e) => - errorDialog(e.reason, "UNCAUGHT PROMISE ERROR\nYou forgot to await a promise\nmaybe hack / grow / weaken ?\n"), - ); + window.addEventListener("unhandledrejection", (e) => { + handleUnknownError( + e.reason, + null, + "UNCAUGHT PROMISE ERROR\nYou forgot to await a promise\nmaybe hack / grow / weaken ?\n\n", + ); + }); } diff --git a/src/ui/React/DialogBox.tsx b/src/ui/React/DialogBox.tsx index 6974dfb94..3d32dbe9b 100644 --- a/src/ui/React/DialogBox.tsx +++ b/src/ui/React/DialogBox.tsx @@ -2,25 +2,7 @@ import { AlertEvents } from "./AlertManager"; import React from "react"; import { Typography } from "@mui/material"; -import { ScriptDeath } from "../../Netscript/ScriptDeath"; export function dialogBoxCreate(txt: string | JSX.Element): void { AlertEvents.emit(typeof txt === "string" ? {txt} : txt); } - -export function errorDialog(e: unknown, initialText = "") { - let errorText = ""; - if (typeof e === "string") errorText = e; - else if (e instanceof ScriptDeath) { - if (!e.errorMessage) return; //No need for a dialog for an empty ScriptDeath - errorText = e.errorMessage; - if (!e.errorMessage.includes(`${e.name}@${e.hostname}`)) { - initialText += `${e.name}@${e.hostname} (PID - ${e.pid})\n\n`; - } - } else if (e instanceof Error) errorText = "Original error message:\n" + e.message; - else { - errorText = "An unknown error was thrown, see console."; - console.error(e); - } - dialogBoxCreate(initialText + errorText); -}