mirror of
https://github.com/morluto/rea
synced 2026-10-25 14:01:13 -04:00
f62a2baad8
Separate syntax, semantic uncertainty, examination limits and observed Electron applicability at the static producer, then preserve them through worker, graph and retained/inline summary views. Derive the established parse_status from the same facts and leave historical measurements unknown. Give semantic resource-limit classifications one schema owner. Replace repeated block-prefix searches and copies with collector-scoped syntax indexes. Keep invocation environments separate, charge indexing/replay to the existing work budget and yield during prefix work. Remove the superseded synchronous collector entrypoint. Validated producer, worker, CLI/MCP, historical and partial-coverage regressions, plus actual cancellation during indexing. Bounded authored 128/256-call controls preserved all prior facts while replacing repeated search/copy work with one index pass; this is not a throughput or agent-token benchmark.
397 lines
14 KiB
TypeScript
397 lines
14 KiB
TypeScript
import { uniqueSortedBy } from "../canonicalOrdering.js";
|
|
import * as t from "@babel/types";
|
|
import { JavaScriptRouteScan } from "./javascriptRouteScan.js";
|
|
import type { JavaScriptRouteSourceFacts } from "./javascriptRouteFacts.js";
|
|
import { RETURNED_REFERENCE_WIDENING_LIMITATION } from "./javascriptSemanticCoverage.js";
|
|
import { collectElectronWrappedCallPathsSteps } from "./electronWrappedCallPaths.js";
|
|
import {
|
|
ELECTRON_IDENTITY_LIMITATION,
|
|
ELECTRON_MODULE,
|
|
} from "./javascriptElectronMemberWrites.js";
|
|
|
|
import {
|
|
completeSemanticSteps,
|
|
traverseJavaScriptAstSteps,
|
|
} from "./javascriptSemanticTraversal.js";
|
|
|
|
import {
|
|
isElectronRendererLoad,
|
|
resolveElectronBrowserWindowReceiver,
|
|
} from "./electronStaticRenderer.js";
|
|
import { finishElectronNativeBindingsSteps } from "./electronStaticAnalysisNative.js";
|
|
import { inspectElectronStaticNode } from "./electronStaticAnalysis.js";
|
|
import {
|
|
hasPossibleCommonJsDelegateWrite,
|
|
semanticFactoryLoaderMemberAt,
|
|
} from "./javascriptSemanticRequireOrigin.js";
|
|
import { semanticResourceLimitReason } from "./javascriptSemanticResourceLimits.js";
|
|
import { createStaticPathIntrinsicQuery } from "./javascriptStaticPathIntrinsics.js";
|
|
import { createSemanticExpressionShapeResolver } from "./javascriptSemanticExpressionShapes.js";
|
|
import { finishElectronBrowserWindowsSteps } from "./electronStaticAnalysisBrowser.js";
|
|
import {
|
|
detectVendors,
|
|
failedJavaScriptStaticAnalysis,
|
|
registrationKey,
|
|
stringValue,
|
|
} from "./javascriptStaticAnalysisHelpers.js";
|
|
import {
|
|
addReference,
|
|
addEndpoint,
|
|
addSourceMapDirectives,
|
|
finishPartialNetworkEndpoints,
|
|
finishModuleRequireCalls,
|
|
finishSqliteStorageCalls,
|
|
inspectCall,
|
|
inspectRpcNode,
|
|
finishRpcScanSteps,
|
|
indexVitePreloadDependencyTable,
|
|
} from "./javascriptStaticAnalysisCalls.js";
|
|
import {
|
|
finishBundlerExports,
|
|
inspectBundlerRegistration,
|
|
inspectBundlerBootstrap,
|
|
inspectEsbuildWrapper,
|
|
} from "./javascriptStaticAnalysisBundler.js";
|
|
import { finalizeLocatedFindings } from "./javascriptStaticAnalysisFindings.js";
|
|
import {
|
|
createJavaScriptAnalysisAccumulator,
|
|
type JavaScriptAnalysisAccumulator as AnalysisAccumulator,
|
|
type JavaScriptFindingContext,
|
|
} from "./javascriptStaticAnalysisState.js";
|
|
import type { JavaScriptStaticAnalysis } from "./javascriptStaticAnalysisTypes.js";
|
|
import type { JavaScriptSemanticAnalysisState } from "./javascriptSemanticState.js";
|
|
import {
|
|
indexJavaScriptElectronIdentitySteps,
|
|
indexJavaScriptBindingsWithReceiversSteps,
|
|
} from "./javascriptSemanticAnalysis.js";
|
|
import {
|
|
parseJavaScriptSource,
|
|
type ParsedJavaScriptSource,
|
|
} from "./javascriptSourceParser.js";
|
|
|
|
import { createJavaScriptRpcScan } from "./javascriptRpcEndpoints.js";
|
|
import {
|
|
hasUncertainTypeOnlyModuleSideEffect,
|
|
isTypeOnlyExportDeclaration,
|
|
isTypeOnlyImportDeclaration,
|
|
semanticImportEqualsSpecifier,
|
|
} from "./javascriptModuleDeclaration.js";
|
|
import {
|
|
javaScriptStaticParseStatus,
|
|
type JavaScriptStaticCoverage,
|
|
} from "./javascriptStaticCoverage.js";
|
|
|
|
/** Parse one JavaScript artifact and recover static structure only. */
|
|
export const analyzeJavaScriptStaticSource = (
|
|
source: string,
|
|
): JavaScriptStaticAnalysis => {
|
|
const file = parseJavaScriptSource(source);
|
|
return file === null
|
|
? failedJavaScriptStaticAnalysis()
|
|
: analyzeParsedJavaScriptStaticSource(source, file);
|
|
};
|
|
|
|
/** Recover static structure from an already parsed JavaScript artifact. */
|
|
export const analyzeParsedJavaScriptStaticSource = (
|
|
source: string,
|
|
file: ParsedJavaScriptSource,
|
|
): JavaScriptStaticAnalysis =>
|
|
completeSemanticSteps(analyzeParsedJavaScriptStaticSourceSteps(source, file));
|
|
|
|
/** Static analysis that yields during its source traversal; results are unchanged. */
|
|
export function* analyzeParsedJavaScriptStaticSourceSteps(
|
|
source: string,
|
|
file: ParsedJavaScriptSource,
|
|
): Generator<void, JavaScriptStaticAnalysis> {
|
|
const accumulator = createJavaScriptAnalysisAccumulator(source.length);
|
|
// One definitions pass serves receiver classification and binding lookup.
|
|
const index = yield* indexJavaScriptBindingsWithReceiversSteps(file.program);
|
|
const { bindings, receivers } = index;
|
|
yield* index.indexAmbientWritesSteps();
|
|
if (hasPossibleCommonJsDelegateWrite(bindings))
|
|
yield* index.indexMemberMutationFactsSteps();
|
|
const electronIdentity = yield* indexJavaScriptElectronIdentitySteps(
|
|
file.program,
|
|
bindings,
|
|
);
|
|
const findings: JavaScriptFindingContext = {
|
|
pathIntrinsics: createStaticPathIntrinsicQuery(index),
|
|
browserWindowReceiver: (node, site, suffix) =>
|
|
resolveElectronBrowserWindowReceiver(node, site, suffix, findings, index),
|
|
rendererLoad: (callee, site) =>
|
|
isElectronRendererLoad(callee, site, findings, index),
|
|
source,
|
|
accumulator,
|
|
vitePreloadDependencyTable: indexVitePreloadDependencyTable(
|
|
file.program,
|
|
bindings.erasedSyntaxNodes,
|
|
),
|
|
openReceiverFacts: receivers.openReceivers,
|
|
electronBindings: electronIdentity.bindings,
|
|
electronExportIntact: electronIdentity.intactExport,
|
|
electronInvocationPath: electronIdentity.invocationPath,
|
|
electronAppPathChangePossible: electronIdentity.mayChangeAppPath,
|
|
rpcScan: createJavaScriptRpcScan(source, receivers.rpcClientRoots, index),
|
|
expressionShapes: createSemanticExpressionShapeResolver(bindings),
|
|
};
|
|
const routeScan = yield* JavaScriptRouteScan.createSteps(
|
|
file,
|
|
index,
|
|
(fact) => addEndpoint(findings, { ...fact, kind: "route" }),
|
|
);
|
|
yield* traverseStaticSourceSteps(file, findings, bindings, routeScan);
|
|
const routes = yield* routeScan.finishSteps();
|
|
if (accumulator.bundlerExportHelpersObserved)
|
|
yield* index.indexMemberMutationFactsSteps();
|
|
finishBundlerExports(accumulator, index);
|
|
yield* finishElectronNativeBindingsSteps(findings, index);
|
|
yield* finishElectronBrowserWindowsSteps(findings, index);
|
|
if (accumulator.pendingModuleRequireCalls.length > 0) {
|
|
yield* index.indexMemberMutationFactsSteps();
|
|
finishModuleRequireCalls(findings, index);
|
|
}
|
|
if (
|
|
accumulator.pendingNetworkCalls.length > 0 ||
|
|
accumulator.pendingSqliteCalls.length > 0
|
|
) {
|
|
yield* index.indexMemberMutationFactsSteps();
|
|
finishPartialNetworkEndpoints(findings, bindings, index);
|
|
finishSqliteStorageCalls(findings, bindings, index);
|
|
}
|
|
accumulator.electronCallPaths = yield* collectElectronWrappedCallPathsSteps(
|
|
file.program,
|
|
bindings,
|
|
source,
|
|
accumulator,
|
|
electronIdentity,
|
|
);
|
|
addSourceMapDirectives(source, file.comments ?? [], accumulator);
|
|
return finalizeStaticAnalysis(source, file, accumulator, bindings, routes);
|
|
}
|
|
|
|
function* traverseStaticSourceSteps(
|
|
file: ParsedJavaScriptSource,
|
|
findings: JavaScriptFindingContext,
|
|
bindings: JavaScriptSemanticAnalysisState,
|
|
routeScan: JavaScriptRouteScan,
|
|
): Generator<void, void> {
|
|
yield* traverseJavaScriptAstSteps(file, {
|
|
enter: (node, parent) => {
|
|
findings.accumulator.visitedNodes += 1;
|
|
routeScan.inspect(node, parent);
|
|
if (!bindings.erasedSyntaxNodes.has(node))
|
|
inspectNode(node, findings, bindings);
|
|
return undefined;
|
|
},
|
|
});
|
|
yield* finishRpcScanSteps(findings);
|
|
}
|
|
|
|
const finalizeStaticAnalysis = (
|
|
source: string,
|
|
file: ParsedJavaScriptSource,
|
|
accumulator: AnalysisAccumulator,
|
|
bindings: JavaScriptSemanticAnalysisState,
|
|
routes: JavaScriptRouteSourceFacts,
|
|
): JavaScriptStaticAnalysis => {
|
|
const parserErrors = file.errors.length;
|
|
const typeOnlySideEffect = hasUncertainTypeOnlyModuleSideEffect(file.program);
|
|
const electronObserved = accumulator.references.some(
|
|
({ value }) =>
|
|
value.specifier !== null && ELECTRON_MODULE.test(value.specifier),
|
|
);
|
|
const resourceLimits = [...bindings.valueResourceLimits].sort();
|
|
const coverage: JavaScriptStaticCoverage = {
|
|
syntax:
|
|
parserErrors === 0
|
|
? { status: "complete", error_count: 0 }
|
|
: { status: "recovered", error_count: parserErrors },
|
|
semantics: {
|
|
unknown_findings: accumulator.unknownFindings,
|
|
uncertainty: [
|
|
...(accumulator.unknownFindings > 0
|
|
? ["dynamic-findings" as const]
|
|
: []),
|
|
...(bindings.returnedReferenceWidenings.size > 0
|
|
? ["returned-reference-widening" as const]
|
|
: []),
|
|
...(typeOnlySideEffect
|
|
? ["type-only-module-side-effect" as const]
|
|
: []),
|
|
],
|
|
},
|
|
examination: {
|
|
status:
|
|
resourceLimits.length > 0 ||
|
|
accumulator.electronCallPathLimitReached === true
|
|
? "limited"
|
|
: "complete",
|
|
semantic_resource_limits: resourceLimits,
|
|
electron_call_paths_limited:
|
|
accumulator.electronCallPathLimitReached === true,
|
|
},
|
|
applicability: { electron: electronObserved ? "observed" : "unknown" },
|
|
};
|
|
const limitations = [
|
|
...(bindings.valueResourceLimits.has("alias-selections")
|
|
? [semanticResourceLimitReason("alias-selections")]
|
|
: []),
|
|
...(bindings.returnedReferenceWidenings.size > 0
|
|
? [RETURNED_REFERENCE_WIDENING_LIMITATION]
|
|
: []),
|
|
...(accumulator.electronCallPaths?.length
|
|
? [
|
|
"Bundled Electron call paths use static argument and closure substitutions. Conditions, ambient helper overrides, runtime reachability and arbitrary side effects remain unproven.",
|
|
]
|
|
: []),
|
|
...(accumulator.electronCallPathLimitReached
|
|
? [
|
|
"Electron wrapper expansion reached its resource budget (1000000 visits/substitutions or depth 128); additional paths remain unknown.",
|
|
]
|
|
: []),
|
|
...(parserErrors === 0
|
|
? []
|
|
: [
|
|
"The parser recovered from syntax errors; affected facts are partial.",
|
|
]),
|
|
...(accumulator.unknownFindings === 0
|
|
? []
|
|
: [
|
|
"One or more static keys, expressions, or Electron boundary values were dynamic and remain unknown.",
|
|
]),
|
|
...(typeOnlySideEffect
|
|
? [
|
|
"Compiler options are unavailable; inline type-only imports or re-exports may preserve a module side-effect dependency.",
|
|
]
|
|
: []),
|
|
...(electronObserved ? [ELECTRON_IDENTITY_LIMITATION] : []),
|
|
"JavaScript syntax was parsed as data and was never evaluated.",
|
|
];
|
|
return {
|
|
parse_status: javaScriptStaticParseStatus(coverage),
|
|
analysis_coverage: coverage,
|
|
parse_error_count: parserErrors,
|
|
visited_ast_nodes: accumulator.visitedNodes,
|
|
references: finalizeLocatedFindings(
|
|
accumulator.references,
|
|
accumulator.moduleRangeIndex,
|
|
),
|
|
endpoints: finalizeLocatedFindings(
|
|
accumulator.endpoints,
|
|
accumulator.moduleRangeIndex,
|
|
),
|
|
routes,
|
|
storage: finalizeLocatedFindings(
|
|
accumulator.storage,
|
|
accumulator.moduleRangeIndex,
|
|
),
|
|
bundler_registrations: uniqueSortedBy(
|
|
accumulator.registrations,
|
|
registrationKey,
|
|
),
|
|
role_paths: finalizeLocatedFindings(
|
|
accumulator.roles,
|
|
accumulator.moduleRangeIndex,
|
|
),
|
|
source_map_urls: accumulator.sourceMaps,
|
|
vendors: detectVendors(source),
|
|
electron: {
|
|
...(accumulator.appPathChanges.length > 0
|
|
? {
|
|
app_path_changes: finalizeLocatedFindings(
|
|
accumulator.appPathChanges,
|
|
accumulator.moduleRangeIndex,
|
|
),
|
|
}
|
|
: {}),
|
|
...(accumulator.electronCallPaths?.length
|
|
? { call_paths: accumulator.electronCallPaths }
|
|
: {}),
|
|
browser_windows: finalizeLocatedFindings(
|
|
accumulator.browserWindows,
|
|
accumulator.moduleRangeIndex,
|
|
),
|
|
context_bridge_apis: finalizeLocatedFindings(
|
|
accumulator.contextBridgeApis,
|
|
accumulator.moduleRangeIndex,
|
|
),
|
|
ipc: finalizeLocatedFindings(
|
|
accumulator.ipc,
|
|
accumulator.moduleRangeIndex,
|
|
),
|
|
sender_validations: finalizeLocatedFindings(
|
|
accumulator.senderValidations,
|
|
accumulator.moduleRangeIndex,
|
|
),
|
|
utility_processes: finalizeLocatedFindings(
|
|
accumulator.utilityProcesses,
|
|
accumulator.moduleRangeIndex,
|
|
),
|
|
native_addon_bindings: finalizeLocatedFindings(
|
|
accumulator.nativeAddonBindings,
|
|
accumulator.moduleRangeIndex,
|
|
),
|
|
},
|
|
limitations,
|
|
};
|
|
};
|
|
|
|
const inspectNode = (
|
|
node: t.Node,
|
|
findings: JavaScriptFindingContext,
|
|
bindings: JavaScriptSemanticAnalysisState,
|
|
): void => {
|
|
const { source, accumulator } = findings;
|
|
inspectElectronStaticNode(node, findings);
|
|
if (t.isCallExpression(node)) {
|
|
inspectBundlerBootstrap(source, node, accumulator, bindings);
|
|
inspectBundlerRegistration(source, node, accumulator, bindings);
|
|
inspectEsbuildWrapper(source, node, accumulator, bindings);
|
|
if (
|
|
semanticFactoryLoaderMemberAt(
|
|
node,
|
|
bindings,
|
|
accumulator.factoryRequireBindingIds,
|
|
"d",
|
|
) === "factory-loader"
|
|
)
|
|
accumulator.bundlerExportHelpersObserved = true;
|
|
inspectCall(source, node, findings, bindings);
|
|
} else if (t.isOptionalCallExpression(node)) {
|
|
inspectCall(source, node, findings, bindings);
|
|
} else if (t.isNewExpression(node))
|
|
inspectCall(source, node, findings, bindings);
|
|
if (t.isTSImportEqualsDeclaration(node)) {
|
|
const specifier = semanticImportEqualsSpecifier(node);
|
|
if (specifier !== null)
|
|
addReference(findings, { node, kind: "require", specifier });
|
|
} else if (
|
|
(t.isImportDeclaration(node) || t.isExportAllDeclaration(node)) &&
|
|
node.source !== undefined &&
|
|
!(t.isImportDeclaration(node) && isTypeOnlyImportDeclaration(node)) &&
|
|
!(t.isExportAllDeclaration(node) && isTypeOnlyExportDeclaration(node))
|
|
)
|
|
addReference(findings, {
|
|
node,
|
|
kind: "static-import",
|
|
specifier: node.source.value,
|
|
});
|
|
else if (
|
|
t.isExportNamedDeclaration(node) &&
|
|
node.source != null &&
|
|
!isTypeOnlyExportDeclaration(node)
|
|
)
|
|
addReference(findings, {
|
|
node,
|
|
kind: "static-import",
|
|
specifier: node.source.value,
|
|
});
|
|
else if (t.isImportExpression(node))
|
|
addReference(findings, {
|
|
node,
|
|
kind: "dynamic-import",
|
|
specifier: stringValue(node.source),
|
|
});
|
|
inspectRpcNode(node, findings);
|
|
};
|