Files
rea/src/composition/javascriptAnalysisWorkerProcess.ts
T
RRFZ b54845a6b8 fix(javascript): admit Electron script loaders only on renderer evidence
The previous commit dropped a module package type whenever a source
required an Electron module. A Node-loaded ES module that requires
Electron, or one whose Electron call goes through a local `require`,
then regained the false require edges the package scope removes.

The parser applies the package type unconditionally again. The worker
re-reads a package-module script that requires an Electron module once
outside the package scope. The source keeps that context only when the
static pass there directly observes a contextBridge exposure or a
renderer IPC operation, which only Electron's renderer-side loaders
provide.
2026-10-29 19:05:37 -04:00

111 lines
4.4 KiB
TypeScript

import {
hasElectronRendererEvidence,
mayRunUnderElectronScriptLoader,
} from "../domain/javascript/electronScriptLoaderEvidence.js";
import { moduleResolutionModeForJavaScriptReference } from "../domain/javascript/javascriptModuleResolutionMode.js";
import { parseJavaScriptSource } from "../domain/javascript/javascriptSourceParser.js";
import { analyzeParsedJavaScriptStaticSourceSteps } from "../domain/javascript/javascriptStaticAnalysis.js";
import type { JavaScriptStaticAnalysis } from "../domain/javascript/javascriptStaticAnalysisTypes.js";
import { analyzeParsedJavaScriptSemanticsSteps } from "../domain/javascript/javascriptSemanticAnalysis.js";
import { failedJavaScriptStaticAnalysis } from "../domain/javascript/javascriptStaticAnalysisHelpers.js";
import { projectJavaScriptModuleSemantics } from "../domain/javascript/javascriptModuleSemanticIr.js";
import {
javaScriptTransferByteBudget,
measureJavaScriptModuleTransfer,
} from "../javascript/analysis/JavaScriptAnalysisTransfer.js";
import { AnalysisResourceConstraintError } from "../domain/analysisErrorCore.js";
import { projectJavaScriptSemanticFileSteps } from "../application/javascript/JavaScriptSemanticGraphBuilder.js";
import { completeJavaScriptAnalysisSteps } from "../application/javascript/JavaScriptAnalysisControl.js";
import {
serveJavaScriptAnalysisWorker,
type JavaScriptSourceWorkerAnalyzer,
} from "../javascript/analysis/JavaScriptAnalysisWorkerServer.js";
const analyze: JavaScriptSourceWorkerAnalyzer = async (
file,
capacity,
callbacks,
packageType,
) => {
if (!file.text.included)
throw new TypeError("JavaScript worker requires decoded source text");
await callbacks.phase("parse");
let parsed = parseJavaScriptSource(file.text.value, file.path, packageType);
if (parsed === null) {
await callbacks.checkpoint(failedJavaScriptStaticAnalysis());
return null;
}
await callbacks.phase("static");
let facts: JavaScriptStaticAnalysis | undefined;
if (mayRunUnderElectronScriptLoader(parsed.program)) {
// Renderer-side evidence is read outside the package scope, where an
// unbound require keeps its Electron identity; with that evidence the
// whole source keeps that context, so its static and semantic facts agree.
const script = parseJavaScriptSource(file.text.value, file.path);
if (script !== null) {
const scriptFacts = await completeJavaScriptAnalysisSteps(
analyzeParsedJavaScriptStaticSourceSteps(file.text.value, script),
);
if (hasElectronRendererEvidence(scriptFacts.electron)) {
parsed = script;
facts = scriptFacts;
}
}
}
facts ??= await completeJavaScriptAnalysisSteps(
analyzeParsedJavaScriptStaticSourceSteps(file.text.value, parsed),
);
const resultBudgetBytes = await callbacks.checkpoint(facts);
if (facts.parse_status === "failed") return null;
if (resultBudgetBytes === 0)
throw new AnalysisResourceConstraintError(
"analyze_javascript_application",
"memory",
`The parent reserved its remaining result capacity for static observations; semantic analysis of ${file.path} was not started`,
{ semantic_result_budget_bytes: 0 },
);
await callbacks.phase("semantic");
const ir = await completeJavaScriptAnalysisSteps(
analyzeParsedJavaScriptSemanticsSteps(
parsed,
moduleResolutionModeForJavaScriptReference(file.path, "static-import"),
capacity.nodes,
),
);
parsed = null;
const module = projectJavaScriptModuleSemantics(ir);
const measured = measureJavaScriptModuleTransfer(
module,
javaScriptTransferByteBudget(resultBudgetBytes),
);
if (measured.retentionBytes > resultBudgetBytes)
throw new AnalysisResourceConstraintError(
"analyze_javascript_application",
"memory",
`Completed module facts for ${file.path} exceed the parent's offered result budget; static facts were retained`,
{
semantic_result_budget_bytes: resultBudgetBytes,
module_transfer_bytes: measured.bytes,
},
);
await callbacks.phase("projection");
const projection = await completeJavaScriptAnalysisSteps(
projectJavaScriptSemanticFileSteps(
file,
ir,
capacity,
facts.electron.call_paths,
),
);
return { module, projection };
};
try {
await serveJavaScriptAnalysisWorker(analyze);
} catch (cause: unknown) {
process.stderr.write(
`${cause instanceof Error ? cause.message : String(cause)}\n`,
);
process.exitCode = 1;
}