mirror of
https://github.com/morluto/rea.git
synced 2026-11-10 08:47:00 +00:00
fix(electron): consume shared native loader and export facts
This commit is contained in:
1 parent
75daf0c7df
commit
5a7bddb3ae
7 files changed
+254
-52
No files matched your search
@@ -15,14 +15,23 @@ import {
|
||||
import { addLocatedFinding } from "./javascriptStaticAnalysisFindings.js";
|
||||
import {
|
||||
argumentNode,
|
||||
calleeName,
|
||||
range,
|
||||
stringValue,
|
||||
} from "./javascriptStaticAnalysisHelpers.js";
|
||||
import {
|
||||
propertyName,
|
||||
semanticStaticPropertyKey,
|
||||
unwrapJavaScriptExpression,
|
||||
} from "./javascriptAstValues.js";
|
||||
import { semanticRequireCallAt } from "./javascriptSemanticRequireOrigin.js";
|
||||
import {
|
||||
commonJsRuntimeExportValues,
|
||||
indexCommonJsExportReceiversSteps,
|
||||
type CommonJsExportReceiverEffect,
|
||||
type CommonJsExportSite,
|
||||
} from "./javascriptCommonJsExportReceivers.js";
|
||||
import type { JavaScriptReceiverIndex } from "./javascriptSemanticAnalysis.js";
|
||||
import type { JavaScriptSemanticAnalysisState } from "./javascriptSemanticState.js";
|
||||
import type { JavaScriptFindingContext } from "./javascriptStaticAnalysisState.js";
|
||||
|
||||
interface NativeBindingInput {
|
||||
@@ -44,10 +53,44 @@ export const inspectElectronNativeNode = (
|
||||
if (t.isImportDeclaration(node)) inspectImport(node, context);
|
||||
else if (t.isExportNamedDeclaration(node)) inspectNamedExport(node, context);
|
||||
else if (t.isExportAllDeclaration(node)) inspectExportAll(node, context);
|
||||
else if (t.isVariableDeclarator(node)) inspectRequireBinding(node, context);
|
||||
else if (t.isAssignmentExpression(node)) inspectReExport(node, context);
|
||||
else if (t.isVariableDeclarator(node) || t.isAssignmentExpression(node)) {
|
||||
const value = nativeCallReceiver(
|
||||
t.isVariableDeclarator(node) ? node.init : node.right,
|
||||
);
|
||||
// Admission is syntax only; the shared call owner proves loader and arguments later.
|
||||
if (
|
||||
value !== undefined &&
|
||||
value.arguments.some((argument) =>
|
||||
t.isArrayExpression(argument)
|
||||
? argument.elements.some((element) =>
|
||||
isNativeSpecifier(stringValue(element) ?? "", "require"),
|
||||
)
|
||||
: isNativeSpecifier(stringValue(argument) ?? "", "require"),
|
||||
)
|
||||
)
|
||||
context.accumulator.pendingNativeAddonBindings.push(node);
|
||||
}
|
||||
};
|
||||
|
||||
/** Shared loader, argument and receiver facts settle before native projection. */
|
||||
export function* finishElectronNativeBindingsSteps(
|
||||
context: JavaScriptFindingContext,
|
||||
index: JavaScriptReceiverIndex,
|
||||
): Generator<void, void> {
|
||||
const pending = context.accumulator.pendingNativeAddonBindings;
|
||||
if (pending.length === 0) return;
|
||||
yield* index.indexMemberMutationFactsSteps();
|
||||
const receivers = pending.some((node) => t.isAssignmentExpression(node))
|
||||
? yield* indexCommonJsExportReceiversSteps(index.bindings)
|
||||
: new Map<CommonJsExportSite, CommonJsExportReceiverEffect>();
|
||||
for (const node of pending) {
|
||||
if (t.isVariableDeclarator(node))
|
||||
inspectRequireBinding(node, context, index.bindings);
|
||||
else inspectReExport(node, context, index.bindings, receivers.get(node));
|
||||
}
|
||||
pending.length = 0;
|
||||
}
|
||||
|
||||
const inspectImport = (
|
||||
node: t.ImportDeclaration,
|
||||
context: JavaScriptFindingContext,
|
||||
@@ -140,8 +183,9 @@ const inspectExportAll = (
|
||||
const inspectRequireBinding = (
|
||||
node: t.VariableDeclarator,
|
||||
context: JavaScriptFindingContext,
|
||||
bindings: JavaScriptSemanticAnalysisState,
|
||||
): void => {
|
||||
const required = nativeRequire(node.init);
|
||||
const required = nativeRequire(node.init, context, bindings);
|
||||
if (required === undefined) return;
|
||||
addBinding({
|
||||
context,
|
||||
@@ -156,9 +200,20 @@ const inspectRequireBinding = (
|
||||
const inspectReExport = (
|
||||
node: t.AssignmentExpression,
|
||||
context: JavaScriptFindingContext,
|
||||
bindings: JavaScriptSemanticAnalysisState,
|
||||
receiver: CommonJsExportReceiverEffect | undefined,
|
||||
): void => {
|
||||
const required = nativeRequire(node.right);
|
||||
if (required === undefined || !isModuleExport(node.left)) return;
|
||||
const exported = commonJsRuntimeExportValues(node, bindings);
|
||||
if (exported.length === 0 || receiver === "detached") return;
|
||||
if (receiver === "uncertain") {
|
||||
context.accumulator.unknownFindings += 1;
|
||||
return;
|
||||
}
|
||||
// The export owner leaves arithmetic/logical stores without a selected RHS.
|
||||
const value = exported[0]?.value;
|
||||
if (value == null) return;
|
||||
const required = nativeRequire(value, context, bindings);
|
||||
if (required === undefined) return;
|
||||
addBinding({
|
||||
context,
|
||||
node,
|
||||
@@ -198,8 +253,22 @@ const addBinding = (input: NativeBindingInput): void => {
|
||||
});
|
||||
};
|
||||
|
||||
const nativeCallReceiver = (
|
||||
node: t.Node | null | undefined,
|
||||
): t.CallExpression | t.OptionalCallExpression | undefined => {
|
||||
if (node == null) return undefined;
|
||||
node = unwrapJavaScriptExpression(node).node;
|
||||
while (t.isMemberExpression(node) || t.isOptionalMemberExpression(node))
|
||||
node = unwrapJavaScriptExpression(node.object).node;
|
||||
return t.isCallExpression(node) || t.isOptionalCallExpression(node)
|
||||
? node
|
||||
: undefined;
|
||||
};
|
||||
|
||||
const nativeRequire = (
|
||||
node: t.Node | null | undefined,
|
||||
context: JavaScriptFindingContext,
|
||||
bindings: JavaScriptSemanticAnalysisState,
|
||||
):
|
||||
| {
|
||||
readonly specifier: string;
|
||||
@@ -207,6 +276,8 @@ const nativeRequire = (
|
||||
readonly access: NativeMemberAccess;
|
||||
}
|
||||
| undefined => {
|
||||
if (node == null) return undefined;
|
||||
node = unwrapJavaScriptExpression(node).node;
|
||||
let member: string | null = null;
|
||||
let hasMemberAccess = false;
|
||||
while (t.isMemberExpression(node) || t.isOptionalMemberExpression(node)) {
|
||||
@@ -215,28 +286,41 @@ const nativeRequire = (
|
||||
// Walking inward leaves the first member consumed from the addon itself.
|
||||
member = property;
|
||||
hasMemberAccess = true;
|
||||
node = node.object;
|
||||
node = unwrapJavaScriptExpression(node.object).node;
|
||||
}
|
||||
if (!t.isCallExpression(node)) return undefined;
|
||||
const name = calleeName(node.callee);
|
||||
if (!t.isCallExpression(node) && !t.isOptionalCallExpression(node))
|
||||
return undefined;
|
||||
const call = semanticRequireCallAt(
|
||||
node,
|
||||
bindings,
|
||||
context.accumulator.factoryRequireBindingIds,
|
||||
);
|
||||
if (
|
||||
name !== "require" &&
|
||||
!name.endsWith(".require") &&
|
||||
!name.includes("__webpack_require__")
|
||||
call === undefined ||
|
||||
call.loader === "local" ||
|
||||
call.operation !== "load"
|
||||
)
|
||||
return undefined;
|
||||
const specifier = stringValue(argumentNode(node.arguments[0]));
|
||||
return specifier !== undefined && isNativeSpecifier(specifier, "require")
|
||||
? {
|
||||
specifier,
|
||||
hasMemberAccess,
|
||||
access: {
|
||||
members: member === null ? [] : [member],
|
||||
namespaceAccess: !hasMemberAccess,
|
||||
dynamicMemberAccess: hasMemberAccess && member === null,
|
||||
},
|
||||
}
|
||||
: undefined;
|
||||
const specifier = stringValue(argumentNode(call.arguments?.[0]));
|
||||
if (specifier === undefined || !isNativeSpecifier(specifier, "require"))
|
||||
return undefined;
|
||||
if (
|
||||
call.loader === "unknown" ||
|
||||
!call.sourceRelative ||
|
||||
call.sourceRelativePath !== undefined
|
||||
) {
|
||||
context.accumulator.unknownFindings += 1;
|
||||
return undefined;
|
||||
}
|
||||
return {
|
||||
specifier,
|
||||
hasMemberAccess,
|
||||
access: {
|
||||
members: member === null ? [] : [member],
|
||||
namespaceAccess: !hasMemberAccess,
|
||||
dynamicMemberAccess: hasMemberAccess && member === null,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
interface NativeMemberAccess {
|
||||
@@ -263,18 +347,6 @@ const bindingMembers = (pattern: t.Node): NativeMemberAccess => {
|
||||
return { members, namespaceAccess, dynamicMemberAccess };
|
||||
};
|
||||
|
||||
const isModuleExport = (node: t.Node): boolean => {
|
||||
const keys: (string | null)[] = [];
|
||||
while (t.isMemberExpression(node) || t.isOptionalMemberExpression(node)) {
|
||||
keys.unshift(semanticStaticPropertyKey(node.property, node.computed));
|
||||
node = node.object;
|
||||
}
|
||||
if (!t.isIdentifier(node)) return false;
|
||||
return node.name === "exports"
|
||||
? keys.length > 0
|
||||
: node.name === "module" && keys[0] === "exports";
|
||||
};
|
||||
|
||||
const isNativeSpecifier = (
|
||||
specifier: string,
|
||||
moduleKind: ElectronNativeAddonBindingFinding["module_kind"],
|
||||
|
||||
Reference in new issue
Block a user