mirror of
https://github.com/morluto/rea.git
synced 2026-11-05 22:36:08 +00:00
* fix(javascript): resolve module-level Electron options in functions * fix(javascript): limit module option ancestry walk * fix(javascript): preserve uncertain captured argument ordering * fix(javascript): retain hoisted activation uncertainty * fix(javascript): retain function ownership through loops * fix(javascript): account for classic global activations * fix(javascript): bind first activations to lexical identities * fix(javascript): retain uncertain global activation timing * fix(javascript): isolate receiver facts and share computed reads * fix(javascript): retain projected and opaque global activations * fix(javascript): refresh activation bounds as opaque effects grow * fix(ci): format website watch directories without changing configuration * fix(javascript): share source reads across opaque-effect updates * fix(javascript): retain constructed-code global activation bounds * fix(javascript): share runtime activation and receiver facts * fix(javascript): reuse pattern reads and function positions * fix(javascript): isolate copied paths and preserve runtime reads * style(javascript): format migrated reference imports * fix(javascript): normalize constructor activation and receiver effects * fix(javascript): retain aliased receiver timing and lexical global this * fix(javascript): preserve effect-free receiver identity across activations * fix(javascript): retain outer activation and prototype effects * fix(javascript): index implicit reads without deep path rescans * fix(javascript): retain aliased prototype and parameter read uncertainty * fix(javascript): index reflected reads through intrinsic invocation facts * fix(javascript): retain unresolved global activation reads * chore: refresh PR branch after review fix publication * fix(javascript): retain projected receiver reads and cache method facts * fix(javascript): retain inherited and implicit activation reads * fix(javascript): retain caller-owned this uncertainty * fix(javascript): retain reflected reads with unresolved spread arguments * fix(javascript): preserve enclosing activation for decorators * fix(javascript): track intrinsic literal prototype effects * fix(javascript): retain catch activation from implicit reads * perf(javascript): reuse effective keys for projected reads * fix(javascript): retain possible caller-owned ambient effects
348 lines
15 KiB
TypeScript
348 lines
15 KiB
TypeScript
import { expect } from "vitest";
|
|
|
|
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
|
import { cliTest } from "../../support/cli/cliFixture.js";
|
|
import { analyzeJavaScriptDirectory } from "../../support/cli/javascriptApplication.js";
|
|
import { writeFixtureFiles } from "../../support/javascriptApplicationFixture.js";
|
|
|
|
cliTest.for(["CLI", "stdio MCP"] as const)(
|
|
"preserves hoisted Electron initialization uncertainty through %s",
|
|
async (surface, { cli }) => {
|
|
const root = await createTestTempDirectory("rea-electron-initialization-");
|
|
await writeFixtureFiles(root, {
|
|
"package.json": JSON.stringify({ main: "early.cjs" }),
|
|
"early.cjs": `
|
|
const { BrowserWindow } = require("electron");
|
|
createWindow();
|
|
const webPreferences = { nodeIntegration: true };
|
|
function createWindow() {
|
|
return new BrowserWindow({ webPreferences });
|
|
}
|
|
require("./late.cjs");
|
|
require("./early-loop.cjs");
|
|
require("./late-loop.cjs");
|
|
throw new Error("must not execute source");
|
|
`,
|
|
"late.cjs": `
|
|
const { BrowserWindow } = require("electron");
|
|
function createWindow() {
|
|
return new BrowserWindow({ webPreferences });
|
|
}
|
|
const webPreferences = { nodeIntegration: true };
|
|
createWindow();
|
|
throw new Error("must not execute source");
|
|
`,
|
|
"early-loop.cjs": `
|
|
const { BrowserWindow } = require("electron");
|
|
createWindow();
|
|
const webPreferences = { nodeIntegration: true };
|
|
function createWindow() {
|
|
for (const item of [0]) return new BrowserWindow({ webPreferences });
|
|
}
|
|
throw new Error("must not execute source");
|
|
`,
|
|
"late-loop.cjs": `
|
|
const { BrowserWindow } = require("electron");
|
|
const webPreferences = { nodeIntegration: true };
|
|
function createWindow() {
|
|
for (const item of [0]) return new BrowserWindow({ webPreferences });
|
|
}
|
|
createWindow();
|
|
throw new Error("must not execute source");
|
|
`,
|
|
});
|
|
const result = await analyzeJavaScriptDirectory(surface, cli, root);
|
|
expect(result.statistics.parse_failures).toBe(0);
|
|
const windows = result.graph.nodes.filter(
|
|
({ kind }) => kind === "browser-window",
|
|
);
|
|
expect(windows).toHaveLength(4);
|
|
const observations = windows.flatMap(({ observations }) => observations);
|
|
const inSource = (source: string) =>
|
|
observations.filter(
|
|
({ evidence }) =>
|
|
evidence.location.available &&
|
|
evidence.location.value.kind === "source-range" &&
|
|
evidence.location.value.source === source,
|
|
);
|
|
for (const source of ["early.cjs", "early-loop.cjs"]) {
|
|
expect(inSource(source)).toHaveLength(1);
|
|
expect(inSource(source)[0]?.properties).toMatchObject({
|
|
web_preferences_status: "dynamic",
|
|
web_preferences: [],
|
|
});
|
|
}
|
|
for (const source of ["late.cjs", "late-loop.cjs"]) {
|
|
expect(inSource(source)).toHaveLength(1);
|
|
expect(inSource(source)[0]?.properties).toMatchObject({
|
|
web_preferences_status: "object-literal",
|
|
web_preferences: [
|
|
{
|
|
name: "nodeIntegration",
|
|
value: { status: "literal", value: true },
|
|
},
|
|
],
|
|
});
|
|
}
|
|
},
|
|
);
|
|
|
|
cliTest.for([
|
|
["CLI", false],
|
|
["stdio MCP", false],
|
|
["CLI", true],
|
|
["stdio MCP", true],
|
|
["CLI", "cached-eval"],
|
|
["stdio MCP", "cached-eval"],
|
|
["CLI", "constructed-function"],
|
|
["stdio MCP", "constructed-function"],
|
|
["CLI", "erased-type"],
|
|
["stdio MCP", "erased-type"],
|
|
["CLI", "module-eval"],
|
|
["stdio MCP", "module-eval"],
|
|
["CLI", "bound-constructor"],
|
|
["stdio MCP", "bound-constructor"],
|
|
["CLI", "reflect-constructor"],
|
|
["stdio MCP", "reflect-constructor"],
|
|
["CLI", "prototype-effect"],
|
|
["stdio MCP", "prototype-effect"],
|
|
["CLI", "assign-effect"],
|
|
["stdio MCP", "assign-effect"],
|
|
["CLI", "this-constructor"],
|
|
["stdio MCP", "this-constructor"],
|
|
["CLI", "alias-prototype"],
|
|
["stdio MCP", "alias-prototype"],
|
|
["CLI", "alias-assign"],
|
|
["stdio MCP", "alias-assign"],
|
|
["CLI", "assignment-pattern"],
|
|
["stdio MCP", "assignment-pattern"],
|
|
["CLI", "iteration-pattern"],
|
|
["stdio MCP", "iteration-pattern"],
|
|
["CLI", "reflect-read"],
|
|
["stdio MCP", "reflect-read"],
|
|
["CLI", "catch-read"],
|
|
["stdio MCP", "catch-read"],
|
|
["CLI", "heritage"],
|
|
["stdio MCP", "heritage"],
|
|
["CLI", "computed-key"],
|
|
["stdio MCP", "computed-key"],
|
|
["CLI", "tagged-function"],
|
|
["stdio MCP", "tagged-function"],
|
|
["CLI", "array-prototype"],
|
|
["stdio MCP", "array-prototype"],
|
|
] as const)(
|
|
"preserves classic global initialization uncertainty through %s (%s)",
|
|
async ([surface, alias], { cli }) => {
|
|
const root = await createTestTempDirectory(
|
|
"rea-electron-global-initialization-",
|
|
);
|
|
const activation =
|
|
alias === "reflect-read"
|
|
? "Reflect.get(globalThis, 'createWindow')();"
|
|
: alias === "catch-read"
|
|
? "try { throw globalThis; } catch ({createWindow:start}) { start(); }"
|
|
: alias === "heritage"
|
|
? "class C extends (this.Function('createWindow()')(), Object) {}"
|
|
: alias === "computed-key"
|
|
? "({ [this.eval('createWindow()')]() {} });"
|
|
: alias === "tagged-function"
|
|
? "Function`createWindow()`();"
|
|
: alias === "array-prototype"
|
|
? "Object.setPrototypeOf(Array.prototype, globalThis); [].createWindow();"
|
|
: alias === "this-constructor"
|
|
? "this['Function']('createWindow()')();"
|
|
: alias === "alias-prototype"
|
|
? "const changed = {}; const indirect = changed; Object.setPrototypeOf(changed, globalThis); indirect.createWindow();"
|
|
: alias === "alias-assign"
|
|
? "const changed = {}; const indirect = changed; Object.assign(changed, globalThis); indirect.createWindow();"
|
|
: alias === "bound-constructor"
|
|
? "const F = Function.bind(null); new F('createWindow()')();"
|
|
: alias === "reflect-constructor"
|
|
? "Reflect.construct(Function, ['createWindow()'])();"
|
|
: alias === "prototype-effect"
|
|
? "const changed = {}; Object.setPrototypeOf(changed, globalThis); changed.createWindow();"
|
|
: alias === "assign-effect"
|
|
? "const changed = {}; Object.assign(changed, globalThis); changed.createWindow();"
|
|
: alias === "assignment-pattern"
|
|
? "let start; ({createWindow:start}=globalThis); start();"
|
|
: alias === "iteration-pattern"
|
|
? "for(const {createWindow:start} of [globalThis]) start();"
|
|
: alias === "module-eval"
|
|
? "eval('createWindow()');"
|
|
: alias === "erased-type"
|
|
? "globalThis.createWindow();"
|
|
: alias === "constructed-function"
|
|
? "new Function('createWindow()')();"
|
|
: alias === "cached-eval"
|
|
? "const noop = () => {}; function unused() { noop(); } (0, eval)('createWindow()');"
|
|
: alias
|
|
? "root.createWindow();"
|
|
: "globalThis.createWindow();";
|
|
const source = (early: boolean) => `
|
|
${alias === "erased-type" ? "import type { T } from './types';" : alias === "module-eval" ? "export {};" : ""}
|
|
${alias === "module-eval" ? 'import { BrowserWindow } from "electron";' : 'const { BrowserWindow } = require("electron");'}
|
|
${alias ? "const root = globalThis;" : ""}
|
|
${alias === "reflect-read" || alias === "catch-read" ? "Object.extra = 1; function unusedArrayRead() { [].createWindow; }" : ""}
|
|
const unrelated = { createWindow() {} };
|
|
function unusedReceiverRead() { unrelated.createWindow; }
|
|
for (const ignored of [1, 2]) { unrelated.createWindow; }
|
|
${early ? activation : ""}
|
|
const webPreferences = { nodeIntegration: true };
|
|
function createWindow() { return new BrowserWindow({ webPreferences }); }
|
|
${early || alias === "module-eval" ? "" : activation}
|
|
createWindow();
|
|
throw new Error("must not execute source");
|
|
`;
|
|
const extension =
|
|
alias === "erased-type" ? "ts" : alias === "module-eval" ? "mjs" : "js";
|
|
const earlyName = `early.${extension}`;
|
|
const lateName = `late.${extension}`;
|
|
await writeFixtureFiles(root, {
|
|
[earlyName]: source(true),
|
|
[lateName]: source(false),
|
|
"index.html": `<script src="${earlyName}"></script><script src="${lateName}"></script>`,
|
|
});
|
|
const result = await analyzeJavaScriptDirectory(surface, cli, root);
|
|
expect(result.statistics.parse_failures).toBe(0);
|
|
const windows = result.graph.nodes.filter(
|
|
({ kind }) => kind === "browser-window",
|
|
);
|
|
expect(windows).toHaveLength(2);
|
|
const inSource = (name: string) =>
|
|
windows
|
|
.flatMap(({ observations }) => observations)
|
|
.filter(
|
|
({ evidence }) =>
|
|
evidence.location.available &&
|
|
evidence.location.value.kind === "source-range" &&
|
|
evidence.location.value.source === name,
|
|
);
|
|
expect(inSource(earlyName)).toHaveLength(1);
|
|
expect(inSource(earlyName)[0]?.properties).toMatchObject({
|
|
web_preferences_status: "dynamic",
|
|
web_preferences: [],
|
|
});
|
|
expect(inSource(lateName)).toHaveLength(1);
|
|
expect(inSource(lateName)[0]?.properties).toMatchObject({
|
|
web_preferences_status: "object-literal",
|
|
web_preferences: [
|
|
{ name: "nodeIntegration", value: { status: "literal", value: true } },
|
|
],
|
|
});
|
|
},
|
|
);
|
|
|
|
cliTest.for(["CLI", "stdio MCP"] as const)(
|
|
"preserves aliased prototype, reflected and parameter activation through %s",
|
|
async (surface, { cli }) => {
|
|
const root = await createTestTempDirectory(
|
|
"rea-electron-prototype-parameters-",
|
|
);
|
|
const activations = {
|
|
callerThis: "function boot() { this.createWindow(); } boot();",
|
|
boundThis:
|
|
"function boot() { this.createWindow(); } boot.call(globalThis);",
|
|
numericPrototype:
|
|
"Object.assign(Object.prototype, globalThis); (0).createWindow();",
|
|
booleanPrototype:
|
|
"Object.assign(Object.prototype, globalThis); true.createWindow();",
|
|
regexpPrototype:
|
|
"Object.assign(Object.prototype, globalThis); /x/.createWindow();",
|
|
nestedGlobalPrototype:
|
|
"Object.setPrototypeOf(globalThis.window.Array.prototype, globalThis); [].createWindow();",
|
|
taggedCatch:
|
|
"function fail() { throw globalThis; } try { fail``; } catch ({createWindow:start}) { start(); }",
|
|
getterCatch:
|
|
"const box = { get value() { throw globalThis; } }; try { box.value; } catch ({createWindow:start}) { start(); }",
|
|
reflectedApply:
|
|
"const args = [globalThis, 'createWindow']; Reflect.get.apply(Reflect, args)();",
|
|
projectedReceiver:
|
|
"const { root } = { root: globalThis, createWindow() {} }; const early = root.createWindow; early();",
|
|
assignedReceiver:
|
|
"let root; ({ root } = { root: globalThis, createWindow() {} }); const early = root.createWindow; early();",
|
|
nestedReceiver:
|
|
"const { nested: { root } } = { nested: { root: globalThis }, createWindow() {} }; root.createWindow();",
|
|
catchPropagated:
|
|
"function fail() { throw globalThis; } try { fail(); } catch ({ createWindow: start }) { start(); }",
|
|
catchNested:
|
|
"function fail() { throw globalThis; } try { if (true) fail(); } catch ({ createWindow: start }) { start(); }",
|
|
objectPrototype:
|
|
"Object.assign(Object.prototype, globalThis); ({}).createWindow();",
|
|
stringPrototype:
|
|
"Object.setPrototypeOf(String.prototype, globalThis); ''.createWindow();",
|
|
lexicalThisPrototype:
|
|
"Object.setPrototypeOf(this.Array.prototype, globalThis); [].createWindow();",
|
|
reflectedTemplate:
|
|
"globalThis[`Reflect`].get(globalThis, 'createWindow')();",
|
|
reflectedOptional: "Reflect.get?.(globalThis, 'createWindow')();",
|
|
reflectedNamespace:
|
|
"const R = Reflect; R.get(globalThis, 'createWindow')();",
|
|
reflectedMethod:
|
|
"const read = Reflect.get; read(globalThis, 'createWindow')();",
|
|
reflectedBound:
|
|
"const read = Reflect.get.bind(Reflect); read(globalThis, 'createWindow')();",
|
|
array:
|
|
"const receiver = []; const alias = receiver; Object.setPrototypeOf(Array.prototype, globalThis); alias.createWindow();",
|
|
function:
|
|
"const receiver = function(){}; const alias = receiver; Object.setPrototypeOf(Function.prototype, globalThis); alias.createWindow();",
|
|
parameter:
|
|
"function boot({ createWindow: start } = {}) { start(); } boot(globalThis);",
|
|
};
|
|
const files: Record<string, string> = {};
|
|
for (const [kind, activation] of Object.entries(activations)) {
|
|
for (const early of [true, false]) {
|
|
files[`${kind}-${early ? "early" : "late"}.js`] = `
|
|
const { BrowserWindow } = require("electron");
|
|
${early ? activation : ""}
|
|
const webPreferences = { nodeIntegration: true };
|
|
function createWindow() { return new BrowserWindow({ webPreferences }); }
|
|
${early ? "" : activation}
|
|
createWindow();
|
|
throw new Error("must not execute source");
|
|
`;
|
|
}
|
|
}
|
|
files["index.html"] = Object.keys(files)
|
|
.map((name) => `<script src="${name}"></script>`)
|
|
.join("");
|
|
await writeFixtureFiles(root, files);
|
|
const result = await analyzeJavaScriptDirectory(surface, cli, root);
|
|
expect(result.statistics.parse_failures).toBe(0);
|
|
const windows = result.graph.nodes.filter(
|
|
({ kind }) => kind === "browser-window",
|
|
);
|
|
expect(windows).toHaveLength(50);
|
|
for (const source of Object.keys(files).filter((name) =>
|
|
name.endsWith(".js"),
|
|
)) {
|
|
const observations = windows
|
|
.flatMap(({ observations }) => observations)
|
|
.filter(
|
|
({ evidence }) =>
|
|
evidence.location.available &&
|
|
evidence.location.value.kind === "source-range" &&
|
|
evidence.location.value.source === source,
|
|
);
|
|
expect(observations).toHaveLength(1);
|
|
const definite =
|
|
source.endsWith("-late.js") &&
|
|
!["parameter-", "callerThis-", "boundThis-"].some((prefix) =>
|
|
source.startsWith(prefix),
|
|
);
|
|
expect(observations[0]?.properties).toMatchObject(
|
|
definite
|
|
? {
|
|
web_preferences_status: "object-literal",
|
|
web_preferences: [
|
|
{
|
|
name: "nodeIntegration",
|
|
value: { status: "literal", value: true },
|
|
},
|
|
],
|
|
}
|
|
: { web_preferences_status: "dynamic", web_preferences: [] },
|
|
);
|
|
}
|
|
},
|
|
);
|