fix(javascript): preserve loader identity across terminal origins (#9107)

Exclude only proven separate values from Webpack loader alias walks; preserve unsupported, projected and unresolved origins as unknown and retain the real alias-state bound.

Refs #7253. The full Bitwarden precision regression remains open.

Validation: 336 focused tests and check:fast passed; real Bitwarden preload identity retained; full main/preload completed with disclosed partial coverage.

Assisted-by: Reimu (gensokyo-skills:reimu-incident-triage)
This commit is contained in:
gensokyo-dev authored and GitHub committed 2026-11-06 07:59:03 +00:00
1 parent 1cc979c104
commit 3e0599a949
3 files changed
+193 -13

No files matched your search

@@ -6,6 +6,7 @@ import { proveJavaScriptBootstrapIntegrity } from "./javascriptBootstrapIntegrit
import {
semanticMemberChain,
semanticStaticPropertyKey,
readExactJavaScriptLiteral,
unwrapJavaScriptExpression,
} from "./javascriptAstValues.js";
import { collectJavaScriptIdentityWritesSteps } from "./javascriptElectronMemberWrites.js";
@@ -159,32 +160,42 @@ const canonicalWebpackInteropBindings = (
// The shared identity-write owner covers assignment, delete, destructuring,
// and Object/Reflect mutators; any write that may select `n` counts.
const writes = new Set<t.Node>();
const interop = createSemanticModuleInterop(state);
const excludesLoader = (node: t.Node): boolean =>
interop.isFreshObject(node) || readExactJavaScriptLiteral(node).found;
const loaderMayOwnReference = (root: t.Node): boolean => {
const pending = [root];
const seen = new Set<t.Node>();
const first = unwrapJavaScriptExpression(root).node;
if (!t.isIdentifier(first)) return !excludesLoader(first);
// Only proven separate values are terminals. An unsupported expression
// might return the loader, so preserve that uncertainty instead of dropping it.
const pending = [first];
const seen = new Set<t.Identifier>();
while (pending.length > 0) {
const reference = pending.pop();
if (reference === undefined || seen.has(reference)) continue;
seen.add(reference);
const node = pending.pop();
if (node === undefined || seen.has(node)) continue;
seen.add(node);
if (seen.size > SEMANTIC_ALIAS_SELECTION_LIMIT) {
state.valueResourceLimits.add("alias-selections");
return true;
}
const node = unwrapJavaScriptExpression(reference).node;
if (!t.isIdentifier(node)) continue;
const binding = resolveSemanticBindingState(state, node, node.name);
if (binding === undefined) continue;
if (binding === undefined) return true;
if (loaderBindingIds.has(binding.bindingId)) return true;
const selected = semanticBindingAtRead(binding, node, state).binding;
for (const source of [
...selected.initializers,
...selected.referenceInitializers,
for (const initializers of [
selected.initializers,
selected.referenceInitializers,
])
if (source.projection.length === 0) pending.push(source.node);
for (const source of initializers) {
// A fresh container does not establish the selected property's value.
if (source.projection.length !== 0) return true;
const next = unwrapJavaScriptExpression(source.node).node;
if (t.isIdentifier(next)) pending.push(next);
else if (!excludesLoader(next)) return true;
}
}
return false;
};
const interop = createSemanticModuleInterop(state);
completeSemanticSteps(
collectJavaScriptIdentityWritesSteps(
state.program,