fix(javascript): preserve loader authority for exact suffixes (#9477)

* fix(javascript): require exact loader suffix case

* fix(javascript): share exact loader mode across import resolution

* test(process): align symlink fixture with filesystem checkpoints

* test(javascript): normalize the module resolution oracle path separators

* test(frida): assert the CLI spawn Evidence contract

* test(mcp): use an unsupported analysis-view parent

* test(browser): separate source-location line bounds from parity

* fix(javascript): preserve uncertain loader provenance

* test(javascript): cover uncertain require resolution

* fix(javascript): retain conditional Electron evidence

* fix(javascript): preserve timed semantic provenance

* fix(javascript): retain conditional builtin effects

* test(javascript): distinguish bundled host authority

* fix(javascript): preserve nested loader invocation identity

* test(javascript): qualify Electron fixture authority

* fix(javascript): preserve loader invocation authority

* fix(javascript): respect source order for resolver escapes

* fix(javascript): check nested loader callable mutations

* fix(javascript): retain conditional constructor requests

* fix(reference): retain constructor loader relationships

* fix(reference): follow nested loader callable paths

* refactor(javascript): share callable mutation interpretation

* fix(javascript): require exact package loader suffixes

* refactor(javascript): unify conditional relation evidence

* refactor(javascript): unify conditional node evidence

* fix(javascript): retain unknown authority for mixed case sources

* fix(javascript): preserve loader uncertainty for dotfiles

* fix(reference): classify javascript suffix dotfiles

* test(reference): cover suffix dotfiles through cli

* fix(javascript): retain equivalent conditional built-in origins

* fix(javascript): preserve wrapped resolver operation

* fix(javascript): retain nested conditional built-in receivers

* test(javascript): declare CommonJS in native addon fixtures

* fix(javascript): retain wrapped resolver candidates

* style: format merged native command schema

* fix(javascript): preserve uncertain loader authority

* test(javascript): require candidate preload evidence

* test(javascript): cover reference loader member writes

---------

Co-authored-by: ShuaixinHuang <1836656514@qq.com>
This commit is contained in:
morlutoandShuaixinHuang authored and GitHub committed 2026-11-09 13:21:24 +08:00
1 parent 78a84d75c0
commit b46f4fcebd
112 files changed
+5156 -797

No files matched your search

@@ -67,12 +67,17 @@ import {
} from "./javascriptSemanticTraversal.js";
import {
semanticRequireOrigin,
semanticRequireOriginCandidate,
observePossibleCommonJsDelegateWrite,
invalidateSemanticRequireDelegateWrites,
} from "./javascriptSemanticRequireOrigin.js";
import { semanticBundledRequireOrigin } from "./javascriptBundledRequireOrigin.js";
import {
semanticBundledRequireOrigin,
semanticBundledRequireOriginCandidate,
} from "./javascriptBundledRequireOrigin.js";
import { collectSemanticMemberMutationsSteps } from "./javascriptSemanticMemberMutations.js";
import {
semanticBindingAtRead,
semanticEffectTiming,
semanticEffectTimingWithinActivation,
} from "./javascriptSemanticMutationInitializers.js";
@@ -844,12 +849,16 @@ const resolveGlobalAliasFact = (
export interface JavaScriptElectronIdentity {
/** Proven export paths keyed by call/construction root offset; absence is unknown. */
readonly bindings: ReadonlyMap<number, readonly string[]>;
/** Conditional export paths whose require loader is not established. */
readonly candidateBindings: ReadonlyMap<number, readonly string[]>;
readonly intactExport: (path: readonly (string | null)[]) => boolean;
readonly exportPath: (root: t.Node) => readonly string[] | undefined;
readonly candidateExportPath: (root: t.Node) => readonly string[] | undefined;
readonly invocationPath: (
node: t.CallExpression | t.OptionalCallExpression,
) => readonly string[] | undefined;
readonly mayChangeAppPath: (node: t.Node) => boolean;
readonly candidateMayChangeAppPath: (node: t.Node) => boolean;
}
export function* indexJavaScriptElectronIdentitySteps(
@@ -857,15 +866,33 @@ export function* indexJavaScriptElectronIdentitySteps(
state: JavaScriptSemanticAnalysisState,
): Generator<void, JavaScriptElectronIdentity> {
const facts = new Map<number, readonly string[]>();
const candidateFacts = new Map<number, readonly string[]>();
let hasCandidateElectronSource = false;
const interop = createSemanticModuleInterop(state);
const writes = new SemanticPropertyPathCoverage();
let collectedWrites = false;
const exportsByBinding = new Map<string, readonly string[] | null>();
const candidatesByBinding = new Map<string, readonly string[] | null>();
const originAvailableAtRead = (
binding: JavaScriptSemanticBindingState,
read: t.Node,
): boolean => {
if (
binding.directOrigins.length > 0 ||
semanticStableInitializer(binding, state) === undefined
)
return true;
const access = semanticBindingAtRead(binding, read, state);
// An unresolved activation can still carry a possible identity, while an
// ordered read before its initializer cannot refer to that value.
return access.status !== "ordered" || access.hasDefiniteInitializer;
};
const rootOrigin = (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;
if (binding === undefined || !originAvailableAtRead(binding, root))
return undefined;
let exported = exportsByBinding.get(binding.bindingId);
if (exported === undefined) {
exported = electronExport(binding, state, interop);
@@ -900,13 +927,79 @@ export function* indexJavaScriptElectronIdentitySteps(
}
return rootOrigin(root);
};
const candidateRootExport = (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 ||
(collectedWrites && interop.invalidated(binding))
)
return undefined;
if (binding.directOrigins.length === 0) {
if (semanticStableInitializer(binding, state) === undefined)
return undefined;
if (!originAvailableAtRead(binding, root)) return undefined;
}
if (
state.opaqueEffects.byScope.size > 0 &&
(semanticOpaqueBindingReadMayHaveChanged(binding, root, state) ||
interop.aliasMayHaveChanged(binding))
)
return undefined;
let candidate = candidatesByBinding.get(binding.bindingId);
if (candidate === undefined) {
const provenance = evaluateSemanticProvenance(binding, state);
const initializer = semanticStableInitializer(binding, state);
const bundled =
initializer === undefined
? undefined
: semanticBundledRequireOriginCandidate(initializer.node, state)
?.origin;
const origin =
bundled?.loaderAuthority === "candidate" &&
initializer?.projection.every(
(key): key is string => typeof key === "string",
)
? {
...bundled,
importedPath: [
...bundled.importedPath,
...initializer.projection,
],
}
: provenance.status === "ambiguous" &&
provenance.origins.length === 1
? provenance.origins[0]
: undefined;
candidate =
origin?.loaderAuthority === "candidate" &&
ELECTRON_MODULE.test(origin.specifier)
? electronExportPath(origin.importedPath)
: null;
candidatesByBinding.set(binding.bindingId, candidate);
}
return candidate ?? undefined;
}
const origin = (
semanticRequireOriginCandidate(root, state) ??
semanticBundledRequireOriginCandidate(root, state)
)?.origin;
return origin?.loaderAuthority === "candidate" &&
ELECTRON_MODULE.test(origin.specifier)
? electronExportPath(origin.importedPath)
: undefined;
};
yield* collectJavaScriptIdentityWritesSteps(
program,
state.erasedSyntaxNodes,
(node, kind) => {
if (kind === "opaque-call") return [interop.opaqueMutationPath(node)];
const { root, members } = electronMemberPath(node);
const exported = kind === "binding" ? undefined : rootOrigin(root);
const exported =
kind === "binding"
? undefined
: (rootOrigin(root) ?? candidateRootExport(root));
const path =
exported === undefined
? (interop.intrinsicPath(root) ??
@@ -936,6 +1029,17 @@ export function* indexJavaScriptElectronIdentitySteps(
!t.isNewExpression(node)
)
return;
if (
(t.isCallExpression(node) || t.isOptionalCallExpression(node)) &&
t.isIdentifier(node.callee, { name: "require" })
) {
const origin = semanticRequireOriginCandidate(node, state)?.origin;
if (
origin?.loaderAuthority === "candidate" &&
ELECTRON_MODULE.test(origin.specifier)
)
hasCandidateElectronSource = true;
}
const { root, members } = electronMemberPath(node.callee);
const exported = rootExport(root);
if (
@@ -943,6 +1047,14 @@ export function* indexJavaScriptElectronIdentitySteps(
intactExport(["electron", ...exported, ...members])
)
facts.set(root.start ?? -1, exported);
else {
const candidate = candidateRootExport(root);
if (
candidate !== undefined &&
intactExport(["electron", ...candidate, ...members])
)
candidateFacts.set(root.start ?? -1, candidate);
}
},
});
const invocationPath = (
@@ -965,14 +1077,23 @@ export function* indexJavaScriptElectronIdentitySteps(
};
return {
bindings: facts,
candidateBindings: candidateFacts,
intactExport,
exportPath: rootExport,
candidateExportPath: candidateRootExport,
invocationPath,
mayChangeAppPath: createSemanticElectronAppPathChangeQuery(
state,
rootOrigin,
interop,
),
candidateMayChangeAppPath: hasCandidateElectronSource
? createSemanticElectronAppPathChangeQuery(
state,
candidateRootExport,
interop,
)
: () => false,
};
}