added possible infinite loop checker

This commit is contained in:
Olivier Gagnon 2021-10-28 23:04:26 -04:00
parent a34d6e0dfa
commit d1d0ccf345
9 changed files with 125 additions and 23 deletions

28
dist/vendor.bundle.js vendored

File diff suppressed because one or more lines are too long

@ -44,7 +44,12 @@
-ms-overflow-style: none; /* for Internet Explorer, Edge */
scrollbar-width: none; /* for Firefox */
}
.myGlyphMarginClass {
background: red;
}
.myContentClass {
background: lightblue;
}
*::-webkit-scrollbar {
display: none; /* for Chrome, Safari, and Opera */
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

27
package-lock.json generated

@ -15,6 +15,7 @@
"@mui/icons-material": "^5.0.3",
"@mui/material": "^5.0.3",
"@mui/styles": "^5.0.1",
"@types/acorn": "^4.0.6",
"@types/escodegen": "^0.0.7",
"@types/numeral": "0.0.25",
"@types/react": "^17.0.21",
@ -3613,6 +3614,14 @@
"node": ">= 6"
}
},
"node_modules/@types/acorn": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/@types/acorn/-/acorn-4.0.6.tgz",
"integrity": "sha512-veQTnWP+1D/xbxVrPC3zHnCZRjSrKfhbMUlEA43iMZLu7EsnTtkJklIuwrCPbOi8YkvDQAiW05VQQFvvz9oieQ==",
"dependencies": {
"@types/estree": "*"
}
},
"node_modules/@types/aria-query": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.2.tgz",
@ -3665,6 +3674,11 @@
"resolved": "https://registry.npmjs.org/@types/escodegen/-/escodegen-0.0.7.tgz",
"integrity": "sha512-46oENdSRNEJXCNrPJoC3vRolZJpfeEm7yvATkd2bCncKFG0PUEyfBCaoacfpcXH4Y5RRuqdVj3J7TI+wwn2SbQ=="
},
"node_modules/@types/estree": {
"version": "0.0.50",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.50.tgz",
"integrity": "sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw=="
},
"node_modules/@types/file-saver": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/@types/file-saver/-/file-saver-2.0.3.tgz",
@ -24027,6 +24041,14 @@
"integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==",
"dev": true
},
"@types/acorn": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/@types/acorn/-/acorn-4.0.6.tgz",
"integrity": "sha512-veQTnWP+1D/xbxVrPC3zHnCZRjSrKfhbMUlEA43iMZLu7EsnTtkJklIuwrCPbOi8YkvDQAiW05VQQFvvz9oieQ==",
"requires": {
"@types/estree": "*"
}
},
"@types/aria-query": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.2.tgz",
@ -24079,6 +24101,11 @@
"resolved": "https://registry.npmjs.org/@types/escodegen/-/escodegen-0.0.7.tgz",
"integrity": "sha512-46oENdSRNEJXCNrPJoC3vRolZJpfeEm7yvATkd2bCncKFG0PUEyfBCaoacfpcXH4Y5RRuqdVj3J7TI+wwn2SbQ=="
},
"@types/estree": {
"version": "0.0.50",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.50.tgz",
"integrity": "sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw=="
},
"@types/file-saver": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/@types/file-saver/-/file-saver-2.0.3.tgz",

@ -16,6 +16,7 @@
"@mui/icons-material": "^5.0.3",
"@mui/material": "^5.0.3",
"@mui/styles": "^5.0.1",
"@types/acorn": "^4.0.6",
"@types/escodegen": "^0.0.7",
"@types/numeral": "0.0.25",
"@types/react": "^17.0.21",

