Merge pull request #2925 from Ornedan/necro-script-fix

Necro script fix
This commit is contained in:
hydroflame 2022-04-11 21:36:07 -04:00 committed by GitHub
commit a17b81dff3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 102 additions and 59 deletions

@ -0,0 +1,37 @@
import { WorkerScript } from "./WorkerScript";
/**
* Script death marker.
*
* IMPORTANT: the game engine should not base any of it's decisions on the data
* carried in a ScriptDeath instance.
*
* This is because ScriptDeath instances are thrown through player code when a
* script is killed. Which grants the player access to the class and the ability
* to construct new instances with arbitrary data.
*/
export class ScriptDeath {
/** Process ID number. */
pid: number;
/** Filename of the script. */
name: string;
/** IP Address on which the script was running */
hostname: string;
/** Status message in case of script error. */
errorMessage = "";
constructor(ws: WorkerScript) {
this.pid = ws.pid;
this.name = ws.name;
this.hostname = ws.hostname;
this.errorMessage = ws.errorMessage;
Object.freeze(this);
}
}
Object.freeze(ScriptDeath);
Object.freeze(ScriptDeath.prototype);

@ -60,7 +60,7 @@ export class WorkerScript {
env: Environment;
/**
* Status message in case of script error. Currently unused I think
* Status message in case of script error.
*/
errorMessage = "";

@ -2,6 +2,7 @@
* Stops an actively-running script (represented by a WorkerScript object)
* and removes it from the global pool of active scripts.
*/
import { ScriptDeath } from "./ScriptDeath";
import { WorkerScript } from "./WorkerScript";
import { workerScripts } from "./WorkerScripts";
import { WorkerScriptStartStopEventEmitter } from "./WorkerScriptStartStopEventEmitter";
@ -139,7 +140,7 @@ function killNetscriptDelay(workerScript: WorkerScript): void {
if (workerScript.delay) {
clearTimeout(workerScript.delay);
if (workerScript.delayReject) {
workerScript.delayReject(workerScript);
workerScript.delayReject(new ScriptDeath(workerScript));
}
}
}

@ -1,15 +1,20 @@
import { isString } from "./utils/helpers/isString";
import { GetServer } from "./Server/AllServers";
import { ScriptDeath } from "./Netscript/ScriptDeath";
import { WorkerScript } from "./Netscript/WorkerScript";
export function netscriptDelay(time: number, workerScript: WorkerScript): Promise<void> {
// Cancel any pre-existing netscriptDelay'ed function call
// TODO: the rejection almost certainly ends up in the uncaught rejection handler.
// Maybe reject with a stack-trace'd error message?
if (workerScript.delayReject) workerScript.delayReject();
return new Promise(function (resolve, reject) {
workerScript.delay = window.setTimeout(() => {
workerScript.delay = null;
workerScript.delayReject = undefined;
if (workerScript.env.stopFlag) reject(workerScript);
if (workerScript.env.stopFlag) reject(new ScriptDeath(workerScript));
else resolve();
}, time);
workerScript.delayReject = reject;

@ -637,9 +637,6 @@ export function NetscriptCorporation(
const office = getOffice(divisionName, cityName);
if (!Object.values(EmployeePositions).includes(job)) throw new Error(`'${job}' is not a valid job.`);
return netscriptDelay(1000, workerScript).then(function () {
if (workerScript.env.stopFlag) {
return Promise.reject(workerScript);
}
return Promise.resolve(office.setEmployeeToJob(job, amount));
});
},

@ -3,6 +3,7 @@
* that allows for scripts to run
*/
import { killWorkerScript } from "./Netscript/killWorkerScript";
import { ScriptDeath } from "./Netscript/ScriptDeath";
import { WorkerScript } from "./Netscript/WorkerScript";
import { workerScripts } from "./Netscript/WorkerScripts";
import { WorkerScriptStartStopEventEmitter } from "./Netscript/WorkerScriptStartStopEventEmitter";
@ -59,7 +60,7 @@ export function prestigeWorkerScripts(): void {
// 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.
function startNetscript2Script(player: IPlayer, workerScript: WorkerScript): Promise<WorkerScript> {
function startNetscript2Script(player: IPlayer, workerScript: WorkerScript): Promise<void> {
workerScript.running = true;
// The name of the currently running netscript function, to prevent concurrent
@ -79,7 +80,7 @@ function startNetscript2Script(player: IPlayer, workerScript: WorkerScript): Pro
// This is not a problem for legacy Netscript because it also checks the
// stop flag in the evaluator.
if (workerScript.env.stopFlag) {
throw workerScript;
throw new ScriptDeath(workerScript);
}
if (propName === "asleep") return f(...args); // OK for multiple simultaneous calls to sleep.
@ -90,7 +91,7 @@ function startNetscript2Script(player: IPlayer, workerScript: WorkerScript): Pro
"promise-returning function? (Currently running: %s tried to run: %s)";
if (runningFn) {
workerScript.errorMessage = makeRuntimeRejectMsg(workerScript, sprintf(msg, runningFn, propName));
throw workerScript;
throw new ScriptDeath(workerScript);
}
runningFn = propName;
@ -135,10 +136,10 @@ function startNetscript2Script(player: IPlayer, workerScript: WorkerScript): Pro
// Note: the environment that we pass to the JS script only needs to contain the functions visible
// to that script, which env.vars does at this point.
return new Promise<WorkerScript>((resolve, reject) => {
return new Promise<void>((resolve, reject) => {
executeJSScript(player, workerScript.getServer().scripts, workerScript)
.then(() => {
resolve(workerScript);
resolve();
})
.catch((e) => reject(e));
}).catch((e) => {
@ -151,20 +152,21 @@ function startNetscript2Script(player: IPlayer, workerScript: WorkerScript): Pro
e.message + ((e.stack && "\nstack:\n" + e.stack.toString()) || ""),
);
}
throw workerScript;
throw new ScriptDeath(workerScript);
} else if (isScriptErrorMessage(e)) {
workerScript.errorMessage = e;
throw workerScript;
} else if (e instanceof WorkerScript) {
throw new ScriptDeath(workerScript);
} else if (e instanceof ScriptDeath) {
throw e;
}
workerScript.errorMessage = makeRuntimeRejectMsg(workerScript, e);
throw workerScript; // Don't know what to do with it, let's rethrow.
// Don't know what to do with it, let's try making an error message out of it
workerScript.errorMessage = makeRuntimeRejectMsg(workerScript, "" + e);
throw new ScriptDeath(workerScript);
});
}
function startNetscript1Script(workerScript: WorkerScript): Promise<WorkerScript> {
function startNetscript1Script(workerScript: WorkerScript): Promise<void> {
const code = workerScript.code;
workerScript.running = true;
@ -179,7 +181,7 @@ function startNetscript1Script(workerScript: WorkerScript): Promise<WorkerScript
workerScript.env.stopFlag = true;
workerScript.running = false;
killWorkerScript(workerScript);
return Promise.resolve(workerScript);
return Promise.resolve();
}
const interpreterInitialization = function (int: any, scope: any): void {
@ -212,7 +214,7 @@ function startNetscript1Script(workerScript: WorkerScript): Promise<WorkerScript
})
.catch(function (err: any) {
// workerscript is when you cancel a delay
if (!(err instanceof WorkerScript)) {
if (!(err instanceof ScriptDeath)) {
console.error(err);
const errorTextArray = err.split("|DELIMITER|");
const hostname = errorTextArray[1];
@ -225,7 +227,7 @@ function startNetscript1Script(workerScript: WorkerScript): Promise<WorkerScript
workerScript.env.stopFlag = true;
workerScript.running = false;
killWorkerScript(workerScript);
return Promise.resolve(workerScript);
return Promise.resolve();
}
});
};
@ -288,14 +290,14 @@ function startNetscript1Script(workerScript: WorkerScript): Promise<WorkerScript
workerScript.env.stopFlag = true;
workerScript.running = false;
killWorkerScript(workerScript);
return Promise.resolve(workerScript);
return Promise.resolve();
}
return new Promise(function (resolve, reject) {
function runInterpreter(): void {
try {
if (workerScript.env.stopFlag) {
return reject(workerScript);
return reject(new ScriptDeath(workerScript));
}
let more = true;
@ -308,7 +310,7 @@ function startNetscript1Script(workerScript: WorkerScript): Promise<WorkerScript
if (more) {
setTimeout(runInterpreter, Settings.CodeInstructionRunTime);
} else {
resolve(workerScript);
resolve();
}
} catch (e: any) {
e = e.toString();
@ -316,7 +318,7 @@ function startNetscript1Script(workerScript: WorkerScript): Promise<WorkerScript
e = makeRuntimeRejectMsg(workerScript, e);
}
workerScript.errorMessage = e;
return reject(workerScript);
return reject(new ScriptDeath(workerScript));
}
}
@ -325,11 +327,12 @@ function startNetscript1Script(workerScript: WorkerScript): Promise<WorkerScript
} catch (e: any) {
if (isString(e)) {
workerScript.errorMessage = e;
return reject(workerScript);
} else if (e instanceof WorkerScript) {
return reject(new ScriptDeath(workerScript));
} else if (e instanceof ScriptDeath) {
return reject(e);
} else {
return reject(workerScript);
console.error(e);
return reject(new ScriptDeath(workerScript));
}
}
});
@ -552,29 +555,29 @@ function createAndAddWorkerScript(
// Create the WorkerScript. NOTE: WorkerScript ctor will set the underlying
// RunningScript's PID as well
const s = new WorkerScript(runningScriptObj, pid, NetscriptFunctions);
s.ramUsage = oneRamUsage;
const workerScript = new WorkerScript(runningScriptObj, pid, NetscriptFunctions);
workerScript.ramUsage = oneRamUsage;
// Add the WorkerScript to the global pool
workerScripts.set(pid, s);
workerScripts.set(pid, workerScript);
WorkerScriptStartStopEventEmitter.emit();
// Start the script's execution
let p: Promise<WorkerScript> | null = null; // Script's resulting promise
if (s.name.endsWith(".js") || s.name.endsWith(".ns")) {
p = startNetscript2Script(player, s);
let scriptExecution: Promise<void> | null = null; // Script's resulting promise
if (workerScript.name.endsWith(".js") || workerScript.name.endsWith(".ns")) {
scriptExecution = startNetscript2Script(player, workerScript);
} else {
p = startNetscript1Script(s);
if (!(p instanceof Promise)) {
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
p.then(function (w: WorkerScript) {
w.running = false;
w.env.stopFlag = true;
scriptExecution.then(function () {
workerScript.running = false;
workerScript.env.stopFlag = true;
// On natural death, the earnings are transfered to the parent if it still exists.
if (parent !== undefined) {
if (parent.running) {
@ -583,51 +586,51 @@ function createAndAddWorkerScript(
}
}
killWorkerScript(s);
w.log("", () => "Script finished running");
}).catch(function (w) {
if (w instanceof Error) {
killWorkerScript(workerScript);
workerScript.log("", () => "Script finished running");
}).catch(function (e) {
if (e instanceof Error) {
dialogBoxCreate("Script runtime unknown error. This is a bug please contact game developer");
console.error("Evaluating workerscript returns an Error. THIS SHOULDN'T HAPPEN: " + w.toString());
console.error("Evaluating workerscript returns an Error. THIS SHOULDN'T HAPPEN: " + e.toString());
return;
} else if (w instanceof WorkerScript) {
if (isScriptErrorMessage(w.errorMessage)) {
const errorTextArray = w.errorMessage.split("|DELIMITER|");
} else if (e instanceof ScriptDeath) {
if (isScriptErrorMessage(workerScript.errorMessage)) {
const errorTextArray = workerScript.errorMessage.split("|DELIMITER|");
if (errorTextArray.length != 4) {
console.error("ERROR: Something wrong with Error text in evaluator...");
console.error("Error text: " + w.errorMessage);
console.error("Error text: " + workerScript.errorMessage);
return;
}
const hostname = errorTextArray[1];
const scriptName = errorTextArray[2];
const errorMsg = errorTextArray[3];
let msg = `RUNTIME ERROR<br>${scriptName}@${hostname}<br>`;
if (w.args.length > 0) {
msg += `Args: ${arrayToString(w.args)}<br>`;
let msg = `RUNTIME ERROR<br>${scriptName}@${hostname} (PID - ${workerScript.pid})<br>`;
if (workerScript.args.length > 0) {
msg += `Args: ${arrayToString(workerScript.args)}<br>`;
}
msg += "<br>";
msg += errorMsg;
dialogBoxCreate(msg);
w.log("", () => "Script crashed with runtime error");
workerScript.log("", () => "Script crashed with runtime error");
} else {
w.log("", () => "Script killed");
workerScript.log("", () => "Script killed");
return; // Already killed, so stop here
}
} else if (isScriptErrorMessage(w)) {
} else if (isScriptErrorMessage(e)) {
dialogBoxCreate("Script runtime unknown error. This is a bug please contact game developer");
console.error(
"ERROR: Evaluating workerscript returns only error message rather than WorkerScript object. THIS SHOULDN'T HAPPEN: " +
w.toString(),
e.toString(),
);
return;
} else {
dialogBoxCreate("An unknown script died for an unknown reason. This is a bug please contact game dev");
console.error(w);
console.error(e);
}
killWorkerScript(s);
killWorkerScript(workerScript);
});
return true;

@ -1,4 +1,4 @@
import { WorkerScript } from "./Netscript/WorkerScript";
import { ScriptDeath } from "./Netscript/ScriptDeath";
import { isScriptErrorMessage } from "./NetscriptEvaluator";
import { dialogBoxCreate } from "./ui/React/DialogBox";
@ -14,9 +14,9 @@ export function setupUncaughtPromiseHandler(): void {
msg += "<br>";
msg += errorMsg;
dialogBoxCreate(msg);
} else if (e.reason instanceof WorkerScript) {
} else if (e.reason instanceof ScriptDeath) {
const msg =
`UNCAUGHT PROMISE ERROR<br>You forgot to await a promise<br>${e.reason.name}@${e.reason.hostname}<br>` +
`UNCAUGHT PROMISE ERROR<br>You forgot to await a promise<br>${e.reason.name}@${e.reason.hostname} (PID - ${e.reason.pid})<br>` +
`Maybe hack / grow / weaken ?`;
dialogBoxCreate(msg);
}