mirror of
https://github.com/morluto/rea.git
synced 2026-11-07 15:26:25 +00:00
* fix(javascript): retain guards after labelled breaks * fix(javascript): bound labelled exit inspection * fix(javascript): preserve local break completion * fix(javascript): avoid exits through unknown control flow * fix(javascript): retain guards after bounded jump scan
97 lines
3.5 KiB
TypeScript
97 lines
3.5 KiB
TypeScript
import * as t from "@babel/types";
|
|
import { expect, it } from "vitest";
|
|
import { ElectronCallPathBlockPrefixes } from "./electronCallPathBlockPrefixes.js";
|
|
import { parseJavaScriptSource } from "./javascriptSourceParser.js";
|
|
import { completeSemanticSteps } from "./javascriptSemanticTraversal.js";
|
|
|
|
it("indexes a real flat block once, retaining contextual guards in source order", () => {
|
|
const parsed = parseJavaScriptSource(`{
|
|
${"0;".repeat(2048)}
|
|
if (uncertain) ordinary();
|
|
if (flag) throw 0;
|
|
electron.send("tail");
|
|
}`);
|
|
const block = parsed?.program.body[0];
|
|
if (!t.isBlockStatement(block)) throw new Error("Expected parsed block");
|
|
const first = block.body[0];
|
|
const tail = block.body.at(-1);
|
|
if (first === undefined || tail === undefined)
|
|
throw new Error("Expected block statements");
|
|
let examined = 0;
|
|
const prefixes = new ElectronCallPathBlockPrefixes(() => {
|
|
examined += 1;
|
|
return true;
|
|
});
|
|
const steps = prefixes.before(block, tail);
|
|
expect(steps.next()).toEqual({ done: false, value: undefined });
|
|
expect(examined).toBe(2048);
|
|
const prefix = completeSemanticSteps(steps);
|
|
const completeVisits = examined;
|
|
expect(completeVisits).toBeGreaterThan(block.body.length);
|
|
expect(prefix).toMatchObject({
|
|
before: 2050,
|
|
entries: [
|
|
{
|
|
position: 2048,
|
|
fact: {
|
|
kind: "branch",
|
|
test: { type: "Identifier", name: "uncertain" },
|
|
consequentExits: false,
|
|
alternateExits: false,
|
|
},
|
|
},
|
|
{
|
|
position: 2049,
|
|
fact: {
|
|
kind: "branch",
|
|
test: { type: "Identifier", name: "flag" },
|
|
consequentExits: true,
|
|
alternateExits: false,
|
|
},
|
|
},
|
|
],
|
|
});
|
|
for (const statement of block.body) {
|
|
const earlier = completeSemanticSteps(prefixes.before(block, statement));
|
|
expect(earlier?.entries).toBe(prefix?.entries);
|
|
}
|
|
expect(examined).toBe(completeVisits);
|
|
expect(completeSemanticSteps(prefixes.before(block, first))?.before).toBe(0);
|
|
});
|
|
|
|
it("does not publish an incomplete block index when its work budget ends", () => {
|
|
const parsed = parseJavaScriptSource(
|
|
`{ 0; 1; if (stop) throw 0; send("tail"); }`,
|
|
);
|
|
const block = parsed?.program.body[0];
|
|
if (!t.isBlockStatement(block)) throw new Error("Expected parsed block");
|
|
const tail = block.body.at(-1);
|
|
if (tail === undefined) throw new Error("Expected terminal statement");
|
|
let remaining = 2;
|
|
const prefixes = new ElectronCallPathBlockPrefixes(() => --remaining >= 0);
|
|
expect(completeSemanticSteps(prefixes.before(block, tail))).toBeUndefined();
|
|
remaining = 100;
|
|
expect(completeSemanticSteps(prefixes.before(block, tail))).toMatchObject({
|
|
before: 3,
|
|
entries: [{ position: 2, fact: { kind: "branch" } }],
|
|
});
|
|
expect(remaining).toBeGreaterThan(0);
|
|
});
|
|
|
|
it("charges nested exit checks to the same budget and does not cache a partial index", () => {
|
|
const parsed = parseJavaScriptSource(
|
|
`{ if (x) { { { throw 0; } } } send(); }`,
|
|
);
|
|
const block = parsed?.program.body[0];
|
|
if (!t.isBlockStatement(block)) throw new Error("Expected parsed block");
|
|
const tail = block.body.at(-1);
|
|
if (tail === undefined) throw new Error("Expected terminal statement");
|
|
let remaining = 4;
|
|
const prefixes = new ElectronCallPathBlockPrefixes(() => --remaining >= 0);
|
|
expect(completeSemanticSteps(prefixes.before(block, tail))).toBeUndefined();
|
|
remaining = 100;
|
|
expect(completeSemanticSteps(prefixes.before(block, tail))).toMatchObject({
|
|
entries: [{ fact: { kind: "branch", consequentExits: true } }],
|
|
});
|
|
});
|