@ -6,7 +6,7 @@
* the way
*/
import * as walk from "acorn-walk";
import { parse } from "acorn";
import acorn, { parse } from "acorn";
import { RamCalculationErrorCode } from "./RamCalculationErrorCodes";
@ -206,6 +206,46 @@ async function parseOnlyRamCalculate(
}
}
export function checkInfiniteLoop(code: string): number {
const ast = parse(code, { sourceType: "module", ecmaVersion: "latest" });
function nodeHasTrueTest(node: acorn.Node): boolean {
return node.type === "Literal" && (node as any).raw === "true";
}
function hasAwait(ast: acorn.Node): boolean {
let hasAwait = false;
walk.recursive(
ast,
{},
{
AwaitExpression: () => {
hasAwait = true;
},
},
);
return hasAwait;
}
let missingAwaitLine = -1;
walk.recursive(
ast,
{},
{
WhileStatement: (node: acorn.Node, st: any, walkDeeper: walk.WalkerCallback<any>) => {
if (nodeHasTrueTest((node as any).test) && !hasAwait(node)) {
console.log(node);
missingAwaitLine = (code.slice(0, node.start).match(/\n/g) || []).length + 1;
} else {
(node as any).body && walkDeeper((node as any).body, st);
}
},
},
);
return missingAwaitLine;
}
/**
* Helper function that parses a single script. It returns a map of all dependencies,
* which are items in the code's AST that potentially need to be evaluated
@ -214,7 +254,6 @@ async function parseOnlyRamCalculate(
*/
function parseOnlyCalculateDeps(code: string, currentModule: string): any {
const ast = parse(code, { sourceType: "module", ecmaVersion: "latest" });
// Everything from the global scope goes in ".". Everything else goes in ".function", where only
// the outermost layer of functions counts.
const globalKey = currentModule + memCheckGlobalKey;

@ -11,7 +11,7 @@ import { dialogBoxCreate } from "../../ui/React/DialogBox";
import { isScriptFilename } from "../../Script/isScriptFilename";
import { Script } from "../../Script/Script";
import { TextFile } from "../../TextFile";
import { calculateRamUsage } from "../../Script/RamCalculations";
import { calculateRamUsage, checkInfiniteLoop } from "../../Script/RamCalculations";
import { RamCalculationErrorCode } from "../../Script/RamCalculationErrorCodes";
import { numeralWrapper } from "../../ui/numeralFormat";
import { CursorPositions } from "../CursorPositions";
@ -90,6 +90,7 @@ export function Root(props: IProps): React.ReactElement {
const editorRef = useRef<IStandaloneCodeEditor | null>(null);
const [filename, setFilename] = useState(props.filename ? props.filename : lastFilename);
const [code, setCode] = useState<string>(props.filename ? props.code : lastCode);
const [decorations, setDecorations] = useState<string[]>([]);
hostname = props.filename ? props.hostname : hostname;
if (hostname === "") {
hostname = props.player.getCurrentServer().hostname;
@ -231,6 +232,30 @@ export function Root(props: IProps): React.ReactElement {
lastCode = newCode;
if (editorRef.current !== null) {
lastPosition = editorRef.current.getPosition();
const awaitWarning = checkInfiniteLoop(newCode);
if (awaitWarning !== -1) {
const newDecorations = editorRef.current.deltaDecorations(decorations, [
{
range: {
startLineNumber: awaitWarning,
startColumn: 1,
endLineNumber: awaitWarning,
endColumn: 10,
},
options: {
isWholeLine: true,
glyphMarginClassName: "myGlyphMarginClass",
glyphMarginHoverMessage: {
value: "Possible infinite loop, await something.",
},
},
},
]);
setDecorations(newDecorations);
} else {
const newDecorations = editorRef.current.deltaDecorations(decorations, []);
setDecorations(newDecorations);
}
}
setCode(newCode);
updateRAM(newCode);
@ -364,7 +389,7 @@ export function Root(props: IProps): React.ReactElement {
defaultValue={code}
onChange={updateCode}
theme={options.theme}
options={options}
options={{ ...options, glyphMargin: true }}
/>
<Box display="flex" flexDirection="row" sx={{ m: 1 }} alignItems="center">
<Button onClick={beautify}>Beautify</Button>

@ -44,7 +44,12 @@
-ms-overflow-style: none; /* for Internet Explorer, Edge */
scrollbar-width: none; /* for Firefox */
}
.myGlyphMarginClass {
background: red;
}
.myContentClass {
background: lightblue;
}
*::-webkit-scrollbar {
display: none; /* for Chrome, Safari, and Opera */
}