mirror of
https://github.com/morluto/rea.git
synced 2026-11-12 01:41:21 +00:00
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:
1 parent
78a84d75c0
commit
b46f4fcebd
112 files changed
+5156
-797
No files matched your search
@@ -4,6 +4,7 @@ import { BUILTIN_RESOURCE_METHODS } from "./javascriptBuiltinResourceMethods.js"
|
||||
import { semanticBuiltinNamespacePath } from "./javascriptSemanticProvenance.js";
|
||||
import {
|
||||
semanticRequireOrigin,
|
||||
semanticRequireOriginCandidate,
|
||||
semanticRequireExportsIdentity,
|
||||
semanticGlobalProcessOrigin,
|
||||
semanticMutableBuiltinOrigin,
|
||||
@@ -22,10 +23,14 @@ import {
|
||||
} from "./javascriptSemanticPropertyPaths.js";
|
||||
|
||||
import { semanticCallableIdForNode } from "./javascriptSemanticProjection.js";
|
||||
import { semanticTopLevelEffectFollowsCapture } from "./javascriptSemanticMutationInitializers.js";
|
||||
import {
|
||||
semanticBindingAtRead,
|
||||
semanticTopLevelEffectFollowsCapture,
|
||||
} from "./javascriptSemanticMutationInitializers.js";
|
||||
import { createSemanticCallableIdStack } from "./javascriptSemanticCallableStack.js";
|
||||
import {
|
||||
resolveSemanticBindingState,
|
||||
semanticStableInitializer,
|
||||
type JavaScriptSemanticAnalysisState,
|
||||
type JavaScriptSemanticBindingState,
|
||||
type JavaScriptSemanticMemberEffect,
|
||||
@@ -171,17 +176,70 @@ export const builtinDataEffectCallablePath = (
|
||||
callee: t.Node,
|
||||
state: JavaScriptSemanticAnalysisState,
|
||||
specifiers: readonly string[],
|
||||
): readonly string[] | null => {
|
||||
return builtinDataEffectCallablePathWithAuthority(
|
||||
callee,
|
||||
state,
|
||||
specifiers,
|
||||
false,
|
||||
);
|
||||
};
|
||||
|
||||
/** Recover a proven built-in or a conditional built-in identity. */
|
||||
export const builtinDataEffectIdentity = (
|
||||
callee: t.Node,
|
||||
state: JavaScriptSemanticAnalysisState,
|
||||
specifiers: readonly string[],
|
||||
): {
|
||||
readonly path: readonly string[];
|
||||
readonly builtinAuthority?: "candidate";
|
||||
} | null => {
|
||||
const proven = builtinDataEffectCallablePathWithAuthority(
|
||||
callee,
|
||||
state,
|
||||
specifiers,
|
||||
false,
|
||||
);
|
||||
if (proven !== null) return { path: proven };
|
||||
const candidate = builtinDataEffectCallablePathWithAuthority(
|
||||
callee,
|
||||
state,
|
||||
specifiers,
|
||||
true,
|
||||
);
|
||||
return candidate === null
|
||||
? null
|
||||
: { path: candidate, builtinAuthority: "candidate" };
|
||||
};
|
||||
|
||||
const builtinDataEffectCallablePathWithAuthority = (
|
||||
callee: t.Node,
|
||||
state: JavaScriptSemanticAnalysisState,
|
||||
specifiers: readonly string[],
|
||||
allowCandidate: boolean,
|
||||
): readonly string[] | null => {
|
||||
const expression = unwrapJavaScriptExpression(callee).node;
|
||||
if (expression !== callee)
|
||||
return builtinDataEffectCallablePath(expression, state, specifiers);
|
||||
return builtinDataEffectCallablePathWithAuthority(
|
||||
expression,
|
||||
state,
|
||||
specifiers,
|
||||
allowCandidate,
|
||||
);
|
||||
if (t.isSequenceExpression(expression)) {
|
||||
const final = expression.expressions.at(-1);
|
||||
return final === undefined
|
||||
? null
|
||||
: builtinDataEffectCallablePath(final, state, specifiers);
|
||||
: builtinDataEffectCallablePathWithAuthority(
|
||||
final,
|
||||
state,
|
||||
specifiers,
|
||||
allowCandidate,
|
||||
);
|
||||
}
|
||||
const required = semanticRequireOrigin(callee, state);
|
||||
const required = (
|
||||
allowCandidate ? semanticRequireOriginCandidate : semanticRequireOrigin
|
||||
)(callee, state);
|
||||
if (required !== undefined) {
|
||||
const origin = builtinNamespaceOrigin(required.origin);
|
||||
return specifiers.some(
|
||||
@@ -193,6 +251,9 @@ export const builtinDataEffectCallablePath = (
|
||||
callee,
|
||||
state,
|
||||
true,
|
||||
undefined,
|
||||
allowCandidate,
|
||||
allowCandidate && required.origin.loaderAuthority !== "candidate",
|
||||
)
|
||||
? origin.importedPath
|
||||
: null;
|
||||
@@ -204,6 +265,7 @@ export const builtinDataEffectCallablePath = (
|
||||
state,
|
||||
specifiers,
|
||||
callee,
|
||||
allowCandidate,
|
||||
[],
|
||||
true,
|
||||
);
|
||||
@@ -216,13 +278,14 @@ export const builtinDataEffectCallablePath = (
|
||||
const receiver = semanticBoundMemberPath(callee.object, state);
|
||||
if (method !== null && receiver !== undefined) {
|
||||
const provenance = evaluateSemanticProvenance(receiver.binding, state);
|
||||
if (provenance.status === "module") {
|
||||
const sourceOrigin = builtinProvenanceOrigin(provenance, allowCandidate, [
|
||||
...receiver.path,
|
||||
method,
|
||||
]);
|
||||
if (sourceOrigin !== undefined) {
|
||||
const origin = builtinNamespaceOrigin({
|
||||
...provenance.origins[0],
|
||||
importedPath: [
|
||||
...provenance.origins[0].importedPath,
|
||||
...receiver.path,
|
||||
],
|
||||
...sourceOrigin,
|
||||
importedPath: [...sourceOrigin.importedPath, ...receiver.path],
|
||||
});
|
||||
if (
|
||||
specifiers.some(
|
||||
@@ -235,6 +298,7 @@ export const builtinDataEffectCallablePath = (
|
||||
state,
|
||||
specifiers,
|
||||
callee,
|
||||
allowCandidate,
|
||||
[...receiver.path, method],
|
||||
true,
|
||||
) !== null
|
||||
@@ -242,7 +306,12 @@ export const builtinDataEffectCallablePath = (
|
||||
: null;
|
||||
}
|
||||
}
|
||||
const source = semanticExpressionModuleOrigin(callee.object, state);
|
||||
const source = semanticExpressionModuleOrigin(
|
||||
callee.object,
|
||||
state,
|
||||
new Set(),
|
||||
allowCandidate,
|
||||
);
|
||||
if (method === null || source === undefined) return null;
|
||||
const origin = builtinNamespaceOrigin(source);
|
||||
return specifiers.some(
|
||||
@@ -255,6 +324,8 @@ export const builtinDataEffectCallablePath = (
|
||||
state,
|
||||
true,
|
||||
callee.object,
|
||||
allowCandidate,
|
||||
allowCandidate && source.loaderAuthority !== "candidate",
|
||||
)
|
||||
? [...origin.importedPath, method]
|
||||
: null;
|
||||
@@ -271,6 +342,7 @@ export const builtinDataEffectCallablePath = (
|
||||
state,
|
||||
specifiers,
|
||||
callee,
|
||||
allowCandidate,
|
||||
[method],
|
||||
true,
|
||||
);
|
||||
@@ -285,8 +357,11 @@ export const builtinDataEffectCallablePath = (
|
||||
return null;
|
||||
}
|
||||
const provenance = evaluateSemanticProvenance(binding, state);
|
||||
if (provenance.status !== "module") return null;
|
||||
const origin = builtinNamespaceOrigin(provenance.origins[0]);
|
||||
const sourceOrigin = builtinProvenanceOrigin(provenance, allowCandidate, [
|
||||
method,
|
||||
]);
|
||||
if (sourceOrigin === undefined) return null;
|
||||
const origin = builtinNamespaceOrigin(sourceOrigin);
|
||||
const selected = [...origin.importedPath, method];
|
||||
return specifiers.some(
|
||||
(specifier) => specifier.replace(/^node:/u, "") === origin.specifier,
|
||||
@@ -296,16 +371,19 @@ export const builtinDataEffectCallablePath = (
|
||||
state,
|
||||
specifiers,
|
||||
initializer.node,
|
||||
allowCandidate,
|
||||
[method],
|
||||
true,
|
||||
)?.length === 0 &&
|
||||
!requiredModuleChanged(
|
||||
provenance.origins[0].specifier,
|
||||
sourceOrigin.specifier,
|
||||
selected,
|
||||
callee,
|
||||
state,
|
||||
true,
|
||||
initializer.node,
|
||||
allowCandidate,
|
||||
allowCandidate && sourceOrigin.loaderAuthority !== "candidate",
|
||||
)
|
||||
? [method]
|
||||
: null;
|
||||
@@ -337,6 +415,7 @@ const builtinBindingPath = (
|
||||
state: JavaScriptSemanticAnalysisState,
|
||||
specifiers: readonly string[],
|
||||
capture: t.Node,
|
||||
allowCandidate: boolean,
|
||||
selectedPath: readonly string[] = [],
|
||||
callableValue = false,
|
||||
visited: ReadonlySet<string> = new Set(),
|
||||
@@ -366,6 +445,7 @@ const builtinBindingPath = (
|
||||
state,
|
||||
specifiers,
|
||||
capture,
|
||||
allowCandidate,
|
||||
selectedPath,
|
||||
callableValue,
|
||||
visited,
|
||||
@@ -382,6 +462,7 @@ const computeBuiltinBindingPath = (
|
||||
state: JavaScriptSemanticAnalysisState,
|
||||
specifiers: readonly string[],
|
||||
capture: t.Node,
|
||||
allowCandidate: boolean,
|
||||
selectedPath: readonly string[] = [],
|
||||
callableValue = false,
|
||||
visited: ReadonlySet<string>,
|
||||
@@ -390,6 +471,19 @@ const computeBuiltinBindingPath = (
|
||||
savedObjectCapture?: t.Node,
|
||||
): readonly string[] | null => {
|
||||
if (binding === undefined || visited.has(binding.bindingId)) return null;
|
||||
// A source-owned initializer is not available before its ordered execution.
|
||||
// Hoisted imports and unresolved activation order retain their existing facts.
|
||||
if (
|
||||
binding.directOrigins.length === 0 &&
|
||||
semanticStableInitializer(binding, state) !== undefined
|
||||
) {
|
||||
const read = semanticBindingAtRead(
|
||||
binding,
|
||||
provenanceCapture ?? capture,
|
||||
state,
|
||||
);
|
||||
if (read.status === "ordered" && !read.hasDefiniteInitializer) return null;
|
||||
}
|
||||
// Provenance identifies the module, independently of whether a selected slot
|
||||
// still refers to its original built-in after member mutation or escape.
|
||||
if (
|
||||
@@ -413,7 +507,7 @@ const computeBuiltinBindingPath = (
|
||||
))
|
||||
),
|
||||
) ||
|
||||
builtinEscapes(binding, state).some(
|
||||
builtinEscapes(binding, state, allowCandidate).some(
|
||||
({ path, node }) =>
|
||||
affectsBuiltinPath(path, selectedPath) &&
|
||||
// A function-value escape cannot replace its native [[Call]]. An
|
||||
@@ -431,9 +525,14 @@ const computeBuiltinBindingPath = (
|
||||
provenanceCapture === undefined
|
||||
? evaluateSemanticProvenance(binding, state)
|
||||
: evaluateSemanticProvenanceAtRead(binding, provenanceCapture, state);
|
||||
if (provenance.status !== "module") return null;
|
||||
const { importedPath } = provenance.origins[0];
|
||||
const namespace = builtinNamespaceOrigin(provenance.origins[0]);
|
||||
const sourceOrigin = builtinProvenanceOrigin(
|
||||
provenance,
|
||||
allowCandidate,
|
||||
selectedPath,
|
||||
);
|
||||
if (sourceOrigin === undefined) return null;
|
||||
const { importedPath } = sourceOrigin;
|
||||
const namespace = builtinNamespaceOrigin(sourceOrigin);
|
||||
if (
|
||||
!specifiers.some((item) => {
|
||||
const name = item.replace(/^node:/u, "");
|
||||
@@ -457,6 +556,10 @@ const computeBuiltinBindingPath = (
|
||||
capture,
|
||||
state,
|
||||
callableValue,
|
||||
undefined,
|
||||
allowCandidate,
|
||||
allowCandidate && origin.loaderAuthority !== "candidate",
|
||||
processSnapshotObjectRoot(origin, selectedPath),
|
||||
)
|
||||
)
|
||||
return null;
|
||||
@@ -491,6 +594,7 @@ const computeBuiltinBindingPath = (
|
||||
state,
|
||||
specifiers,
|
||||
current.capture,
|
||||
allowCandidate,
|
||||
current.path,
|
||||
callableValue,
|
||||
ancestors,
|
||||
@@ -535,7 +639,11 @@ const computeBuiltinBindingPath = (
|
||||
{ ...current, node: node.right },
|
||||
);
|
||||
} else {
|
||||
const required = semanticRequireOrigin(node, state);
|
||||
const required = (
|
||||
allowCandidate
|
||||
? semanticRequireOriginCandidate
|
||||
: semanticRequireOrigin
|
||||
)(node, state);
|
||||
if (
|
||||
required !== undefined &&
|
||||
requiredModuleChanged(
|
||||
@@ -545,6 +653,8 @@ const computeBuiltinBindingPath = (
|
||||
state,
|
||||
callableValue,
|
||||
current.savedObjectCapture,
|
||||
allowCandidate,
|
||||
allowCandidate && required.origin.loaderAuthority !== "candidate",
|
||||
)
|
||||
)
|
||||
return null;
|
||||
@@ -557,7 +667,7 @@ const computeBuiltinBindingPath = (
|
||||
semanticBuiltinNamespacePath(importedPath)
|
||||
? []
|
||||
: builtinNamespaceOrigin({
|
||||
...provenance.origins[0],
|
||||
...sourceOrigin,
|
||||
importedPath: resolvedPath,
|
||||
}).importedPath;
|
||||
};
|
||||
@@ -580,6 +690,37 @@ const builtinNamespaceOrigin = (
|
||||
: { ...origin, specifier, importedPath };
|
||||
};
|
||||
|
||||
const builtinProvenanceOrigin = (
|
||||
provenance: ReturnType<typeof evaluateSemanticProvenance>,
|
||||
allowCandidate: boolean,
|
||||
selectedPath: readonly string[],
|
||||
): JavaScriptModuleOrigin | undefined => {
|
||||
if (provenance.status === "module") return provenance.origins[0];
|
||||
if (
|
||||
!allowCandidate ||
|
||||
provenance.status !== "ambiguous" ||
|
||||
!provenance.origins.some((origin) => origin.loaderAuthority === "candidate")
|
||||
)
|
||||
return undefined;
|
||||
const first = provenance.origins[0];
|
||||
if (first === undefined) return undefined;
|
||||
if (provenance.origins.length === 1) return first;
|
||||
if (selectedPath[0] === "default") return undefined;
|
||||
const normalized = builtinNamespaceOrigin(first);
|
||||
return provenance.origins.every((origin) => {
|
||||
const other = builtinNamespaceOrigin(origin);
|
||||
return (
|
||||
other.specifier === normalized.specifier &&
|
||||
other.importedPath.length === normalized.importedPath.length &&
|
||||
other.importedPath.every(
|
||||
(part, index) => part === normalized.importedPath[index],
|
||||
)
|
||||
);
|
||||
})
|
||||
? first
|
||||
: undefined;
|
||||
};
|
||||
|
||||
const affectsBuiltinPath = (
|
||||
path: JavaScriptSemanticPropertyPath,
|
||||
selected: readonly string[],
|
||||
@@ -639,6 +780,7 @@ interface BuiltinEffectIndex {
|
||||
readonly JavaScriptSemanticMemberEffect[]
|
||||
>;
|
||||
readonly receivers: WeakMap<t.Node, boolean>;
|
||||
readonly candidateReceivers: WeakMap<t.Node, boolean>;
|
||||
}
|
||||
|
||||
// Derived passes run after mutation collection. These indexes share that state's
|
||||
@@ -663,6 +805,7 @@ const builtinEffectIndex = (
|
||||
moduleEffects: new Map(),
|
||||
bindingEscapes: new Map(),
|
||||
receivers: new WeakMap(),
|
||||
candidateReceivers: new WeakMap(),
|
||||
};
|
||||
builtinEffectIndexes.set(state, index);
|
||||
}
|
||||
@@ -672,14 +815,16 @@ const builtinEffectIndex = (
|
||||
const builtinEscapes = (
|
||||
binding: JavaScriptSemanticBindingState,
|
||||
state: JavaScriptSemanticAnalysisState,
|
||||
allowCandidate = false,
|
||||
): readonly JavaScriptSemanticMemberEffect[] => {
|
||||
const cache = builtinEffectIndex(state).bindingEscapes;
|
||||
let effects = cache.get(binding.bindingId);
|
||||
const key = `${binding.bindingId}:${allowCandidate ? "candidate" : "proven"}`;
|
||||
let effects = cache.get(key);
|
||||
if (effects === undefined) {
|
||||
effects = binding.escapedPaths.filter(
|
||||
({ node }) => !originalBuiltinReceiver(node, state),
|
||||
({ node }) => !originalBuiltinReceiver(node, state, allowCandidate),
|
||||
);
|
||||
cache.set(binding.bindingId, effects);
|
||||
cache.set(key, effects);
|
||||
}
|
||||
return effects;
|
||||
};
|
||||
@@ -687,13 +832,16 @@ const builtinEscapes = (
|
||||
const builtinModuleEffects = (
|
||||
specifier: string,
|
||||
state: JavaScriptSemanticAnalysisState,
|
||||
allowCandidate = false,
|
||||
): readonly JavaScriptSemanticRequiredModuleEffect[] => {
|
||||
const cache = builtinEffectIndex(state).moduleEffects;
|
||||
let effects = cache.get(specifier);
|
||||
const key = `${specifier}:${allowCandidate ? "candidate" : "proven"}`;
|
||||
let effects = cache.get(key);
|
||||
if (effects === undefined) {
|
||||
effects = (state.requiredModuleEffects.get(specifier) ?? []).filter(
|
||||
({ kind, node }) =>
|
||||
kind === "write" || !originalBuiltinReceiver(node, state),
|
||||
kind === "write" ||
|
||||
!originalBuiltinReceiver(node, state, allowCandidate),
|
||||
);
|
||||
if (specifier === "fs/promises") {
|
||||
const sharedFsEscapes = (state.requiredModuleEffects.get("fs") ?? [])
|
||||
@@ -701,7 +849,7 @@ const builtinModuleEffects = (
|
||||
({ kind, path, node }) =>
|
||||
kind === "escape" &&
|
||||
(path.length === 0 || path[0] === "promises") &&
|
||||
!originalBuiltinReceiver(node, state),
|
||||
!originalBuiltinReceiver(node, state, allowCandidate),
|
||||
)
|
||||
.map((effect) => ({
|
||||
...effect,
|
||||
@@ -710,11 +858,29 @@ const builtinModuleEffects = (
|
||||
}));
|
||||
effects = [...effects, ...sharedFsEscapes];
|
||||
}
|
||||
cache.set(specifier, effects);
|
||||
cache.set(key, effects);
|
||||
}
|
||||
return effects;
|
||||
};
|
||||
|
||||
/** A named ESM process env/argv value snapshots the object, not later slot replacement. */
|
||||
const processSnapshotObjectRoot = (
|
||||
origin: JavaScriptModuleOrigin,
|
||||
selected: readonly string[],
|
||||
): "env" | "argv" | undefined => {
|
||||
if (
|
||||
origin.resolutionMode === "require" ||
|
||||
origin.specifier.replace(/^node:/u, "") !== "process"
|
||||
)
|
||||
return undefined;
|
||||
const [root, ...rest] = [...origin.importedPath, ...selected];
|
||||
if ((root !== "env" && root !== "argv") || rest.length === 0)
|
||||
return undefined;
|
||||
// The default export is the mutable process object, not a snapshot slot.
|
||||
if (origin.importedPath[0] === "default") return undefined;
|
||||
return root;
|
||||
};
|
||||
|
||||
/** Check shared exports at the use or method-capture point, not at every later call. */
|
||||
const requiredModuleChanged = (
|
||||
specifier: string,
|
||||
@@ -723,10 +889,20 @@ const requiredModuleChanged = (
|
||||
state: JavaScriptSemanticAnalysisState,
|
||||
callableValue = false,
|
||||
savedObjectCapture?: t.Node,
|
||||
allowCandidate = false,
|
||||
ignoreCandidateEffects = false,
|
||||
snapshotObjectRoot?: "env" | "argv",
|
||||
): boolean => {
|
||||
const identity = semanticRequireExportsIdentity(specifier, selected);
|
||||
return builtinModuleEffects(identity.specifier, state).some(
|
||||
({ path, node, kind }) =>
|
||||
return builtinModuleEffects(identity.specifier, state, allowCandidate).some(
|
||||
({ path, node, kind, loaderAuthority }) =>
|
||||
!(ignoreCandidateEffects && loaderAuthority === "candidate") &&
|
||||
!(
|
||||
snapshotObjectRoot !== undefined &&
|
||||
kind === "write" &&
|
||||
path.length === 1 &&
|
||||
path[0] === snapshotObjectRoot
|
||||
) &&
|
||||
!(
|
||||
savedObjectCapture !== undefined &&
|
||||
path.length < identity.path.length &&
|
||||
@@ -752,12 +928,14 @@ const requiredModuleChanged = (
|
||||
const originalBuiltinReceiver = (
|
||||
node: t.Node | undefined,
|
||||
state: JavaScriptSemanticAnalysisState,
|
||||
allowCandidate = false,
|
||||
): boolean => {
|
||||
if (node === undefined) return false;
|
||||
const receivers = builtinEffectIndex(state).receivers;
|
||||
const index = builtinEffectIndex(state);
|
||||
const receivers = allowCandidate ? index.candidateReceivers : index.receivers;
|
||||
const previous = receivers.get(node);
|
||||
if (previous !== undefined) return previous;
|
||||
const result = computeOriginalBuiltinReceiver(node, state);
|
||||
const result = computeOriginalBuiltinReceiver(node, state, allowCandidate);
|
||||
receivers.set(node, result);
|
||||
return result;
|
||||
};
|
||||
@@ -765,25 +943,49 @@ const originalBuiltinReceiver = (
|
||||
const computeOriginalBuiltinReceiver = (
|
||||
node: t.Node | undefined,
|
||||
state: JavaScriptSemanticAnalysisState,
|
||||
allowCandidate: boolean,
|
||||
): boolean => {
|
||||
if (!t.isCallExpression(node) && !t.isOptionalCallExpression(node))
|
||||
return false;
|
||||
const callee = unwrapJavaScriptExpression(node.callee).node;
|
||||
if (!t.isMemberExpression(callee) && !t.isOptionalMemberExpression(callee))
|
||||
return false;
|
||||
const receiver = t.isIdentifier(callee.object)
|
||||
? resolveSemanticBindingState(state, callee.object, callee.object.name)
|
||||
: undefined;
|
||||
const boundReceiver = semanticBoundMemberPath(callee.object, state);
|
||||
const receiver = boundReceiver?.binding;
|
||||
const receiverPath = boundReceiver?.path ?? [];
|
||||
const provenance =
|
||||
receiver === undefined
|
||||
? undefined
|
||||
: evaluateSemanticProvenance(receiver, state);
|
||||
const receiverMethod = semanticStaticPropertyKey(
|
||||
callee.property,
|
||||
callee.computed,
|
||||
);
|
||||
const boundOrigin =
|
||||
provenance === undefined || receiverMethod === null
|
||||
? undefined
|
||||
: builtinProvenanceOrigin(provenance, allowCandidate, [
|
||||
...receiverPath,
|
||||
receiverMethod,
|
||||
]);
|
||||
const sourceOrigin =
|
||||
provenance?.status === "module"
|
||||
? provenance.origins[0]
|
||||
: (semanticExpressionModuleOrigin(callee.object, state) ??
|
||||
semanticRequireOrigin(callee.object, state)?.origin ??
|
||||
semanticGlobalProcessOrigin(callee.object, state));
|
||||
(boundOrigin === undefined
|
||||
? undefined
|
||||
: {
|
||||
...boundOrigin,
|
||||
importedPath: [...boundOrigin.importedPath, ...receiverPath],
|
||||
}) ??
|
||||
semanticExpressionModuleOrigin(
|
||||
callee.object,
|
||||
state,
|
||||
new Set(),
|
||||
allowCandidate,
|
||||
) ??
|
||||
(allowCandidate
|
||||
? semanticRequireOriginCandidate(callee.object, state)
|
||||
: semanticRequireOrigin(callee.object, state)
|
||||
)?.origin ??
|
||||
semanticGlobalProcessOrigin(callee.object, state);
|
||||
if (sourceOrigin === undefined) return false;
|
||||
const origin = builtinNamespaceOrigin(sourceOrigin);
|
||||
const posixReceiver =
|
||||
@@ -792,10 +994,15 @@ const computeOriginalBuiltinReceiver = (
|
||||
origin.importedPath[0] === "posix";
|
||||
if (origin.importedPath.length !== 0 && !posixReceiver) return false;
|
||||
const method = semanticStaticPropertyKey(callee.property, callee.computed);
|
||||
const identity = semanticRequireExportsIdentity(origin.specifier, [
|
||||
...origin.importedPath,
|
||||
method ?? "",
|
||||
]);
|
||||
// ESM namespace/named exports are snapshots, whereas the default object and
|
||||
// CommonJS exports share mutable slots with require().
|
||||
const sources =
|
||||
provenance === undefined || boundOrigin === undefined
|
||||
? [sourceOrigin]
|
||||
: provenance.origins.map((source) => ({
|
||||
...source,
|
||||
importedPath: [...source.importedPath, ...receiverPath],
|
||||
}));
|
||||
if (
|
||||
method === null ||
|
||||
!BUILTIN_RECEIVER_METHODS[
|
||||
@@ -803,16 +1010,38 @@ const computeOriginalBuiltinReceiver = (
|
||||
]?.has(method) ||
|
||||
receiver?.memberWrites.some(
|
||||
({ path, node: write }) =>
|
||||
affectsBuiltinPath(path, [method]) &&
|
||||
affectsBuiltinPath(path, [...receiverPath, method]) &&
|
||||
!semanticTopLevelEffectFollowsCapture(node, write, state.parentsByNode),
|
||||
) ||
|
||||
(
|
||||
builtinEffectIndex(state).writesByModule.get(identity.specifier) ?? []
|
||||
).some(
|
||||
({ path, node: write }) =>
|
||||
affectsBuiltinPath(path, identity.path) &&
|
||||
!semanticTopLevelEffectFollowsCapture(node, write, state.parentsByNode),
|
||||
)
|
||||
sources.some((source) => {
|
||||
const shared =
|
||||
method === null
|
||||
? undefined
|
||||
: semanticMutableBuiltinOrigin(source, [method]);
|
||||
const identity =
|
||||
shared === undefined
|
||||
? undefined
|
||||
: semanticRequireExportsIdentity(shared.specifier, shared.path);
|
||||
return (
|
||||
identity !== undefined &&
|
||||
(
|
||||
builtinEffectIndex(state).writesByModule.get(identity.specifier) ?? []
|
||||
).some(
|
||||
({ path, node: write, loaderAuthority }) =>
|
||||
!(
|
||||
allowCandidate &&
|
||||
source.loaderAuthority !== "candidate" &&
|
||||
loaderAuthority === "candidate"
|
||||
) &&
|
||||
affectsBuiltinPath(path, identity.path) &&
|
||||
!semanticTopLevelEffectFollowsCapture(
|
||||
node,
|
||||
write,
|
||||
state.parentsByNode,
|
||||
),
|
||||
)
|
||||
);
|
||||
})
|
||||
)
|
||||
return false;
|
||||
return !node.arguments.some((argument) => {
|
||||
@@ -821,7 +1050,11 @@ const computeOriginalBuiltinReceiver = (
|
||||
if (passed === undefined) return false;
|
||||
const passedOrigin = evaluateSemanticProvenance(passed, state);
|
||||
return (
|
||||
passedOrigin.status === "module" &&
|
||||
(passedOrigin.status === "module" ||
|
||||
(allowCandidate &&
|
||||
passedOrigin.status === "ambiguous" &&
|
||||
passedOrigin.origins.length === 1 &&
|
||||
passedOrigin.origins[0]?.loaderAuthority === "candidate")) &&
|
||||
builtinNamespaceOrigin(passedOrigin.origins[0]).specifier ===
|
||||
origin.specifier &&
|
||||
builtinNamespaceOrigin(passedOrigin.origins[0]).importedPath.length === 0
|
||||
@@ -833,19 +1066,33 @@ const semanticExpressionModuleOrigin = (
|
||||
expression: t.Node,
|
||||
state: JavaScriptSemanticAnalysisState,
|
||||
visited = new Set<string>(),
|
||||
allowCandidate = false,
|
||||
): JavaScriptModuleOrigin | undefined => {
|
||||
const node = unwrapJavaScriptExpression(expression).node;
|
||||
const required = semanticRequireOrigin(node, state);
|
||||
const required = (
|
||||
allowCandidate ? semanticRequireOriginCandidate : semanticRequireOrigin
|
||||
)(node, state);
|
||||
if (required !== undefined) return required.origin;
|
||||
if (t.isIdentifier(node)) {
|
||||
const binding = resolveSemanticBindingState(state, node, node.name);
|
||||
if (binding === undefined || visited.has(binding.bindingId))
|
||||
return undefined;
|
||||
const provenance = evaluateSemanticProvenance(binding, state);
|
||||
return provenance.status === "module" ? provenance.origins[0] : undefined;
|
||||
return provenance.status === "module" ||
|
||||
(allowCandidate &&
|
||||
provenance.status === "ambiguous" &&
|
||||
provenance.origins.length === 1 &&
|
||||
provenance.origins[0]?.loaderAuthority === "candidate")
|
||||
? provenance.origins[0]
|
||||
: undefined;
|
||||
}
|
||||
if (t.isMemberExpression(node) || t.isOptionalMemberExpression(node)) {
|
||||
const base = semanticExpressionModuleOrigin(node.object, state, visited);
|
||||
const base = semanticExpressionModuleOrigin(
|
||||
node.object,
|
||||
state,
|
||||
visited,
|
||||
allowCandidate,
|
||||
);
|
||||
const key = semanticStaticPropertyKey(node.property, node.computed);
|
||||
return base === undefined || key === null
|
||||
? undefined
|
||||
@@ -875,11 +1122,49 @@ export const isSemanticProcessObject = (
|
||||
state,
|
||||
["process", "node:process"],
|
||||
node,
|
||||
false,
|
||||
selected,
|
||||
)?.length === 0
|
||||
);
|
||||
};
|
||||
|
||||
/** Preserve conditional process identity across loader and mutation uncertainty. */
|
||||
export const semanticProcessObjectIdentity = (
|
||||
node: t.Expression | t.Super,
|
||||
state: JavaScriptSemanticAnalysisState,
|
||||
family: "env" | "argv",
|
||||
key?: string | null,
|
||||
): { readonly builtinAuthority?: "candidate" } | null => {
|
||||
if (isSemanticProcessObject(node, state, family, key)) return {};
|
||||
if (!t.isIdentifier(node)) return null;
|
||||
const binding = resolveSemanticBindingState(state, node, node.name);
|
||||
const selected = key === undefined || key === null ? [family] : [family, key];
|
||||
if (binding === undefined)
|
||||
return semanticGlobalProcessOrigin(node, state) !== undefined &&
|
||||
!requiredModuleChanged(
|
||||
"process",
|
||||
selected,
|
||||
node,
|
||||
state,
|
||||
false,
|
||||
undefined,
|
||||
false,
|
||||
true,
|
||||
)
|
||||
? { builtinAuthority: "candidate" }
|
||||
: null;
|
||||
return builtinBindingPath(
|
||||
binding,
|
||||
state,
|
||||
["process", "node:process"],
|
||||
node,
|
||||
true,
|
||||
selected,
|
||||
)?.length === 0
|
||||
? { builtinAuthority: "candidate" }
|
||||
: null;
|
||||
};
|
||||
|
||||
/** Resolve the nearest direct variable/assignment owner before a callable. */
|
||||
export const outerDataEffectBinding = (
|
||||
node: t.Node,
|
||||
|
||||
Reference in new issue
Block a user