mirror of
https://github.com/morluto/rea.git
synced 2026-11-13 02:02:36 +00:00
* 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>
235 lines
8.4 KiB
TypeScript
235 lines
8.4 KiB
TypeScript
import * as t from "@babel/types";
|
|
import { posix } from "node:path";
|
|
|
|
import {
|
|
semanticMemberChain,
|
|
semanticStaticPropertyKey,
|
|
unwrapJavaScriptExpression,
|
|
} from "./javascript/javascriptAstValues.js";
|
|
import { isUnmodifiedGlobalReference } from "./javascript/javascriptSemanticAmbientWrites.js";
|
|
import {
|
|
indexJavaScriptBindingsWithReceiversSteps,
|
|
type JavaScriptReceiverIndex,
|
|
} from "./javascript/javascriptSemanticAnalysis.js";
|
|
import type { JavaScriptSemanticAnalysisState } from "./javascript/javascriptSemanticState.js";
|
|
import {
|
|
semanticRequireCallAt,
|
|
semanticRequireLoaderAt,
|
|
semanticSelectedMemberMayHaveChanged,
|
|
type JavaScriptRequireCallFact,
|
|
} from "./javascript/javascriptSemanticRequireOrigin.js";
|
|
import {
|
|
completeSemanticSteps,
|
|
traverseJavaScriptAst,
|
|
} from "./javascript/javascriptSemanticTraversal.js";
|
|
import {
|
|
javaScriptRuntimeContext,
|
|
type ParsedJavaScriptSource,
|
|
} from "./javascript/javascriptSourceParser.js";
|
|
|
|
export type ReferenceSourceRequireResolutionBase =
|
|
| "source-file"
|
|
| { readonly kind: "source-relative-file"; readonly path: string }
|
|
| "unknown";
|
|
|
|
export interface ReferenceSourceRequireCall {
|
|
readonly arguments: JavaScriptRequireCallFact["arguments"];
|
|
readonly resolutionBase: ReferenceSourceRequireResolutionBase;
|
|
readonly limitation?: string;
|
|
}
|
|
|
|
export type ReferenceSourceRequireInvocation =
|
|
| t.CallExpression
|
|
| t.OptionalCallExpression
|
|
| t.NewExpression;
|
|
|
|
/** Identify exact calls through a source-owned Node loader and their resolver base. */
|
|
export const referenceSourceRequireCalls = (
|
|
file: Pick<ParsedJavaScriptSource, "program">,
|
|
path: string,
|
|
): ReadonlyMap<
|
|
ReferenceSourceRequireInvocation,
|
|
ReferenceSourceRequireCall
|
|
> => {
|
|
// Unmarked paths depend on package scope or compiler options. Preserve their
|
|
// candidate CommonJS edges; explicit module paths have no wrapper loader.
|
|
const runtimeContext = javaScriptRuntimeContext(file.program);
|
|
const index = completeSemanticSteps(
|
|
indexJavaScriptBindingsWithReceiversSteps(
|
|
file.program,
|
|
runtimeContext === "module" ? undefined : "commonjs-wrapper",
|
|
),
|
|
);
|
|
const factoryBindings: ReadonlySet<string> = new Set();
|
|
const candidates: {
|
|
readonly call: ReferenceSourceRequireInvocation;
|
|
readonly arguments: JavaScriptRequireCallFact["arguments"];
|
|
readonly resolutionBase: ReferenceSourceRequireResolutionBase;
|
|
readonly limitation?: string;
|
|
}[] = [];
|
|
traverseJavaScriptAst(file.program, {
|
|
enter: (node) => {
|
|
if (
|
|
!t.isCallExpression(node) &&
|
|
!t.isOptionalCallExpression(node) &&
|
|
!t.isNewExpression(node)
|
|
)
|
|
return;
|
|
if (callReceiverIdentifier(node.callee) === undefined) return;
|
|
let fact = semanticRequireLoaderAt(node, index.bindings, factoryBindings);
|
|
const callee = unwrapJavaScriptExpression(node.callee).node;
|
|
if (
|
|
fact === "create-require" ||
|
|
((t.isMemberExpression(callee) ||
|
|
t.isOptionalMemberExpression(callee)) &&
|
|
["call", "apply", "resolve"].includes(
|
|
semanticStaticPropertyKey(callee.property, callee.computed) ?? "",
|
|
))
|
|
) {
|
|
// Definitions identify a possible factory. Its mutable member reads
|
|
// need the shared write index before they can prove an association.
|
|
completeSemanticSteps(index.indexMemberMutationFactsSteps());
|
|
fact = semanticRequireLoaderAt(node, index.bindings, factoryBindings);
|
|
}
|
|
const callFact = semanticRequireCallAt(
|
|
node,
|
|
index.bindings,
|
|
factoryBindings,
|
|
index,
|
|
);
|
|
if (callFact === undefined || callFact.loader === "local") return;
|
|
if (
|
|
fact !== "create-require" &&
|
|
fact !== "commonjs-loader" &&
|
|
callFact.loader !== "create-require" &&
|
|
callFact.loader !== "commonjs-loader"
|
|
)
|
|
return;
|
|
const sourceRelativePath = callFact.sourceRelativePath;
|
|
const normalizedSourceRelativePath =
|
|
sourceRelativePath === undefined
|
|
? undefined
|
|
: posix.normalize(
|
|
posix.join(posix.dirname(path), sourceRelativePath),
|
|
);
|
|
const base: ReferenceSourceRequireResolutionBase =
|
|
runtimeContext === "unknown"
|
|
? "unknown"
|
|
: callFact.loader === "create-require"
|
|
? sourceRelativePath === undefined
|
|
? callFact.sourceRelative === true
|
|
? "source-file"
|
|
: "unknown"
|
|
: normalizedSourceRelativePath === undefined ||
|
|
normalizedSourceRelativePath === ".." ||
|
|
normalizedSourceRelativePath.startsWith("../")
|
|
? "unknown"
|
|
: normalizedSourceRelativePath === path
|
|
? "source-file"
|
|
: {
|
|
kind: "source-relative-file",
|
|
path: normalizedSourceRelativePath,
|
|
}
|
|
: callFact.loader === "commonjs-loader" &&
|
|
runtimeContext === "commonjs"
|
|
? "source-file"
|
|
: "unknown";
|
|
const searchBase = requireResolveSearchBase(
|
|
callFact.operation,
|
|
callFact.arguments,
|
|
index.bindings,
|
|
base,
|
|
runtimeContext === "commonjs",
|
|
);
|
|
candidates.push({
|
|
call: node,
|
|
arguments: callFact.arguments,
|
|
resolutionBase: searchBase,
|
|
...(runtimeContext === "unknown"
|
|
? {
|
|
limitation: `Module context is unknown in ${path}; the requested dependency was retained without a proven loader base.`,
|
|
}
|
|
: {}),
|
|
});
|
|
},
|
|
});
|
|
if (
|
|
candidates.some(
|
|
({ call }) =>
|
|
!t.isIdentifier(unwrapJavaScriptExpression(call.callee).node),
|
|
)
|
|
)
|
|
completeSemanticSteps(index.indexMemberMutationFactsSteps());
|
|
return new Map(
|
|
candidates.map(
|
|
({ call, arguments: callArguments, resolutionBase, limitation }) =>
|
|
[
|
|
call,
|
|
{
|
|
arguments: callArguments,
|
|
...(limitation === undefined ? {} : { limitation }),
|
|
resolutionBase:
|
|
!t.isIdentifier(unwrapJavaScriptExpression(call.callee).node) &&
|
|
hasPriorMemberMutation(index, call)
|
|
? "unknown"
|
|
: resolutionBase,
|
|
},
|
|
] as const,
|
|
),
|
|
);
|
|
};
|
|
|
|
/** `require.resolve` options replace the default search paths unless exact current-directory paths are supplied. */
|
|
const requireResolveSearchBase = (
|
|
operation: JavaScriptRequireCallFact["operation"],
|
|
callArguments: JavaScriptRequireCallFact["arguments"],
|
|
bindings: JavaScriptSemanticAnalysisState,
|
|
loaderBase: ReferenceSourceRequireResolutionBase,
|
|
hasCommonJsWrapper: boolean,
|
|
): ReferenceSourceRequireResolutionBase => {
|
|
const arguments_ = callArguments ?? [];
|
|
if (operation !== "resolve" || arguments_.length < 2) return loaderBase;
|
|
if (arguments_.length !== 2) return "unknown";
|
|
const options = t.isExpression(arguments_[1]) ? arguments_[1] : undefined;
|
|
if (options === undefined || !t.isObjectExpression(options)) return "unknown";
|
|
if (options.properties.length === 0) return loaderBase;
|
|
if (options.properties.length !== 1) return "unknown";
|
|
const property = options.properties[0];
|
|
if (!t.isObjectProperty(property)) return "unknown";
|
|
if (semanticStaticPropertyKey(property.key, property.computed) !== "paths")
|
|
return "unknown";
|
|
const value = t.isExpression(property.value) ? property.value : undefined;
|
|
if (!t.isArrayExpression(value) || value.elements.length !== 1)
|
|
return "unknown";
|
|
const directory = value.elements[0];
|
|
return hasCommonJsWrapper &&
|
|
t.isIdentifier(directory, { name: "__dirname" }) &&
|
|
isUnmodifiedGlobalReference(directory, bindings, "__dirname")
|
|
? "source-file"
|
|
: "unknown";
|
|
};
|
|
|
|
const hasPriorMemberMutation = (
|
|
index: JavaScriptReceiverIndex,
|
|
site: ReferenceSourceRequireInvocation,
|
|
): boolean => {
|
|
const callee = unwrapJavaScriptExpression(site.callee).node;
|
|
return (
|
|
semanticMemberChain(callee) === undefined ||
|
|
semanticSelectedMemberMayHaveChanged(index, site, callee)
|
|
);
|
|
};
|
|
|
|
const callReceiverIdentifier = (
|
|
callee: t.Node | null | undefined,
|
|
): t.Identifier | undefined => {
|
|
if (!t.isNode(callee)) return undefined;
|
|
let expression = unwrapJavaScriptExpression(callee).node;
|
|
while (
|
|
t.isMemberExpression(expression) ||
|
|
t.isOptionalMemberExpression(expression)
|
|
)
|
|
expression = unwrapJavaScriptExpression(expression.object).node;
|
|
return t.isIdentifier(expression) ? expression : undefined;
|
|
};
|