mirror of
https://github.com/morluto/rea.git
synced 2026-11-06 23:06:18 +00:00
fix(javascript): check Electron renderer evidence without a second static pass
b54845a6b ran the full static pass on an unscoped parse of every
package-module script that requires Electron, then discarded it when no
renderer operation turned up, so such main-process sources paid for two
full passes.
A candidate must now also name contextBridge or ipcRenderer, which any
renderer evidence needs. The evidence check runs only the static pass's
binding and Electron identity preamble with its exposure and IPC
descriptors. The full static pass runs once, on the chosen parse.
This commit is contained in:
1 parent
b54845a6b8
commit
4a86bca9b0
4 files changed
+132
-46
No files matched your search
@@ -1,9 +1,20 @@
|
||||
import * as t from "@babel/types";
|
||||
|
||||
import type { ElectronStaticFindings } from "./electronStaticAnalysisTypes.js";
|
||||
import { ipcDescriptor } from "./electronStaticAnalysisIpc.js";
|
||||
import { electronCalleePath } from "./electronStaticAnalysisValues.js";
|
||||
import { contextBridgeExposureArguments } from "./javascriptContextBridgeExposure.js";
|
||||
import { ELECTRON_MODULE } from "./javascriptElectronMemberWrites.js";
|
||||
import { traverseJavaScriptAst } from "./javascriptSemanticTraversal.js";
|
||||
import {
|
||||
indexJavaScriptBindingsWithReceiversSteps,
|
||||
indexJavaScriptElectronIdentitySteps,
|
||||
} from "./javascriptSemanticAnalysis.js";
|
||||
import { hasPossibleCommonJsDelegateWrite } from "./javascriptSemanticRequireOrigin.js";
|
||||
import {
|
||||
traverseJavaScriptAst,
|
||||
traverseJavaScriptValueAstSteps,
|
||||
} from "./javascriptSemanticTraversal.js";
|
||||
import { javaScriptRuntimeContext } from "./javascriptSourceParser.js";
|
||||
import { stringValue } from "./javascriptStaticAnalysisHelpers.js";
|
||||
|
||||
/**
|
||||
* Whether a script owes its module context only to a `"type": "module"`
|
||||
@@ -12,7 +23,10 @@ import { javaScriptRuntimeContext } from "./javascriptSourceParser.js";
|
||||
* need the .mjs suffix ("ES Modules (ESM) in Electron"). The call alone is not
|
||||
* evidence of that loader: Node's ESM loader, which the main process uses,
|
||||
* has no `require`, so the same call there throws. Module syntax would make
|
||||
* the source a module under every loader, so only scripts qualify.
|
||||
* the source a module under every loader, so only scripts qualify. The
|
||||
* source must also name `contextBridge` or `ipcRenderer`: Electron identity
|
||||
* reaches a member only through an identifier, string, or template key, so a
|
||||
* source naming neither cannot yield {@link electronRendererEvidenceSteps}.
|
||||
*/
|
||||
export const mayRunUnderElectronScriptLoader = (
|
||||
program: t.Program,
|
||||
@@ -22,33 +36,68 @@ export const mayRunUnderElectronScriptLoader = (
|
||||
program.sourceType !== "script"
|
||||
)
|
||||
return false;
|
||||
let found = false;
|
||||
let requiresElectron = false;
|
||||
let namesRendererModule = false;
|
||||
traverseJavaScriptAst(program, {
|
||||
skip: () => found,
|
||||
skip: () => requiresElectron && namesRendererModule,
|
||||
enter: (node) => {
|
||||
found ||=
|
||||
requiresElectron ||=
|
||||
t.isCallExpression(node) &&
|
||||
t.isIdentifier(node.callee, { name: "require" }) &&
|
||||
t.isStringLiteral(node.arguments[0]) &&
|
||||
ELECTRON_MODULE.test(node.arguments[0].value);
|
||||
ELECTRON_MODULE.test(stringValue(node.arguments[0]) ?? "");
|
||||
namesRendererModule ||=
|
||||
(t.isIdentifier(node) && rendererModules.has(node.name)) ||
|
||||
(t.isStringLiteral(node) && rendererModules.has(node.value)) ||
|
||||
(t.isTemplateElement(node) &&
|
||||
rendererModules.has(node.value.cooked ?? ""));
|
||||
},
|
||||
});
|
||||
return found;
|
||||
return requiresElectron && namesRendererModule;
|
||||
};
|
||||
|
||||
const rendererModules: ReadonlySet<string> = new Set([
|
||||
"contextBridge",
|
||||
"ipcRenderer",
|
||||
]);
|
||||
|
||||
/**
|
||||
* `contextBridge` and `ipcRenderer` are renderer-process Electron modules; the
|
||||
* main process cannot obtain them. A directly observed exposure or renderer
|
||||
* IPC operation therefore places its source under a renderer-side loader.
|
||||
* Wrapper substitutions are inferred, so they do not count.
|
||||
* Whether a source directly calls a `contextBridge` exposure or a renderer IPC
|
||||
* operation. Both are renderer-process Electron modules that the main process
|
||||
* cannot obtain, so such a call places the source under a renderer-side
|
||||
* loader. The check runs only the static pass's binding and Electron identity
|
||||
* preamble and its exposure and IPC descriptors, without the rest of that
|
||||
* pass; wrapper substitutions are inferred, so they are not expanded.
|
||||
*/
|
||||
export const hasElectronRendererEvidence = (
|
||||
electron: ElectronStaticFindings,
|
||||
): boolean =>
|
||||
electron.context_bridge_apis.some(
|
||||
({ resolution_basis }) => resolution_basis === undefined,
|
||||
) ||
|
||||
electron.ipc.some(
|
||||
({ side, resolution_basis }) =>
|
||||
side === "renderer" && resolution_basis === undefined,
|
||||
export function* electronRendererEvidenceSteps(
|
||||
program: t.Program,
|
||||
): Generator<void, boolean> {
|
||||
const index = yield* indexJavaScriptBindingsWithReceiversSteps(program);
|
||||
yield* index.indexAmbientWritesSteps();
|
||||
if (hasPossibleCommonJsDelegateWrite(index.bindings))
|
||||
yield* index.indexMemberMutationFactsSteps();
|
||||
const identity = yield* indexJavaScriptElectronIdentitySteps(
|
||||
program,
|
||||
index.bindings,
|
||||
);
|
||||
const context = { electronBindings: identity.bindings };
|
||||
let found = false;
|
||||
yield* traverseJavaScriptValueAstSteps(
|
||||
program,
|
||||
index.bindings.erasedSyntaxNodes,
|
||||
{
|
||||
skip: () => found,
|
||||
enter: (node) => {
|
||||
if (!t.isCallExpression(node) && !t.isOptionalCallExpression(node))
|
||||
return;
|
||||
const path = electronCalleePath(node.callee, context);
|
||||
found ||=
|
||||
(path?.length === 2 &&
|
||||
path[0] === "contextBridge" &&
|
||||
contextBridgeExposureArguments(path[1] ?? "", node.arguments) !==
|
||||
undefined) ||
|
||||
ipcDescriptor(path)?.side === "renderer";
|
||||
},
|
||||
},
|
||||
);
|
||||
return found;
|
||||
}
|
||||
Reference in new issue
Block a user