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
@@ -43,6 +43,7 @@ import type {
|
||||
import { VITE_OUTPUT_DEPENDENCY_PREFIX } from "./javascriptStaticAnalysisTypes.js";
|
||||
import {
|
||||
resolveSemanticBindingState,
|
||||
semanticBindingLookupAt,
|
||||
semanticStableInitializer,
|
||||
type JavaScriptBindingIndex,
|
||||
type JavaScriptSemanticAnalysisState,
|
||||
@@ -55,7 +56,10 @@ import {
|
||||
evaluateSemanticExpression,
|
||||
evaluateSemanticExpressionProvenance,
|
||||
} from "./javascriptSemanticValues.js";
|
||||
import { semanticTopLevelEffectFollowsCapture } from "./javascriptSemanticMutationInitializers.js";
|
||||
import {
|
||||
semanticBindingAtRead,
|
||||
semanticTopLevelEffectFollowsCapture,
|
||||
} from "./javascriptSemanticMutationInitializers.js";
|
||||
import {
|
||||
semanticFactoryLoaderMemberAt,
|
||||
semanticRequireCallAt,
|
||||
@@ -411,7 +415,7 @@ const chunkLoadSpecifier = (
|
||||
const vitePreloadDependencySpecifiers = (
|
||||
node: JavaScriptCallLike,
|
||||
name: string,
|
||||
table: readonly (string | undefined)[] | null,
|
||||
table: FindingContext["vitePreloadDependencyTable"],
|
||||
bindings: JavaScriptSemanticAnalysisState,
|
||||
): readonly (string | undefined)[] => {
|
||||
if (
|
||||
@@ -420,10 +424,23 @@ const vitePreloadDependencySpecifiers = (
|
||||
!t.isArrayExpression(node.arguments[0])
|
||||
)
|
||||
return [];
|
||||
const callee = unwrapJavaScriptExpression(node.callee).node;
|
||||
const lookup = semanticBindingLookupAt(bindings, callee, name);
|
||||
if (lookup.kind !== "binding" && lookup.kind !== "uncertain-binding")
|
||||
return [];
|
||||
const projected = semanticBindingAtRead(lookup.binding, callee, bindings);
|
||||
// An ordered read drops assignments that have not happened yet. An
|
||||
// unresolved read keeps the single stable initializer, which is how a
|
||||
// captured call still names the helper when nothing else writes it.
|
||||
const visible =
|
||||
projected.status === "ordered" ? projected.binding : lookup.binding;
|
||||
if (semanticStableInitializer(visible, bindings)?.node !== table.helper)
|
||||
return [];
|
||||
const indexes = node.arguments[0].elements;
|
||||
// Explicit cache overrides change which table the helper reads. Retain the
|
||||
// call slots as unknown unless every override is statically undefined.
|
||||
if (
|
||||
lookup.kind !== "binding" ||
|
||||
!node.arguments
|
||||
.slice(1)
|
||||
.every((argument) => isExplicitDefaultArgument(argument, bindings))
|
||||
@@ -433,7 +450,7 @@ const vitePreloadDependencySpecifiers = (
|
||||
if (element === null || !t.isNumericLiteral(element)) return undefined;
|
||||
const index = element.value;
|
||||
return Number.isSafeInteger(index) && index >= 0
|
||||
? viteDependencySpecifier(table[index])
|
||||
? viteDependencySpecifier(table.dependencies[index])
|
||||
: undefined;
|
||||
});
|
||||
};
|
||||
@@ -473,8 +490,9 @@ type ViteMapDepsFunction =
|
||||
/** Index the canonical Vite helper's cache initializer from parsed syntax. */
|
||||
export const indexVitePreloadDependencyTable = (
|
||||
program: t.Program,
|
||||
erasedSyntaxNodes: WeakSet<t.Node>,
|
||||
): readonly (string | undefined)[] | null => {
|
||||
bindings: JavaScriptSemanticAnalysisState,
|
||||
): FindingContext["vitePreloadDependencyTable"] => {
|
||||
const { erasedSyntaxNodes } = bindings;
|
||||
const helpers: ViteMapDepsFunction[] = [];
|
||||
traverseJavaScriptValueAst(program, erasedSyntaxNodes, {
|
||||
enter: (node) => {
|
||||
@@ -497,7 +515,7 @@ export const indexVitePreloadDependencyTable = (
|
||||
const mapIdentifier = t.isAssignmentPattern(mapParameter)
|
||||
? mapParameter.left
|
||||
: mapParameter;
|
||||
if (!t.isIdentifier(mapIdentifier)) return [];
|
||||
if (!t.isIdentifier(mapIdentifier)) return { helper, dependencies: [] };
|
||||
const tables: t.ArrayExpression[] = [];
|
||||
traverseJavaScriptValueAst(helper, erasedSyntaxNodes, {
|
||||
skip: (node) => t.isFunction(node),
|
||||
@@ -515,9 +533,9 @@ export const indexVitePreloadDependencyTable = (
|
||||
tables.push(node.right);
|
||||
},
|
||||
});
|
||||
if (tables.length !== 1) return [];
|
||||
if (tables.length !== 1) return { helper, dependencies: [] };
|
||||
const table = tables[0];
|
||||
if (table === undefined) return [];
|
||||
if (table === undefined) return { helper, dependencies: [] };
|
||||
// A spread contributes an unknown number of entries, so every later slot
|
||||
// has an unknown index; leave those out instead of shifting them.
|
||||
const spread = table.elements.findIndex((element) =>
|
||||
@@ -540,6 +558,17 @@ export const indexVitePreloadDependencyTable = (
|
||||
semanticIdentifierRole(node, parent, readAncestors) === null
|
||||
)
|
||||
return;
|
||||
const lookup = semanticBindingLookupAt(bindings, node, node.name);
|
||||
if (lookup.kind === "ambient" || lookup.kind === "captured") return;
|
||||
if (lookup.kind === "binding" || lookup.kind === "uncertain-binding") {
|
||||
// A later reassignment removes the whole-binding initializer. The
|
||||
// mutation is still the helper when the ordered read at this site is.
|
||||
const projected = semanticBindingAtRead(lookup.binding, node, bindings);
|
||||
const visible =
|
||||
projected.status === "ordered" ? projected.binding : lookup.binding;
|
||||
if (semanticStableInitializer(visible, bindings)?.node !== helper)
|
||||
return;
|
||||
}
|
||||
if (
|
||||
(t.isCallExpression(parent) || t.isOptionalCallExpression(parent)) &&
|
||||
parent.callee === node
|
||||
@@ -548,7 +577,12 @@ export const indexVitePreloadDependencyTable = (
|
||||
escapedOrMutated = true;
|
||||
},
|
||||
});
|
||||
return escapedOrMutated ? dependencies.map(() => undefined) : dependencies;
|
||||
return {
|
||||
helper,
|
||||
dependencies: escapedOrMutated
|
||||
? dependencies.map(() => undefined)
|
||||
: dependencies,
|
||||
};
|
||||
};
|
||||
|
||||
const requestConstructorUrl = (
|
||||
|
||||
Reference in new issue
Block a user