Files
rea/src/domain/javascript/javascriptStaticAnalysis.mapEndpoints.test.ts
T
Kaoru0822 73b756430f fix(javascript): read utilityProcess.fork options from the argv slot (#6280)
Electron's utilityProcess.fork(modulePath[, args][, options]) treats a
non-array object in the second argument as the options and ignores the
third. The static extractor only read the third argument, so
`fork("./worker.js", { serviceName })` lost the declared name while
reporting exact, complete coverage, and an object in the second slot let
an ignored third argument supply the name.

Move the optional-argv options selection that child_process already used
into the shared expression-shape owner and reuse it for utility
processes. Unproven argv shapes, spreads and identifier options remain
unknown with partial coverage; nullish options or a nullish serviceName
declare no name, matching Electron's runtime checks.

Fixes #6208

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 01:17:40 -07:00

200 lines
11 KiB
TypeScript

import { expect, it } from "vitest";
import * as t from "@babel/types";
import { analyzeJavaScriptStaticSource } from "./javascriptStaticAnalysis.js";
import { inspectCall } from "./javascriptStaticAnalysisCalls.js";
import { createJavaScriptAnalysisAccumulator } from "./javascriptStaticAnalysisState.js";
import { indexJavaScriptBindingsWithReceiversSteps } from "./javascriptSemanticAnalysis.js";
import { parseJavaScriptSource } from "./javascriptSourceParser.js";
import { createJavaScriptRpcScan } from "./javascriptRpcEndpoints.js";
import { createSemanticExpressionShapeResolver } from "./javascriptSemanticExpressionShapes.js";
import {
completeSemanticSteps,
traverseJavaScriptAst,
} from "./javascriptSemanticTraversal.js";
it("does not classify a local Map lookup as a network endpoint", () => {
const analysis = analyzeJavaScriptStaticSource(`
const records = new Map([["/accounts", { name: "Alice" }]]);
console.log(records.get("/accounts"));
fetch("/network");
`);
expect(analysis.endpoints).toMatchObject([
{ kind: "network", value: "/network", mechanism: "call:fetch" },
]);
expect(analysis.endpoints[0]?.interpretation).toBeUndefined();
});
it.each([
'const records = new Map(); const alias = records; alias.get("/accounts"); records.delete("/accounts"); alias.get("/accounts")',
'new Map().get("/accounts")',
'new Map().delete("https://example.test/accounts")',
'const records = new Map(); const alias = records; alias.get("/accounts")',
'const records = new globalThis.Map(); records?.["get"]("/accounts")',
'function inspect() { const records = new Map(); return records.get("/accounts"); }',
'const inspect = () => { const records = new Map(); const alias = records; return alias.delete("/accounts"); }',
'const browser = window; browser.open("about:blank"); new Map().get("/accounts")',
'const box = {}; box.value = { name: "Alice" }; inspect(box); new Map().get("/accounts")',
'patch(() => ({ name: "Alice" })); new Map().get("/accounts")',
'const box = {}; box.prototype = Map.prototype; new Map().get("/accounts")',
'const box = {}; const alias = box; alias.prototype = Map.prototype; inspect({}); new Map().get("/accounts")',
'const box = {}; box.make = () => Map.prototype; new Map().get("/accounts")',
'let box = {}; const first = box; box = {}; box.p = Map.prototype; patch(first); new Map().get("/accounts")',
'const box = {}; box.a = {}; box.a.p = Map.prototype; box.safe = {}; patch(box.safe); new Map().get("/accounts")',
'const records = new Map(); records.get = fetch; new Map().get("/accounts")',
'const holder = { get prototype() { return Map.prototype; } }; new Map().get("/accounts")',
'const holder = { get selected() { return {}; }, get other() { return Map.prototype; } }; holder.selected.get = fetch; new Map().get("/accounts")',
'function Map() {} new Map().constructor.prototype.get = fetch; globalThis.Map.prototype; new globalThis.Map().get("/accounts")',
'const factory = { make() { return {}; } }; factory.make = () => ({}); patch(factory.make()); new Map().get("/accounts")',
])("omits a proven Map operation: %s", (source) => {
expect(analyzeJavaScriptStaticSource(source).endpoints).toEqual([]);
});
it.each([
'client.get("/accounts")',
'Object.getPrototypeOf = () => Map.prototype; Object.getPrototypeOf({}).get = fetch; new Map().get("/accounts")',
'Reflect.getPrototypeOf = () => Map.prototype; Reflect.getPrototypeOf({}).get = fetch; new Map().get("/accounts")',
'const records = new Map(); records.get = request; records.get("/accounts")',
'let records = new Map(); records = client; records.get("/accounts")',
'function inspect(Map) { new Map().get("/accounts"); }',
'Map.prototype.get = request; const records = new Map(); records.get("/accounts")',
'const records = new Map(); records.__proto__ = { get: fetch }; records.get("/accounts")',
'const records = new Map(); const alias = records; alias.__proto__ = { get: fetch }; records.get("/accounts")',
'const records = new Map(); records["__proto__"] = { delete: fetch }; records.delete("/accounts")',
'function inspect() { records.get("/accounts"); const records = new Map(); }',
'const alias = records; const records = new Map(); alias.get("/accounts")',
'function inspect() { const alias = records; const records = new Map(); alias.get("/accounts"); }',
'function inspect() { const records = new Map(); records.get = fetch; records.get("/accounts"); }',
'patch(Map.prototype); const records = new Map(); records.get("/accounts")',
'const prototype = Map.prototype; patch(prototype); new Map().get("/accounts")',
'patch(Map); new Map().get("/accounts")',
'new Patch(Map.prototype); new Map().get("/accounts")',
'Map.prototype.patch = function () { this.get = fetch; }; Map.prototype.patch(); new Map().get("/accounts")',
'Map.patch(); new Map().get("/accounts")',
'globalThis.patch = function () { this.Map.prototype.get = fetch; }; globalThis.patch(); new Map().get("/accounts")',
'window.open = function () { this.Map.prototype.get = fetch; }; window.open(); new Map().get("/accounts")',
'function invoke() { window.open(); } window.open = patch; invoke(); new Map().get("/accounts")',
'patch`prototype ${Map.prototype}`; new Map().get("/accounts")',
'const box = { prototype: Map.prototype }; patch(box); new Map().get("/accounts")',
'patch((() => Map.prototype)()); new Map().get("/accounts")',
'const prototype = (() => Map.prototype)(); prototype.get = fetch; new Map().get("/accounts")',
'const box = {}; box.prototype = Map.prototype; box.prototype.get = fetch; new Map().get("/accounts")',
'const box = {}; box.p = (anything(), Map.prototype); patch(box); new Map().get("/accounts")',
'window.prototype = Map.prototype; new Map().get("/accounts")',
'const box = new ExternalBox(); box.prototype = Map.prototype; new Map().get("/accounts")',
'function expose(box) { box.prototype = Map.prototype; } new Map().get("/accounts")',
'const factory = { make() { return Map.prototype; } }; factory.make().get = fetch; new Map().get("/accounts")',
'new Map().__proto__.get = fetch; new Map().get("/accounts")',
'new Map().constructor.prototype.get = fetch; new Map().get("/accounts")',
'Object.getPrototypeOf(new Map()).constructor.prototype.get = fetch; new Map().get("/accounts")',
'const holder = { get prototype() { return Map.prototype; } }; holder.prototype.get = fetch; new Map().get("/accounts")',
'const holder = { get ["prototype"]() { return { nested: Map.prototype }; } }; holder.prototype.nested.get = fetch; new Map().get("/accounts")',
'const holder = { get prototype() { return Map.prototype; } }; patch(holder); new Map().get("/accounts")',
'function store() { box.child.prototype = Map.prototype; } const box = {}; const inner = {}; box.child = inner; store(); patch(inner); new Map().get("/accounts")',
'function store() { box.child.next.prototype = Map.prototype; } const box = {}; const middle = {}; const inner = {}; middle.next = inner; box.child = middle; store(); patch(inner); new Map().get("/accounts")',
'const records = new Map(); Object.getPrototypeOf(records).get = fetch; new Map().get("/accounts")',
'const getPrototype = Reflect.getPrototypeOf; getPrototype(new Map()).get = fetch; new Map().get("/accounts")',
'patch(() => Map.prototype); new Map().get("/accounts")',
'const factory = { make() { return {}; } }; factory.make = () => Map.prototype; patch(factory.make()); new Map().get("/accounts")',
'function make() { return { prototype: Map.prototype }; } patch(make()); new Map().get("/accounts")',
'const factory = { prototype() { return Map.prototype; } }; patch(factory.prototype()); new Map().get("/accounts")',
'patch([Map.prototype]); new Map().get("/accounts")',
'Object.setPrototypeOf(Map.prototype, custom); new Map().get("/accounts")',
])("keeps unresolved lookup semantics as a candidate: %s", (source) => {
expect(analyzeJavaScriptStaticSource(source).endpoints).toMatchObject([
{ kind: "network", value: "/accounts", interpretation: "candidate" },
]);
});
it.each([
"records.delete = fetch;",
"patch(records);",
"Map.prototype.delete = fetch;",
"const alias = records; alias.__proto__ = { delete: fetch };",
])("retains genuine changes between Map operations: %s", (mutation) => {
const analysis = analyzeJavaScriptStaticSource(
`const records = new Map(); records.get('/before'); ${mutation} records.delete('/after');`,
);
expect(analysis.endpoints).toContainEqual(
expect.objectContaining({ value: "/after", interpretation: "candidate" }),
);
});
it.each(["get", "delete"])(
"does not assume an unchanged constructor adder for Map.%s",
(method) => {
const analysis = analyzeJavaScriptStaticSource(`
Map.prototype.set = function() { this.${method} = fetch; return this; };
new Map([['x', 1]]).${method}('/accounts');
`);
expect(analysis.endpoints).toContainEqual(
expect.objectContaining({
value: "/accounts",
interpretation: "candidate",
}),
);
},
);
it.each([
"box.prototype = Map.prototype;",
'const alias = box; const prototype = Map.prototype; alias["prototype"] = prototype;',
"({ value: box.prototype } = { value: Map.prototype });",
"({ value: box.prototype = Map.prototype } = {});",
"for (box.prototype of [Map.prototype]) {}",
])(
"retains prototype identities stored after container initialization: %s",
(store) => {
const source = `const box = {}; ${store}
function patch(b) { b.prototype.get = fetch; }
patch(box); new Map().get("/accounts");`;
expect(analyzeJavaScriptStaticSource(source).endpoints).toMatchObject([
{ kind: "network", value: "/accounts", interpretation: "candidate" },
]);
},
);
it.each([
['new Headers().get("content-type")', 0],
['new URLSearchParams().delete("q")', 0],
['new Map().get("/accounts")', 0],
['new Map().delete("/accounts")', 0],
['client.get("/accounts")', 0],
['const client = createClient(); client.get("/accounts")', 0],
['const records = new Map(); records.get("/accounts")', 1],
] as const)(
"requests mutation facts only for unresolved local receivers: %s",
(source, pending) => {
const file = parseJavaScriptSource(source);
if (file === null) throw new Error("Expected parsed source");
const index = completeSemanticSteps(
indexJavaScriptBindingsWithReceiversSteps(file.program),
);
completeSemanticSteps(index.indexAmbientWritesSteps());
const accumulator = createJavaScriptAnalysisAccumulator(source.length);
const context = {
source,
accumulator,
vitePreloadDependencyTable: null,
openReceiverFacts: index.receivers.openReceivers,
rpcScan: createJavaScriptRpcScan(
source,
index.receivers.rpcClientRoots,
index,
),
expressionShapes: createSemanticExpressionShapeResolver(index.bindings),
};
traverseJavaScriptAst(file.program, {
enter(node) {
if (
t.isCallExpression(node) ||
t.isOptionalCallExpression(node) ||
t.isNewExpression(node)
)
inspectCall(source, node, context, index.bindings);
},
});
expect(accumulator.pendingNetworkCalls).toHaveLength(pending);
},
);