CODEBASE: Recheck all usages of typecasting with JSON.parse (#1775)

This commit is contained in:
catloversg 2024-11-21 14:47:02 +07:00 committed by GitHub
parent 70a231e421
commit 8c4fcfe045
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 55 additions and 28 deletions

@ -1,3 +1,4 @@
import { dialogBoxCreate } from "../ui/React/DialogBox";
import { Reviver } from "../utils/JSONReviver";
import { BaseGift } from "./BaseGift";
@ -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));
@ -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;
}

@ -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);

@ -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]) {

@ -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
@ -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 = {

@ -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
@ -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);

@ -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> {

@ -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();
});
}