2026-07-16 05:29:40 +08:00
import * as t from "@babel/types" ;
2026-10-12 09:28:59 +08:00
import { collectElectronWrappedCallPaths } from "./electronWrappedCallPaths.js" ;
2026-10-11 09:25:32 +08:00
import {
ELECTRON_IDENTITY_LIMITATION ,
ELECTRON_MODULE ,
} from "./javascriptElectronMemberWrites.js" ;
2026-07-16 05:29:40 +08:00
2026-10-10 05:27:41 -07:00
import {
completeSemanticSteps ,
traverseJavaScriptAstSteps ,
} from "./javascriptSemanticTraversal.js" ;
2026-10-07 17:40:05 +08:00
2026-07-18 03:34:51 +00:00
import { inspectElectronStaticNode } from "./electronStaticAnalysis.js" ;
2026-07-16 05:29:40 +08:00
import {
detectVendors ,
failedJavaScriptStaticAnalysis ,
registrationKey ,
sortedUnique ,
stringValue ,
} from "./javascriptStaticAnalysisHelpers.js" ;
import {
2026-07-18 03:34:51 +00:00
addReference ,
addSourceMapDirectives ,
inspectCall ,
inspectRouteProperty ,
inspectRoleProperty ,
2026-10-10 23:28:52 -03:00
inspectRpcNode ,
2026-07-18 03:34:51 +00:00
} from "./javascriptStaticAnalysisCalls.js" ;
2026-07-24 13:17:19 +00:00
import {
inspectBundlerRegistration ,
2026-10-12 09:28:59 +08:00
inspectBundlerBootstrap ,
2026-07-24 13:17:19 +00:00
inspectEsbuildWrapper ,
} from "./javascriptStaticAnalysisBundler.js" ;
2026-07-18 04:46:48 +00:00
import { finalizeLocatedFindings } from "./javascriptStaticAnalysisFindings.js" ;
2026-07-18 03:34:51 +00:00
import {
createJavaScriptAnalysisAccumulator ,
type JavaScriptAnalysisAccumulator as AnalysisAccumulator ,
2026-10-10 00:44:32 +00:00
type JavaScriptFindingContext ,
2026-07-18 03:34:51 +00:00
} from "./javascriptStaticAnalysisState.js" ;
2026-09-29 15:30:43 +08:00
import type { JavaScriptStaticAnalysis } from "./javascriptStaticAnalysisTypes.js" ;
2026-10-09 00:20:48 +08:00
import {
2026-10-10 21:40:16 +08:00
classifyParsedJavaScriptElectronBindingsSteps ,
2026-10-12 09:28:59 +08:00
indexJavaScriptBindingsSteps ,
2026-10-10 23:28:52 -03:00
classifyParsedJavaScriptRpcClientRootsSteps ,
2026-10-09 00:20:48 +08:00
classifyParsedJavaScriptOpenReceivers ,
type JavaScriptOpenReceiverFact ,
} from "./javascriptSemanticAnalysis.js" ;
2026-07-25 09:10:51 +00:00
import {
parseJavaScriptSource ,
type ParsedJavaScriptSource ,
} from "./javascriptSourceParser.js" ;
2026-07-16 05:29:40 +08:00
2026-10-10 23:28:52 -03:00
import { createJavaScriptRpcScan } from "./javascriptRpcEndpoints.js" ;
2026-09-29 15:30:43 +08:00
/** Parse one JavaScript artifact and recover static structure only. */
2026-07-16 05:29:40 +08:00
export const analyzeJavaScriptStaticSource = (
source : string ,
) : JavaScriptStaticAnalysis => {
2026-07-25 09:10:51 +00:00
const file = parseJavaScriptSource ( source );
return file === null
? failedJavaScriptStaticAnalysis ()
2026-10-09 00:20:48 +08:00
: analyzeParsedJavaScriptStaticSource (
source ,
file ,
classifyParsedJavaScriptOpenReceivers ( file ),
);
2026-07-25 09:10:51 +00:00
};
/** Recover static structure from an already parsed JavaScript artifact. */
export const analyzeParsedJavaScriptStaticSource = (
source : string ,
file : ParsedJavaScriptSource ,
2026-10-09 00:20:48 +08:00
openReceiverFacts? : ReadonlyMap < number , JavaScriptOpenReceiverFact >,
2026-10-10 05:27:41 -07:00
) : JavaScriptStaticAnalysis =>
completeSemanticSteps (
analyzeParsedJavaScriptStaticSourceSteps ( source , file , openReceiverFacts ),
);
/** Static analysis that yields during its source traversal; results are unchanged. */
export function * analyzeParsedJavaScriptStaticSourceSteps (
source : string ,
file : ParsedJavaScriptSource ,
openReceiverFacts? : ReadonlyMap < number , JavaScriptOpenReceiverFact >,
) : Generator < void , JavaScriptStaticAnalysis > {
2026-10-09 05:09:19 +08:00
const accumulator = createJavaScriptAnalysisAccumulator ( source . length );
2026-10-10 21:40:16 +08:00
const electronBindings =
yield * classifyParsedJavaScriptElectronBindingsSteps ( file );
2026-10-10 23:28:52 -03:00
const clientRoots = yield * classifyParsedJavaScriptRpcClientRootsSteps ( file );
2026-10-10 05:27:41 -07:00
yield * traverseStaticSourceSteps ( file , {
2026-10-10 00:44:32 +00:00
source ,
accumulator ,
...( openReceiverFacts === undefined ? {} : { openReceiverFacts }),
2026-10-10 21:40:16 +08:00
electronBindings ,
2026-10-10 23:28:52 -03:00
rpcScan : createJavaScriptRpcScan ( source , clientRoots ),
2026-10-10 00:44:32 +00:00
});
2026-10-12 09:28:59 +08:00
const bindings = yield * indexJavaScriptBindingsSteps ( file . program );
accumulator . electronCallPaths = collectElectronWrappedCallPaths (
file . program ,
bindings ,
source ,
accumulator ,
);
2026-10-06 01:18:25 -07:00
addSourceMapDirectives ( source , file . comments ?? [], accumulator );
2026-09-29 15:30:43 +08:00
return finalizeStaticAnalysis ( source , file , accumulator );
2026-10-10 05:27:41 -07:00
}
2026-07-16 06:45:34 +08:00
2026-10-10 05:27:41 -07:00
function * traverseStaticSourceSteps (
2026-07-25 09:10:51 +00:00
file : ParsedJavaScriptSource ,
2026-10-10 00:44:32 +00:00
findings : JavaScriptFindingContext ,
2026-10-10 05:27:41 -07:00
) : Generator < void , void > {
yield * traverseJavaScriptAstSteps ( file , {
2026-10-07 17:40:05 +08:00
enter : ( node ) => {
2026-10-10 00:44:32 +00:00
findings . accumulator . visitedNodes += 1 ;
inspectNode ( node , findings );
2026-10-07 17:40:05 +08:00
return undefined ;
},
2026-09-29 15:30:43 +08:00
});
2026-10-10 05:27:41 -07:00
}
2026-07-16 06:45:34 +08:00
const finalizeStaticAnalysis = (
source : string ,
2026-07-25 09:10:51 +00:00
file : ParsedJavaScriptSource ,
2026-07-16 06:45:34 +08:00
accumulator : AnalysisAccumulator ,
) : JavaScriptStaticAnalysis => {
2026-07-16 05:29:40 +08:00
const parserErrors = file . errors . length ;
const limitations = [
2026-10-12 09:28:59 +08:00
...( 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." ,
]
: []),
2026-07-16 05:29:40 +08:00
...( parserErrors === 0
? []
: [
"The parser recovered from syntax errors; affected facts are partial." ,
]),
...( accumulator . unknownFindings === 0
? []
: [
2026-07-16 06:45:34 +08:00
"One or more static keys, expressions, or Electron boundary values were dynamic and remain unknown." ,
2026-07-16 05:29:40 +08:00
]),
2026-10-11 09:25:32 +08:00
...( accumulator . references . some (
({ value }) =>
value . specifier !== null && ELECTRON_MODULE . test ( value . specifier ),
)
? [ ELECTRON_IDENTITY_LIMITATION ]
: []),
2026-07-16 05:29:40 +08:00
"JavaScript syntax was parsed as data and was never evaluated." ,
];
return {
2026-09-29 15:30:43 +08:00
parse_status :
parserErrors > 0 || accumulator . unknownFindings > 0
2026-09-29 13:57:24 +08:00
? "partial"
: "complete" ,
2026-07-16 05:29:40 +08:00
parse_error_count : parserErrors ,
2026-09-29 15:30:43 +08:00
visited_ast_nodes : accumulator.visitedNodes ,
2026-07-16 06:45:34 +08:00
references : finalizeLocatedFindings (
accumulator . references ,
2026-10-09 05:09:19 +08:00
accumulator . moduleRangeIndex ,
2026-07-16 06:45:34 +08:00
),
endpoints : finalizeLocatedFindings (
accumulator . endpoints ,
2026-10-09 05:09:19 +08:00
accumulator . moduleRangeIndex ,
),
storage : finalizeLocatedFindings (
accumulator . storage ,
accumulator . moduleRangeIndex ,
2026-07-16 06:45:34 +08:00
),
2026-07-16 05:29:40 +08:00
bundler_registrations : sortedUnique (
accumulator . registrations ,
registrationKey ,
),
2026-10-09 05:09:19 +08:00
role_paths : finalizeLocatedFindings (
accumulator . roles ,
accumulator . moduleRangeIndex ,
),
2026-07-16 05:29:40 +08:00
source_map_urls : accumulator.sourceMaps ,
vendors : detectVendors ( source ),
2026-07-16 06:45:34 +08:00
electron : {
2026-10-12 09:28:59 +08:00
...( accumulator . electronCallPaths ? . length
? { call_paths : accumulator.electronCallPaths }
: {}),
2026-07-16 06:45:34 +08:00
browser_windows : finalizeLocatedFindings (
accumulator . browserWindows ,
2026-10-09 05:09:19 +08:00
accumulator . moduleRangeIndex ,
2026-07-16 06:45:34 +08:00
),
context_bridge_apis : finalizeLocatedFindings (
accumulator . contextBridgeApis ,
2026-10-09 05:09:19 +08:00
accumulator . moduleRangeIndex ,
),
ipc : finalizeLocatedFindings (
accumulator . ipc ,
accumulator . moduleRangeIndex ,
2026-07-16 06:45:34 +08:00
),
sender_validations : finalizeLocatedFindings (
accumulator . senderValidations ,
2026-10-09 05:09:19 +08:00
accumulator . moduleRangeIndex ,
2026-07-16 06:45:34 +08:00
),
utility_processes : finalizeLocatedFindings (
accumulator . utilityProcesses ,
2026-10-09 05:09:19 +08:00
accumulator . moduleRangeIndex ,
2026-07-16 06:45:34 +08:00
),
native_addon_bindings : finalizeLocatedFindings (
accumulator . nativeAddonBindings ,
2026-10-09 05:09:19 +08:00
accumulator . moduleRangeIndex ,
2026-07-16 06:45:34 +08:00
),
},
2026-07-16 05:29:40 +08:00
limitations ,
};
};
const inspectNode = (
node : t.Node ,
2026-10-10 00:44:32 +00:00
findings : JavaScriptFindingContext ,
2026-07-16 05:29:40 +08:00
) : void => {
2026-10-10 00:44:32 +00:00
const { source , accumulator } = findings ;
2026-07-16 06:45:34 +08:00
inspectElectronStaticNode ( node , findings );
2026-07-16 05:29:40 +08:00
if ( t . isCallExpression ( node )) {
2026-10-12 09:28:59 +08:00
inspectBundlerBootstrap ( source , node , accumulator );
2026-09-29 15:30:43 +08:00
inspectBundlerRegistration ( source , node , accumulator );
inspectEsbuildWrapper ( source , node , accumulator );
2026-07-24 13:17:19 +00:00
inspectCall ( source , node , findings );
2026-10-09 00:20:48 +08:00
} else if ( t . isOptionalCallExpression ( node )) {
inspectCall ( source , node , findings );
2026-07-24 13:17:19 +00:00
} else if ( t . isNewExpression ( node )) inspectCall ( source , node , findings );
2026-07-16 05:29:40 +08:00
if (
( t . isImportDeclaration ( node ) || t . isExportAllDeclaration ( node )) &&
node . source !== undefined
)
addReference ( findings , {
node ,
kind : "static-import" ,
specifier : node.source.value ,
});
else if ( t . isExportNamedDeclaration ( node ) && node . source != null )
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 ),
});
if ( t . isObjectProperty ( node )) {
inspectRouteProperty ( node , findings );
inspectRoleProperty ( node , findings );
}
2026-10-10 23:28:52 -03:00
inspectRpcNode ( node , findings );
2026-07-16 05:29:40 +08:00
};