2026-07-16 05:29:40 +08:00
import * as t from "@babel/types" ;
2026-10-19 12:26:54 +08:00
import { RETURNED_REFERENCE_WIDENING_LIMITATION } from "./javascriptSemanticCoverage.js" ;
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-10-20 17:25:19 +08:00
import { hasPossibleCommonJsDelegateWrite } from "./javascriptSemanticRequireOrigin.js" ;
import { semanticResourceLimitReason } from "./javascriptSemanticResourceLimits.js" ;
import { createStaticNodePathResolveQuery } from "./javascriptStaticPathIntrinsics.js" ;
2026-10-16 14:52:33 +08:00
import { finishElectronBrowserWindowsSteps } from "./electronStaticAnalysisBrowser.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 ,
2026-10-16 08:14:52 +08:00
finishPartialNetworkEndpoints ,
2026-10-20 17:25:19 +08:00
finishModuleRequireCalls ,
2026-10-18 04:55:18 +08:00
finishSqliteStorageCalls ,
2026-07-18 03:34:51 +00:00
inspectCall ,
inspectRouteProperty ,
inspectRoleProperty ,
2026-10-10 23:28:52 -03:00
inspectRpcNode ,
2026-10-15 15:59:42 +08:00
finishRpcScanSteps ,
2026-10-14 08:46:50 +08:00
indexVitePreloadDependencyTable ,
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-12 10:03:03 +08:00
import type { JavaScriptSemanticAnalysisState } from "./javascriptSemanticState.js" ;
2026-10-09 00:20:48 +08:00
import {
2026-10-11 19:44:17 -07:00
indexJavaScriptElectronIdentitySteps ,
2026-10-14 17:59:44 -07:00
indexJavaScriptBindingsWithReceiversSteps ,
2026-10-09 00:20:48 +08:00
} 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-10-13 14:39:06 +08:00
import {
hasUncertainTypeOnlyModuleSideEffect ,
isTypeOnlyExportDeclaration ,
isTypeOnlyImportDeclaration ,
2026-10-15 09:34:18 -07:00
semanticImportEqualsSpecifier ,
2026-10-13 14:39:06 +08:00
} from "./javascriptModuleDeclaration.js" ;
2026-10-10 23:28:52 -03:00
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-14 17:59:44 -07:00
: analyzeParsedJavaScriptStaticSource ( source , 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-10 05:27:41 -07:00
) : JavaScriptStaticAnalysis =>
2026-10-14 17:59:44 -07:00
completeSemanticSteps ( analyzeParsedJavaScriptStaticSourceSteps ( source , file ));
2026-10-10 05:27:41 -07:00
/** Static analysis that yields during its source traversal; results are unchanged. */
export function * analyzeParsedJavaScriptStaticSourceSteps (
source : string ,
file : ParsedJavaScriptSource ,
) : Generator < void , JavaScriptStaticAnalysis > {
2026-10-09 05:09:19 +08:00
const accumulator = createJavaScriptAnalysisAccumulator ( source . length );
2026-10-14 17:59:44 -07:00
// One definitions pass serves receiver classification and binding lookup.
2026-10-15 15:59:42 +08:00
const index = yield * indexJavaScriptBindingsWithReceiversSteps ( file . program );
const { bindings , receivers } = index ;
yield * index . indexAmbientWritesSteps ();
2026-10-20 17:25:19 +08:00
if ( hasPossibleCommonJsDelegateWrite ( bindings ))
yield * index . indexMemberMutationFactsSteps ();
2026-10-11 19:44:17 -07:00
const electronIdentity = yield * indexJavaScriptElectronIdentitySteps (
file . program ,
bindings ,
);
2026-10-16 08:14:52 +08:00
const findings : JavaScriptFindingContext = {
2026-10-20 17:25:19 +08:00
nodePathResolve : createStaticNodePathResolveQuery ( index ),
2026-10-16 08:14:52 +08:00
source ,
accumulator ,
vitePreloadDependencyTable : indexVitePreloadDependencyTable ( file . program ),
openReceiverFacts : receivers.openReceivers ,
electronBindings : electronIdentity.bindings ,
2026-10-20 17:25:19 +08:00
electronInvocationPath : electronIdentity.invocationPath ,
electronAppPathChangePossible : electronIdentity.mayChangeAppPath ,
2026-10-16 08:14:52 +08:00
rpcScan : createJavaScriptRpcScan ( source , receivers . rpcClientRoots , index ),
};
yield * traverseStaticSourceSteps ( file , findings , bindings );
2026-10-16 14:52:33 +08:00
yield * finishElectronBrowserWindowsSteps ( findings , index );
2026-10-20 17:25:19 +08:00
if ( accumulator . pendingModuleRequireCalls . length > 0 ) {
yield * index . indexMemberMutationFactsSteps ();
finishModuleRequireCalls ( findings , bindings );
}
2026-10-18 04:55:18 +08:00
if (
accumulator . pendingNetworkCalls . length > 0 ||
accumulator . pendingSqliteCalls . length > 0
) {
2026-10-16 08:14:52 +08:00
yield * index . indexMemberMutationFactsSteps ();
finishPartialNetworkEndpoints ( findings , bindings , index );
2026-10-18 04:55:18 +08:00
finishSqliteStorageCalls ( findings , bindings , index );
2026-10-16 08:14:52 +08:00
}
2026-10-12 09:28:59 +08:00
accumulator . electronCallPaths = collectElectronWrappedCallPaths (
file . program ,
bindings ,
source ,
accumulator ,
2026-10-11 19:44:17 -07:00
electronIdentity ,
2026-10-12 09:28:59 +08:00
);
2026-10-06 01:18:25 -07:00
addSourceMapDirectives ( source , file . comments ?? [], accumulator );
2026-10-19 12:26:54 +08:00
return finalizeStaticAnalysis ( source , file , accumulator , bindings );
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-12 10:03:03 +08:00
bindings : JavaScriptSemanticAnalysisState ,
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 ;
2026-10-12 10:03:03 +08:00
inspectNode ( node , findings , bindings );
2026-10-07 17:40:05 +08:00
return undefined ;
},
2026-09-29 15:30:43 +08:00
});
2026-10-15 15:59:42 +08:00
yield * finishRpcScanSteps ( findings );
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 ,
2026-10-19 12:26:54 +08:00
bindings : JavaScriptSemanticAnalysisState ,
2026-07-16 06:45:34 +08:00
) : JavaScriptStaticAnalysis => {
2026-07-16 05:29:40 +08:00
const parserErrors = file . errors . length ;
const limitations = [
2026-10-20 17:25:19 +08:00
...( bindings . valueResourceLimits . has ( "alias-selections" )
? [ semanticResourceLimitReason ( "alias-selections" )]
: []),
2026-10-19 12:26:54 +08:00
...( bindings . returnedReferenceWidenings . size > 0
? [ RETURNED_REFERENCE_WIDENING_LIMITATION ]
: []),
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-13 14:39:06 +08:00
...( hasUncertainTypeOnlyModuleSideEffect ( file . program )
? [
"Compiler options are unavailable; inline type-only imports or re-exports may preserve a module side-effect dependency." ,
]
: []),
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 :
2026-10-20 17:25:19 +08:00
parserErrors > 0 ||
accumulator . unknownFindings > 0 ||
bindings . valueResourceLimits . has ( "alias-selections" )
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-20 17:25:19 +08:00
...( accumulator . appPathChanges . length > 0
? {
app_path_changes : finalizeLocatedFindings (
accumulator . appPathChanges ,
accumulator . moduleRangeIndex ,
),
}
: {}),
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-10-12 10:03:03 +08:00
bindings : JavaScriptSemanticAnalysisState ,
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 10:03:03 +08:00
inspectBundlerBootstrap ( source , node , accumulator , bindings );
2026-10-13 04:12:14 +08:00
inspectBundlerRegistration ( source , node , accumulator , bindings );
inspectEsbuildWrapper ( source , node , accumulator , bindings );
2026-10-13 06:24:45 +08:00
inspectCall ( source , node , findings , bindings );
2026-10-09 00:20:48 +08:00
} else if ( t . isOptionalCallExpression ( node )) {
2026-10-13 06:24:45 +08:00
inspectCall ( source , node , findings , bindings );
} else if ( t . isNewExpression ( node ))
inspectCall ( source , node , findings , bindings );
2026-10-13 05:04:21 +08:00
if ( t . isTSImportEqualsDeclaration ( node )) {
const specifier = semanticImportEqualsSpecifier ( node );
if ( specifier !== null )
addReference ( findings , { node , kind : "require" , specifier });
} else if (
2026-07-16 05:29:40 +08:00
( t . isImportDeclaration ( node ) || t . isExportAllDeclaration ( node )) &&
2026-10-13 14:39:06 +08:00
node . source !== undefined &&
! ( t . isImportDeclaration ( node ) && isTypeOnlyImportDeclaration ( node )) &&
! ( t . isExportAllDeclaration ( node ) && isTypeOnlyExportDeclaration ( node ))
2026-07-16 05:29:40 +08:00
)
addReference ( findings , {
node ,
kind : "static-import" ,
specifier : node.source.value ,
});
2026-10-13 14:39:06 +08:00
else if (
t . isExportNamedDeclaration ( node ) &&
node . source != null &&
! isTypeOnlyExportDeclaration ( node )
)
2026-07-16 05:29:40 +08:00
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
};