fix(javascript): preserve builtin identity and share alias traversal

Track module writes and escapes at their owner, preserve capture and reassignment ordering, and share CommonJS/default namespace identity without erasing parent-slot changes. Carry write sites into ambient alias resolution and use parsed write targets across consumers. Deduplicate returned-reference work with cycle context; retain unchanged temporal selection identity so provenance memoization stays effective.

Reviewed fixes include #2041, #2465, #2830, #2881, and #2948. Remove superseded mutation representations and the ineffective per-markValue dedup layer.

Validation: 380 focused JavaScript tests passed under a 384 MiB heap cap, including the previously aborting 30-level/128-call alias regression. Six public CLI/MCP builtin tests passed. Exact pdf.js completed-workflow performance remains unmeasured.
This commit is contained in:
morluto committed 2026-10-14 08:44:52 +08:00
1 parent 0f4c484be5
commit 04672f2b91
34 files changed
+3778 -321

No files matched your search

@@ -1,15 +1,17 @@
import * as t from "@babel/types";
import { semanticBuiltinNamespacePath } from "./javascriptSemanticProvenance.js";
import type {
JavaScriptSemanticCallable,
JavaScriptSemanticChildProcessInteraction,
JavaScriptSemanticChildProcessSpawn,
} from "./javascriptSemanticIr.js";
import { semanticCallableIdForNode } from "./javascriptSemanticProjection.js";
import { semanticStaticPropertyName } from "./javascriptAstValues.js";
import {
semanticStaticPropertyName,
unwrapJavaScriptExpression,
} from "./javascriptAstValues.js";
import {
isUnshadowedGlobal,
resolveSemanticBindingState,
type JavaScriptSemanticAnalysisState,
type JavaScriptSemanticBindingState,
@@ -18,6 +20,7 @@ import { compareUnicodeCodePoints } from "../unicodeCodePointOrder.js";
import { traverseJavaScriptAst } from "./javascriptSemanticTraversal.js";
import { range } from "./javascriptStaticAnalysisHelpers.js";
import {
builtinDataEffectMethod,
dataEffectLiteralString,
dataEffectMemberCallee,
assignedSemanticResultBindings,
@@ -222,9 +225,7 @@ const childInteraction = (
.sort(compareUnicodeCodePoints),
eventName: kind === "listener" ? eventName : null,
signalName:
kind === "signal"
? (dataEffectLiteralString(node.arguments[0]) ?? "SIGTERM")
: null,
kind === "signal" ? killSignalName(node.arguments, state) : null,
listenerLocation:
kind === "listener" && t.isNode(listener) ? range(listener) : null,
resolution:
@@ -237,6 +238,23 @@ const childInteraction = (
};
};
// Only an omitted argument or the unshadowed global undefined selects the
// SIGTERM default; an unresolved argument leaves the signal unknown.
const killSignalName = (
args: t.CallExpression["arguments"],
state: JavaScriptSemanticAnalysisState,
): string | null => {
const [signal] = args;
if (signal === undefined) return "SIGTERM";
const expression = unwrapJavaScriptExpression(signal).node;
if (
t.isUnaryExpression(expression, { operator: "void" }) ||
isUnshadowedGlobal(expression, state, "undefined")
)
return "SIGTERM";
return dataEffectLiteralString(signal);
};
const classifyInteraction = (
method: string,
argument:
@@ -266,52 +284,15 @@ const childProcessMethod = (
callee: t.Node,
state: JavaScriptSemanticAnalysisState,
): JavaScriptSemanticChildProcessSpawn["method"] | null => {
if (t.isIdentifier(callee)) {
const binding = resolveSemanticBindingState(state, callee, callee.name);
return binding === undefined ? null : methodFromBinding(binding);
}
if (!t.isMemberExpression(callee) && !t.isOptionalMemberExpression(callee))
return null;
const method = admittedChildMethod(
semanticStaticPropertyName(callee.property, callee.computed),
const method = builtinDataEffectMethod(callee, state, [
"child_process",
"node:child_process",
]);
return (
CHILD_PROCESS_METHODS.find((candidate) => candidate === method) ?? null
);
if (method === null || !t.isIdentifier(callee.object)) return null;
const binding = resolveSemanticBindingState(
state,
callee.object,
callee.object.name,
);
return childProcessNamespace(binding) ? method : null;
};
const methodFromBinding = (
binding: JavaScriptSemanticBindingState,
): JavaScriptSemanticChildProcessSpawn["method"] | null => {
for (const { specifier, importedPath } of binding.directOrigins) {
if (!childProcessModule(specifier)) continue;
const method = admittedChildMethod(importedPath.at(-1) ?? "");
if (method !== null) return method;
}
return null;
};
const admittedChildMethod = (
value: string,
): JavaScriptSemanticChildProcessSpawn["method"] | null =>
CHILD_PROCESS_METHODS.find((candidate) => candidate === value) ?? null;
const childProcessNamespace = (
binding: JavaScriptSemanticBindingState | undefined,
): boolean =>
binding?.directOrigins.some(
({ specifier, importedPath }) =>
childProcessModule(specifier) &&
semanticBuiltinNamespacePath(importedPath),
) ?? false;
const childProcessModule = (specifier: string): boolean =>
specifier === "child_process" || specifier === "node:child_process";
const resolveChildCandidates = (
node: t.Node,
binding: JavaScriptSemanticBindingState | undefined,