Files
reverse-engineering-anything/src/domain/javascript/javascriptStaticAnalysisState.ts
T
morlutoandCursor Agent a896e35c47 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>
2026-11-11 17:33:47 +08:00

219 lines
8.3 KiB
TypeScript

import type * as t from "@babel/types";
import type { JavaScriptBundlerFactoryRoles } from "./javascriptBundlerFactoryRoles.js";
import type { JavaScriptOpenReceiverFact } from "./javascriptSemanticReceiverFacts.js";
import type { JavaScriptRpcScan } from "./javascriptRpcEndpoints.js";
import type { JavaScriptStaticPathIntrinsicQuery } from "./javascriptStaticAnalysisHelpers.js";
import type { JavaScriptExpressionShapeResolver } from "./javascriptSemanticExpressionShapes.js";
import type {
JavaScriptBundlerRegistration,
JavaScriptRolePath,
JavaScriptStaticAnalysis,
JavaScriptStaticEndpoint,
JavaScriptStaticReference,
JavaScriptStaticStorage,
} from "./javascriptStaticAnalysisTypes.js";
import type {
ElectronAppPathChangeFinding,
ElectronCallPath,
ElectronBrowserWindowFinding,
ElectronContextBridgeFinding,
ElectronIpcFinding,
ElectronNativeAddonBindingFinding,
ElectronSenderValidationFinding,
ElectronProtocolFinding,
ElectronDynamicCodeFinding,
ElectronUtilityProcessFinding,
ElectronWindowConstructor,
} from "./electronStaticAnalysisTypes.js";
import { JavaScriptModuleRangeIndex } from "./javascriptStaticAnalysisFindings.js";
/** Source offsets for one recovered bundle module factory. */
export interface JavaScriptModuleRange {
readonly start: number;
readonly end: number;
readonly key: string;
readonly runtime: string;
readonly requireName: string | null;
}
/** Static finding retained with its source offset until module attribution. */
export interface LocatedJavaScriptFinding<Value> {
readonly offset: number;
readonly value: Value;
}
/** Mutable collections used during one inert AST traversal. */
export interface JavaScriptAnalysisAccumulator {
readonly pendingBundlerExports: {
readonly factory: t.Node;
readonly roles: JavaScriptBundlerFactoryRoles;
readonly exports: string[];
}[];
/** Factory parameter defaults the bundler calling convention never runs. */
readonly unusedFactoryDefaults: Set<t.Node>;
readonly pendingModuleRequireCalls: (
| t.CallExpression
| t.OptionalCallExpression
| t.NewExpression
)[];
readonly appPathChanges: LocatedJavaScriptFinding<ElectronAppPathChangeFinding>[];
electronCallPaths?: readonly ElectronCallPath[];
electronCallPathLimits?: Set<"wrapper_visits" | "wrapper_depth">;
readonly references: LocatedJavaScriptFinding<JavaScriptStaticReference>[];
readonly endpoints: LocatedJavaScriptFinding<JavaScriptStaticEndpoint>[];
/** Target and receiver identities awaiting the shared mutation index. */
readonly pendingNetworkCalls: {
readonly node:
| t.CallExpression
| t.OptionalCallExpression
| t.NewExpression;
readonly name: string;
}[];
readonly pendingSqliteCalls: {
readonly node:
| t.CallExpression
| t.OptionalCallExpression
| t.NewExpression;
readonly name: string;
}[];
readonly storage: LocatedJavaScriptFinding<JavaScriptStaticStorage>[];
readonly roles: LocatedJavaScriptFinding<JavaScriptRolePath>[];
readonly sourceMaps: JavaScriptStaticAnalysis["source_map_urls"][number][];
readonly registrations: JavaScriptBundlerRegistration[];
readonly browserWindows: LocatedJavaScriptFinding<ElectronBrowserWindowFinding>[];
readonly pendingBrowserWindows: {
readonly node: t.NewExpression;
readonly constructor: ElectronWindowConstructor;
}[];
readonly contextBridgeApis: LocatedJavaScriptFinding<ElectronContextBridgeFinding>[];
readonly ipc: LocatedJavaScriptFinding<ElectronIpcFinding>[];
readonly senderValidations: LocatedJavaScriptFinding<ElectronSenderValidationFinding>[];
readonly utilityProcesses: LocatedJavaScriptFinding<ElectronUtilityProcessFinding>[];
readonly nativeAddonBindings: LocatedJavaScriptFinding<ElectronNativeAddonBindingFinding>[];
readonly pendingNativeAddonBindings: (
| t.VariableDeclarator
| t.AssignmentExpression
)[];
readonly protocols: LocatedJavaScriptFinding<ElectronProtocolFinding>[];
readonly dynamicCode: LocatedJavaScriptFinding<ElectronDynamicCodeFinding>[];
readonly pendingDynamicCode: (t.NewExpression | t.CallExpression)[];
readonly moduleRangeIndex: JavaScriptModuleRangeIndex;
/** Binding identities proven as third-parameter loaders of recovered factories. */
readonly factoryRequireBindingIds: Set<string>;
readonly seen: Set<string>;
bundlerExportHelpersObserved: boolean;
visitedNodes: number;
unknownFindings: number;
}
/** Shared source and accumulator for helper inspections. */
export interface JavaScriptFindingContext {
/** Native path/URL identities established by shared lexical and mutation owners. */
readonly pathIntrinsics?: JavaScriptStaticPathIntrinsicQuery;
readonly electronExportIntact?: (path: readonly string[]) => boolean;
readonly browserWindowReceiver?: (
node: t.Node,
site: t.Node,
suffix: readonly string[],
) =>
| {
readonly owner: t.NewExpression;
readonly path: readonly string[];
readonly authority: { readonly loader_authority?: "candidate" };
}
| undefined;
readonly xhrOpenRequest?: (node: t.Node) => boolean;
readonly rendererLoad?: (
callee: t.Node,
site: t.Node,
) => { readonly loader_authority?: "candidate" } | undefined;
readonly rpcScan: JavaScriptRpcScan;
/** Shared bounded argv/nullish argument shapes for optional-slot signatures. */
readonly expressionShapes: JavaScriptExpressionShapeResolver;
readonly source: string;
readonly accumulator: JavaScriptAnalysisAccumulator;
readonly vitePreloadDependencyTable: {
readonly helper: t.Node;
readonly dependencies: readonly (string | undefined)[];
} | null;
readonly openReceiverFacts?: ReadonlyMap<number, JavaScriptOpenReceiverFact>;
/** Proven Electron export path segments by root offset. */
readonly electronBindings?: ReadonlyMap<number, readonly string[]>;
/** Conditional Electron export paths, keyed by call/construction root. */
readonly electronCandidateBindings?: ReadonlyMap<number, readonly string[]>;
/** Normalized invocation target from the shared Electron and intrinsic owners. */
readonly electronInvocationPath?: (
node: t.CallExpression | t.OptionalCallExpression,
) => readonly string[] | undefined;
/** A possible shared-app mutation does not establish concrete API identity. */
readonly electronAppPathChangePossible?: (node: t.Node) => boolean;
readonly electronCandidateAppPathChangePossible?: (node: t.Node) => boolean;
}
/** Candidate import, require, worker, or service-worker reference. */
export interface JavaScriptReferenceInput {
readonly operation?: JavaScriptStaticReference["operation"];
readonly node: t.Node;
readonly kind: JavaScriptStaticReference["kind"];
readonly specifier: string | undefined;
readonly resolutionBasePath?: string;
readonly loader?: JavaScriptStaticReference["loader"];
readonly loaderAuthority?: "candidate";
}
/** Candidate route or network endpoint literal. */
export interface JavaScriptEndpointInput {
readonly node: t.Node;
readonly kind: JavaScriptStaticEndpoint["kind"];
readonly value: string;
readonly mechanism: string;
readonly interpretation?: "candidate";
readonly resolution?: "partial";
}
/** Values required to append one module-attributable static finding. */
export interface LocatedJavaScriptFindingInput<
Value extends { readonly module_key: string | null },
> {
readonly collection: LocatedJavaScriptFinding<Value>[];
readonly key: string;
readonly node: t.Node;
readonly value: Value;
}
/** Create isolated mutable state for one source analysis. */
export const createJavaScriptAnalysisAccumulator = (
sourceLength: number,
): JavaScriptAnalysisAccumulator => ({
pendingBundlerExports: [],
unusedFactoryDefaults: new Set(),
pendingModuleRequireCalls: [],
appPathChanges: [],
references: [],
endpoints: [],
pendingNetworkCalls: [],
storage: [],
pendingSqliteCalls: [],
roles: [],
sourceMaps: [],
registrations: [],
browserWindows: [],
pendingBrowserWindows: [],
contextBridgeApis: [],
ipc: [],
senderValidations: [],
utilityProcesses: [],
nativeAddonBindings: [],
pendingNativeAddonBindings: [],
protocols: [],
dynamicCode: [],
pendingDynamicCode: [],
moduleRangeIndex: new JavaScriptModuleRangeIndex(sourceLength),
factoryRequireBindingIds: new Set(),
seen: new Set(),
bundlerExportHelpersObserved: false,
visitedNodes: 0,
unknownFindings: 0,
});