mirror of
https://github.com/morluto/rea.git
synced 2026-10-28 02:15:44 +00:00
fix(javascript): distinguish Map lookups from network endpoints
Resolve unchanged Map operations through shared binding, capture, mutation, and ambient escape facts. Reuse that proof in application and captured bundle analysis; unresolved get/delete receivers remain inferred candidates. Preserve repeated native operations without manufacturing receiver escapes. Retain uncertainty for real escapes, opaque eval, and changed constructor adder hooks. Converge stored aliases at the producing boundary. Adapt the JavaScript changes from #4863, excluding its Linux resource fix. Add captured-bundle parity, repeated-operation and constructor-hook coverage. Fixes #4853 Co-authored-by: abdooo2235 <abdalrhmantheiv15@gmail.com>
This commit is contained in:
1 parent
e6c487799b
commit
fbfdb6abe9
16 files changed
+1021
-73
No files matched your search
@@ -0,0 +1,197 @@
|
||||
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 {
|
||||
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,
|
||||
),
|
||||
};
|
||||
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);
|
||||
},
|
||||
);
|
||||
Reference in new issue
Block a user