fix(javascript): exclude overwritten Electron API identities (#1664)

* fix(javascript): exclude overwritten Electron API identities

* test(javascript): bind Electron in file-base path fixture

* test(electron): bind BrowserWindow in preload override fixtures

* test(mcp): allow cold catalog validation in runtime probe

* test(electron): bind APIs in option reconstruction fixtures
This commit is contained in:
Ysopwnable authored and GitHub committed 2026-10-11 09:25:32 +08:00
1 parent ad41afa2b5
commit 29cc1b8746
13 files changed
+434 -62

No files matched your search

@@ -44,6 +44,11 @@ import {
} from "./javascriptSemanticReturns.js";
import { collectJavaScriptDerivedSemanticsSteps } from "./javascriptSemanticDerivedAnalysis.js";
import { evaluateSemanticProvenance } from "./javascriptSemanticValues.js";
import {
collectElectronMemberWritesSteps,
electronMemberPath,
ELECTRON_MODULE,
} from "./javascriptElectronMemberWrites.js";
import { range } from "./javascriptStaticAnalysisHelpers.js";
import { propertyName } from "./javascriptAstValues.js";
import { semanticCoverage } from "./javascriptSemanticCoverage.js";
@@ -311,8 +316,6 @@ const resolveGlobalAliasFact = (
return openGlobalFact(name);
};
const ELECTRON_MODULE = /^electron(?:\/(?:common|main|renderer|utility))?$/u;
/**
* Proven Electron export path segments for call and construction roots, keyed by
* root offset. Only proven Electron origins are present; callers must not
@@ -331,6 +334,27 @@ export function* classifyParsedJavaScriptElectronBindingsSteps(
const state = createState(file.program);
yield* collectDefinitionsSteps(file.program, state);
const exportsByBinding = new Map<string, readonly string[] | null>();
const rootExport = (root: t.Node): readonly string[] | undefined => {
if (t.isIdentifier(root)) {
if (semanticResolutionBlocked(state, root, root.name)) return undefined;
const binding = resolveSemanticBindingState(state, root, root.name);
if (binding === undefined) return undefined;
let exported = exportsByBinding.get(binding.bindingId);
if (exported === undefined) {
exported = electronExport(binding, state);
exportsByBinding.set(binding.bindingId, exported);
}
return exported ?? undefined;
}
const origin = semanticRequireOrigin(root, state);
return origin !== undefined && ELECTRON_MODULE.test(origin.specifier)
? origin.importedPath
: undefined;
};
const writes = yield* collectElectronMemberWritesSteps(
file.program,
rootExport,
);
yield* traverseJavaScriptAstSteps(file.program, {
enter: (node) => {
if (
@@ -339,35 +363,15 @@ export function* classifyParsedJavaScriptElectronBindingsSteps(
!t.isNewExpression(node)
)
return;
const root = calleeRoot(node.callee);
if (root === undefined) return;
if (t.isIdentifier(root)) {
const binding = resolveSemanticBindingState(state, root, root.name);
if (binding === undefined) return;
let exported = exportsByBinding.get(binding.bindingId);
if (exported === undefined) {
exported = electronExport(binding, state);
exportsByBinding.set(binding.bindingId, exported);
}
if (exported !== null) facts.set(root.start ?? -1, exported);
return;
}
const origin = semanticRequireOrigin(root, state);
if (origin !== undefined && ELECTRON_MODULE.test(origin.specifier))
facts.set(root.start ?? -1, origin.importedPath);
const { root, members } = electronMemberPath(node.callee);
const exported = rootExport(root);
if (exported !== undefined && !writes.covers([...exported, ...members]))
facts.set(root.start ?? -1, exported);
},
});
return facts;
}
const calleeRoot = (node: t.Node): t.Node | undefined => {
while (t.isMemberExpression(node) || t.isOptionalMemberExpression(node)) {
if (!t.isNode(node.object)) return undefined;
node = node.object;
}
return node;
};
// Read import and require origins directly; general value evaluation is
// unnecessary here and costly across large vendor bundles.
const electronExport = (