mirror of
https://github.com/morluto/rea.git
synced 2026-11-15 11:13:21 +00:00
fix(javascript): preserve semantic effects and graph evidence (#9900)
* fix(javascript): preserve semantic facts across writes and implicit effects * fix(javascript): preserve bundler identities and helper bindings * fix(javascript): retain semantic graph evidence and coverage * test(javascript): include the request link in endpoint traces Response consumers now also link the originating request to the response, so an endpoint trace reports both that association and the response's json consumer. * fix(javascript): keep source accessors known after a copied value escapes An unknown computed getter makes accessor lookup consider every property. A spread copy already records a value escape, and that escape was treated as an unknown getter on the source, so the next read escaped the whole object. Accessor lookup still fails closed for unknown writes and calls. * fix(javascript): keep Object.defineProperty identified at its own call The call that escapes Object was treated as a prior replacement of defineProperty, so a descriptor value never replaced the exported sender and bundled IPC paths were dropped. * fix(javascript): keep Vite preload calls that precede reassignment Helper identity used every write to the binding, so a later assignment erased a call that had already read the canonical helper. Use the binding value at the call when that order is known. * fix(javascript): apply Vite cache mutations before a later reassignment A later helper write made the whole-binding initializer check ignore an earlier cache mutation. Identity at that mutation uses the same ordered read as the call. --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com>
This commit is contained in:
1 parent
37d2d207d2
commit
a896e35c47
44 files changed
+2090
-609
No files matched your search
@@ -4,6 +4,7 @@ import { expect, it } from "vitest";
|
||||
import { indexJavaScriptBindingsSteps } from "./javascriptSemanticAnalysis.js";
|
||||
import {
|
||||
semanticClassPropertyReferences,
|
||||
semanticNativePrototypeChain,
|
||||
semanticObjectPropertyReferences,
|
||||
} from "./javascriptSemanticObjectReferences.js";
|
||||
import { completeSemanticSteps } from "./javascriptSemanticTraversal.js";
|
||||
@@ -36,6 +37,37 @@ const spelled = (
|
||||
],
|
||||
);
|
||||
|
||||
it.each([
|
||||
["{}", "then", ["Object"]],
|
||||
["{then:null}", "then", []],
|
||||
["{then}", "then", []],
|
||||
["{...{then:null}}", "then", []],
|
||||
["{...unknown}", "then", ["Object"]],
|
||||
["{__proto__:null}", "then", []],
|
||||
["{__proto__:{}}", "then", []],
|
||||
["{__proto__:0}", "then", ["Object"]],
|
||||
["{__proto__:unknown}", "then", ["Object"]],
|
||||
['{["__proto__"]:null}', "then", ["Object"]],
|
||||
["[]", "then", ["Array", "Object"]],
|
||||
["[]", "length", []],
|
||||
["[value]", "0", []],
|
||||
["[,value]", "0", ["Array", "Object"]],
|
||||
["[value,...unknown]", "0", []],
|
||||
["[...unknown,value]", "0", ["Array", "Object"]],
|
||||
] as const)(
|
||||
"retains possible native prototype lookup for %s[%s]",
|
||||
(value, key, expected) => {
|
||||
const parsed = parseJavaScriptSource(`const o=${value};`, "prototype.mjs");
|
||||
const declaration = parsed?.program.body[0];
|
||||
if (!t.isVariableDeclaration(declaration))
|
||||
throw new Error("Expected declaration");
|
||||
const node = declaration.declarations[0]?.init;
|
||||
if (!t.isObjectExpression(node) && !t.isArrayExpression(node))
|
||||
throw new Error("Expected literal");
|
||||
expect(semanticNativePrototypeChain(node, key)).toEqual(expected);
|
||||
},
|
||||
);
|
||||
|
||||
it("resolves exact keys with source-order overrides, spreads and accessors", () => {
|
||||
const fixture = objectAndState(
|
||||
`const o = { a: 1, ...base, b: 2, a: 3, [dynamic]: 4, __proto__: proto, ...{ b: 5 }, get c() { return 6; }, c: 7 };`,
|
||||
@@ -54,14 +86,13 @@ it("resolves exact keys with source-order overrides, spreads and accessors", ()
|
||||
["4", "[]"],
|
||||
["base", '["missing"]'],
|
||||
]);
|
||||
// A wildcard key still scans every property.
|
||||
// A wildcard keeps possible slots, excluding data replaced by a known spread.
|
||||
expect(spelled(fixture, null)).toEqual([
|
||||
["7", "[]"],
|
||||
["{ b: 5 }", '[{"excludedKeys":["c"]}]'],
|
||||
["proto", "[null]"],
|
||||
["4", "[]"],
|
||||
["3", "[]"],
|
||||
["2", "[]"],
|
||||
["base", '[{"excludedKeys":["a","b","c"]}]'],
|
||||
]);
|
||||
// Numeric keys use the same exact spelling as string keys.
|
||||
@@ -70,6 +101,112 @@ it("resolves exact keys with source-order overrides, spreads and accessors", ()
|
||||
).toEqual([['"one"', "[]"]]);
|
||||
});
|
||||
|
||||
it.each([1, "1"])(
|
||||
"preserves inherited numeric property selection %j and own shadowing",
|
||||
(key) => {
|
||||
expect(
|
||||
spelled(objectAndState("const o = { __proto__: prototype };"), key),
|
||||
).toEqual([["prototype", JSON.stringify([String(key)])]]);
|
||||
expect(
|
||||
spelled(
|
||||
objectAndState('const o = { __proto__: prototype, 1: "own" };'),
|
||||
key,
|
||||
),
|
||||
).toEqual([['"own"', "[]"]]);
|
||||
},
|
||||
);
|
||||
|
||||
it.each([
|
||||
[
|
||||
"static",
|
||||
"static method(){return 1;} method(){return 2;}",
|
||||
"static method(){return 1;}",
|
||||
],
|
||||
[
|
||||
"instance",
|
||||
"static method(){return 1;} method(){return 2;}",
|
||||
"method(){return 2;}",
|
||||
],
|
||||
[
|
||||
"prototype",
|
||||
"static method(){return 1;} method(){return 2;}",
|
||||
"method(){return 2;}",
|
||||
],
|
||||
["static", "static method(){return 1;} static set method(value){}", null],
|
||||
["static", "static method=child; static method(){return 1;}", "child"],
|
||||
[
|
||||
"static",
|
||||
"static get method(){return child;} static method(){return 1;}",
|
||||
"static method(){return 1;}",
|
||||
],
|
||||
[
|
||||
"static",
|
||||
"static method(){return 1;} static get method(){return child;}",
|
||||
"child",
|
||||
],
|
||||
[
|
||||
"static",
|
||||
"static get method(){return child;} static method(){return 1;} static set method(value){}",
|
||||
null,
|
||||
],
|
||||
] as const)(
|
||||
"selects method-valued %s descriptors with runtime precedence: %s",
|
||||
(kind, body, expected) => {
|
||||
const source = `class Box { ${body} }`;
|
||||
const parsed = parseJavaScriptSource(source, "methods.mjs");
|
||||
if (parsed === null) throw new Error("Expected a parsed class");
|
||||
const owner = parsed.program.body[0];
|
||||
if (!t.isClassDeclaration(owner)) throw new Error("Expected a class");
|
||||
const state = completeSemanticSteps(
|
||||
indexJavaScriptBindingsSteps(parsed.program),
|
||||
);
|
||||
expect(
|
||||
semanticClassPropertyReferences({ owner, kind }, ["method"], state).map(
|
||||
({ node }) => source.slice(node.start ?? 0, node.end ?? 0),
|
||||
),
|
||||
).toEqual(expected === null ? [] : [expected]);
|
||||
},
|
||||
);
|
||||
|
||||
it.each(["method(){return 1;}", "...{method: child}"])(
|
||||
"keeps an intervening object data definition between accessor pairs: %s",
|
||||
(definition) => {
|
||||
expect(
|
||||
spelled(
|
||||
objectAndState(
|
||||
`const o = { get method(){return child;} , ${definition}, set method(value){} };`,
|
||||
),
|
||||
"method",
|
||||
),
|
||||
).toEqual([]);
|
||||
},
|
||||
);
|
||||
|
||||
it.each(["static", "instance"] as const)(
|
||||
"retains inherited %s field ownership for enumerable copies",
|
||||
(kind) => {
|
||||
const source = `class Base { ${kind === "static" ? "static" : ""} method=child; } class Box extends Base {}`;
|
||||
const parsed = parseJavaScriptSource(source, "fields.mjs");
|
||||
if (parsed === null) throw new Error("Expected a parsed class");
|
||||
const owner = parsed.program.body[1];
|
||||
if (!t.isClassDeclaration(owner)) throw new Error("Expected derived class");
|
||||
const state = completeSemanticSteps(
|
||||
indexJavaScriptBindingsSteps(parsed.program),
|
||||
);
|
||||
const references = semanticClassPropertyReferences(
|
||||
{ owner, kind, fieldsOnly: true },
|
||||
["method"],
|
||||
state,
|
||||
);
|
||||
expect(
|
||||
references.map(({ node }) =>
|
||||
source.slice(node.start ?? 0, node.end ?? 0),
|
||||
),
|
||||
).toEqual(["child"]);
|
||||
expect(references[0]?.inherited).toBe(kind === "static" ? true : undefined);
|
||||
},
|
||||
);
|
||||
|
||||
it("preserves references through a small static getter permutation", () => {
|
||||
const getters = Array.from(
|
||||
{ length: 5 },
|
||||
|
||||
Reference in new issue
Block a user