Skip to content

Commit

Permalink
CODEBASE: Recheck all usages of typecasting with JSON.parse (bitburne…
Browse files Browse the repository at this point in the history
  • Loading branch information
catloversg authored Nov 21, 2024
1 parent 70a231e commit 8c4fcfe
Show file tree
Hide file tree
Showing 7 changed files with 55 additions and 28 deletions.
30 changes: 22 additions & 8 deletions src/CotMG/Helper.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { dialogBoxCreate } from "../ui/React/DialogBox";
import { Reviver } from "../utils/JSONReviver";
import { BaseGift } from "./BaseGift";

Expand All @@ -6,15 +7,26 @@ import { StaneksGift } from "./StaneksGift";
export let staneksGift = new StaneksGift();

export function loadStaneksGift(saveString: string): void {
if (saveString) {
staneksGift = JSON.parse(saveString, Reviver) as StaneksGift;
} else {
let staneksGiftData: unknown;
try {
staneksGiftData = JSON.parse(saveString, Reviver);
if (!(staneksGiftData instanceof StaneksGift)) {
throw new Error(`Data of Stanek's Gift is not an instance of "StaneksGift"`);
}
} catch (error) {
console.error(error);
console.error("Invalid StaneksGiftSave:", saveString);
staneksGift = new StaneksGift();
setTimeout(() => {
dialogBoxCreate(`Cannot load data of Stanek's Gift. Stanek's Gift is reset. Error: ${error}.`);
}, 1000);
return;
}
staneksGift = staneksGiftData;
}

export function zeros(width: number, height: number): number[][] {
const array: number[][] = [];
const array = [];

for (let i = 0; i < width; ++i) {
array.push(Array<number>(height).fill(0));
Expand All @@ -24,14 +36,16 @@ export function zeros(width: number, height: number): number[][] {
}

export function calculateGrid(gift: BaseGift): number[][] {
const newgrid = zeros(gift.width(), gift.height()) as unknown as number[][];
const newGrid = zeros(gift.width(), gift.height());
for (let i = 0; i < gift.width(); i++) {
for (let j = 0; j < gift.height(); j++) {
const fragment = gift.fragmentAt(i, j);
if (!fragment) continue;
newgrid[i][j] = 1;
if (!fragment) {
continue;
}
newGrid[i][j] = 1;
}
}

return newgrid;
return newGrid;
}
5 changes: 5 additions & 0 deletions src/Player.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ export function setPlayer(playerObj: PlayerObject): void {
}

export function loadPlayer(saveString: string): PlayerObject {
/**
* If we want to check player with "instanceof PlayerObject", we have to import PlayerObject normally (not "import
* type"). It will create a cyclic dependency. Fixing this cyclic dependency is really hard. It's not worth the
* effort, so we typecast it here.
*/
const player = JSON.parse(saveString, Reviver) as PlayerObject;
player.money = parseFloat(player.money + "");
player.exploits = sanitizeExploits(player.exploits);
Expand Down
4 changes: 4 additions & 0 deletions src/RemoteFileAPI/Remote.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ export class Remote {
}

function handleMessageEvent(this: WebSocket, e: MessageEvent): void {
/**
* Validating e.data and the result of JSON.parse() is too troublesome, so we typecast them here. If the data is
* invalid, it means the RFA "client" (the tool that the player is using) is buggy, but that's not our problem.
*/
const msg = JSON.parse(e.data as string) as RFAMessage;

if (!msg.method || !RFARequestHandler[msg.method]) {
Expand Down
23 changes: 13 additions & 10 deletions src/SaveObject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import { downloadContentAsFile } from "./utils/FileUtils";
import { showAPIBreaks } from "./utils/APIBreaks/APIBreak";
import { breakInfos261 } from "./utils/APIBreaks/2.6.1";
import { handleGetSaveDataInfoError } from "./Netscript/ErrorMessages";
import { isObject } from "./utils/helpers/typeAssertion";

/* SaveObject.js
* Defines the object used to save/load games
Expand Down Expand Up @@ -220,23 +221,25 @@ class BitburnerSaveObject {
}

if (!decodedSaveData || decodedSaveData === "") {
return Promise.reject(new Error("Save game is invalid"));
console.error("decodedSaveData:", decodedSaveData);
return Promise.reject(new Error("Save game is invalid. The save data cannot be decoded."));
}

let parsedSaveData;
let parsedSaveData: unknown;
try {
parsedSaveData = JSON.parse(decodedSaveData) as {
ctor: string;
data: {
PlayerSave: string;
};
};
parsedSaveData = JSON.parse(decodedSaveData);
} catch (error) {
console.error(error); // We'll handle below
}

if (!parsedSaveData || parsedSaveData.ctor !== "BitburnerSaveObject" || !parsedSaveData.data) {
return Promise.reject(new Error("Save game did not seem valid"));
if (
!isObject(parsedSaveData) ||
parsedSaveData.ctor !== "BitburnerSaveObject" ||
!isObject(parsedSaveData.data) ||
typeof parsedSaveData.data.PlayerSave !== "string"
) {
console.error("decodedSaveData:", decodedSaveData);
return Promise.reject(new Error("Save game is invalid. The decoded save data is not valid."));
}

const data: ImportData = {
Expand Down
9 changes: 3 additions & 6 deletions src/Settings/Settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { defaultTheme } from "../Themes/Themes";
import { defaultStyles } from "../Themes/Styles";
import { CursorStyle, CursorBlinking, WordWrapOptions } from "../ScriptEditor/ui/Options";
import { defaultMonacoTheme } from "../ScriptEditor/ui/themes";
import { objectAssert } from "../utils/helpers/typeAssertion";

/**
* This function won't be able to catch **all** invalid hostnames, and it's still fine. In order to validate a hostname
Expand Down Expand Up @@ -157,12 +158,8 @@ export const Settings = {
disableSuffixes: false,

load(saveString: string) {
const save = JSON.parse(saveString) as {
theme?: typeof Settings.theme;
styles?: typeof Settings.styles;
overview?: typeof Settings.overview;
EditorTheme?: typeof Settings.EditorTheme;
};
const save: unknown = JSON.parse(saveString);
objectAssert(save);
save.theme && Object.assign(Settings.theme, save.theme);
save.styles && Object.assign(Settings.styles, save.styles);
save.overview && Object.assign(Settings.overview, save.overview);
Expand Down
4 changes: 3 additions & 1 deletion src/utils/helpers/typeAssertion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ function getFriendlyType(v: unknown): string {
return v === null ? "null" : Array.isArray(v) ? "array" : typeof v;
}

//All assertion functions used here should return the friendlyType of the input.
export function isObject(v: unknown): v is Record<string, unknown> {
return getFriendlyType(v) === "object";
}

/** For non-objects, and for array/null, throws an error with the friendlyType of v. */
export function objectAssert(v: unknown): asserts v is Record<string, unknown> {
Expand Down
8 changes: 5 additions & 3 deletions test/jest/FullSave.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@ import { Companies } from "../../src/Company/Companies";

describe("Check Save File Continuity", () => {
establishInitialConditions();
// Calling getSaveString forces save info to update
saveObject.getSaveData();
beforeAll(async () => {
// Calling getSaveString forces save info to update
await saveObject.getSaveData();
});

const savesToTest = ["FactionsSave", "PlayerSave", "CompaniesSave", "GoSave"] as const;
for (const saveToTest of savesToTest) {
test(`${saveToTest} continuity`, () => {
const parsed = JSON.parse(saveObject[saveToTest]);
const parsed: unknown = JSON.parse(saveObject[saveToTest]);
expect(parsed).toMatchSnapshot();
});
}
Expand Down

0 comments on commit 8c4fcfe

Please sign in to comment.