rea-agents 2.4.0 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (186) hide show
  1. package/README.md +80 -18
  2. package/bridge/ghidra/ReaGhidraBridge.java +48 -1
  3. package/bridge/hopper_bridge.py +259 -24
  4. package/dist/application/AnalysisClientCleanup.js +14 -0
  5. package/dist/application/ApplicationWorkflowEvidenceResolver.js +31 -13
  6. package/dist/application/BinarySession.js +15 -4
  7. package/dist/application/BinarySessionStatus.js +31 -1
  8. package/dist/application/BrowserEvidence.js +14 -0
  9. package/dist/application/BrowserScenarioCapturePort.js +1 -0
  10. package/dist/application/BrowserScenarioCaptureService.js +53 -0
  11. package/dist/application/BrowserScenarioEvidence.js +40 -0
  12. package/dist/application/CallPathTracing.js +160 -0
  13. package/dist/application/CapabilityInventory.js +68 -12
  14. package/dist/application/CompositeProvider.js +10 -1
  15. package/dist/application/DirectAnalysis.js +7 -2
  16. package/dist/application/EnhancedLiteralTracing.js +118 -0
  17. package/dist/application/EnhancedTools.js +35 -121
  18. package/dist/application/JavaScriptApplicationWorkflowEvidence.js +13 -0
  19. package/dist/application/JavaScriptApplicationWorkflowService.js +27 -1
  20. package/dist/application/JavaScriptRuntimeObservationEvidence.js +34 -0
  21. package/dist/application/JavaScriptRuntimeObservationPort.js +1 -0
  22. package/dist/application/JavaScriptRuntimeObservationService.js +46 -0
  23. package/dist/application/JavaScriptSemanticGraphAsyncProjection.js +147 -0
  24. package/dist/application/JavaScriptSemanticGraphBuilder.js +34 -6
  25. package/dist/application/JavaScriptSemanticGraphChildProcessProjection.js +156 -0
  26. package/dist/application/JavaScriptSemanticGraphConstruction.js +2 -2
  27. package/dist/application/JavaScriptSemanticGraphDataProjection.js +203 -0
  28. package/dist/application/JavaScriptSemanticGraphFingerprintProjection.js +25 -0
  29. package/dist/application/JavaScriptSemanticGraphFlowProjection.js +129 -8
  30. package/dist/application/JavaScriptSemanticGraphObjectProjection.js +81 -0
  31. package/dist/application/JavaScriptSemanticGraphProjection.js +14 -7
  32. package/dist/application/JavaScriptSemanticGraphResourceProjection.js +59 -0
  33. package/dist/application/JavaScriptSemanticGraphValueProjection.js +77 -0
  34. package/dist/application/ProcessReactiveEffects.js +6 -0
  35. package/dist/application/ProcessReactiveHarness.js +16 -1
  36. package/dist/application/ProjectPermissionStore.js +1 -0
  37. package/dist/application/ReconstructionObligationAuthority.js +72 -0
  38. package/dist/application/ReconstructionObligationCandidates.js +265 -0
  39. package/dist/application/ReconstructionObligationLedgerDiagnostics.js +47 -0
  40. package/dist/application/ReconstructionObligationLedgerEvaluation.js +263 -0
  41. package/dist/application/ReconstructionObligationLedgerProjection.js +86 -0
  42. package/dist/application/ReconstructionObligationLedgerService.js +72 -0
  43. package/dist/application/ReconstructionObligationOriginalCases.js +33 -0
  44. package/dist/application/ReconstructionObligationPolicies.js +132 -0
  45. package/dist/application/ReconstructionReadinessService.js +48 -0
  46. package/dist/artifacts/ArtifactProvider.js +87 -22
  47. package/dist/browser/BrowserScenarioSecrets.js +53 -0
  48. package/dist/browser/BrowserScenarioSessionPort.js +1 -0
  49. package/dist/browser/CdpBrowserProvider.js +2 -2
  50. package/dist/browser/CdpCaptureDocuments.js +57 -0
  51. package/dist/browser/CdpCaptureStorage.js +71 -29
  52. package/dist/browser/CdpCaptureStorageFingerprints.js +328 -0
  53. package/dist/browser/CdpEndpoint.js +8 -6
  54. package/dist/browser/CdpPageCapture.js +2 -0
  55. package/dist/browser/JavaScriptRuntimeScope.js +38 -0
  56. package/dist/browser/PlaywrightBrowserScenarioProvider.js +253 -0
  57. package/dist/browser/PlaywrightExecutionBoundary.js +42 -0
  58. package/dist/browser/PlaywrightScenarioActions.js +62 -0
  59. package/dist/browser/PlaywrightScenarioArtifacts.js +238 -0
  60. package/dist/browser/PlaywrightScenarioBrowser.js +144 -0
  61. package/dist/browser/PlaywrightScenarioEvents.js +301 -0
  62. package/dist/browser/PlaywrightScenarioSession.js +300 -0
  63. package/dist/browser/V8InspectorCaptureProjection.js +110 -0
  64. package/dist/browser/V8InspectorEndpoint.js +72 -0
  65. package/dist/browser/V8InspectorProvider.js +267 -0
  66. package/dist/catalogIdentity.js +8 -0
  67. package/dist/cli/artifactCommands.js +39 -0
  68. package/dist/cli/coreAnalysisCommands.js +37 -0
  69. package/dist/cli.js +4 -0
  70. package/dist/cliApplicationCommands.js +30 -2
  71. package/dist/cliBrowserAdvancedCommands.js +17 -5
  72. package/dist/cliBrowserCommands.js +1 -0
  73. package/dist/cliBrowserScenarioCommands.js +43 -0
  74. package/dist/cliCommandNames.js +8 -0
  75. package/dist/cliJavaScriptRuntimeCommands.js +164 -0
  76. package/dist/cliObservationOptions.js +4 -0
  77. package/dist/cliPolicyCommands.js +1 -0
  78. package/dist/config/environment.js +12 -0
  79. package/dist/config/parseConfig.js +88 -17
  80. package/dist/contracts/analysisSearchContract.js +14 -0
  81. package/dist/contracts/applicationToolContracts.js +86 -2
  82. package/dist/contracts/applicationWorkflowInputContracts.js +18 -0
  83. package/dist/contracts/artifactToolContracts.js +22 -0
  84. package/dist/contracts/browserProviderToolContracts.js +7 -0
  85. package/dist/contracts/browserScenarioToolContracts.js +81 -0
  86. package/dist/contracts/browserToolContracts.js +88 -98
  87. package/dist/contracts/enhancedInputs.js +25 -6
  88. package/dist/contracts/functionInstructionContract.js +8 -0
  89. package/dist/contracts/hopperMemoryContracts.js +22 -0
  90. package/dist/contracts/javascriptApplicationWorkflowExamples.js +42 -0
  91. package/dist/contracts/javascriptRuntimeObservationToolContracts.js +58 -0
  92. package/dist/contracts/reconstructionReadinessExample.js +436 -0
  93. package/dist/contracts/toolContractExamples.js +5 -0
  94. package/dist/contracts/toolContractTypes.js +1 -0
  95. package/dist/contracts/toolContracts.js +13 -21
  96. package/dist/contracts/toolEffects.js +21 -0
  97. package/dist/contracts/toolOutputSchemaGroups.js +70 -20
  98. package/dist/contracts/toolOutputSchemaPrimitives.js +37 -0
  99. package/dist/domain/analysisErrorProjection.js +8 -0
  100. package/dist/domain/artifactInspection.js +347 -0
  101. package/dist/domain/browserCaptureComparison.js +82 -0
  102. package/dist/domain/browserObservation.js +8 -1
  103. package/dist/domain/browserObservationErrors.js +1 -0
  104. package/dist/domain/browserObservationSchemas.js +26 -0
  105. package/dist/domain/browserScenario.js +165 -0
  106. package/dist/domain/browserScenarioCapture.js +57 -0
  107. package/dist/domain/browserScenarioCaptureValues.js +204 -0
  108. package/dist/domain/browserScenarioDiff.js +324 -0
  109. package/dist/domain/browserScenarioDiffValues.js +157 -0
  110. package/dist/domain/browserScenarioNormalization.js +45 -0
  111. package/dist/domain/browserScenarioValues.js +372 -0
  112. package/dist/domain/hopperValues.js +10 -0
  113. package/dist/domain/javascriptRuntimeObservation.js +170 -0
  114. package/dist/domain/javascriptRuntimeReconciliationParsing.js +120 -2
  115. package/dist/domain/javascriptRuntimeReconciliationResult.js +3 -3
  116. package/dist/domain/javascriptRuntimeReconciliationRuntime.js +2 -2
  117. package/dist/domain/javascriptRuntimeReconciliationSchemas.js +1 -1
  118. package/dist/domain/javascriptSemanticAnalysis.js +5 -4
  119. package/dist/domain/javascriptSemanticAsyncEffects.js +253 -0
  120. package/dist/domain/javascriptSemanticChildProcesses.js +237 -0
  121. package/dist/domain/javascriptSemanticDataEffectHelpers.js +125 -0
  122. package/dist/domain/javascriptSemanticDataEffects.js +284 -0
  123. package/dist/domain/javascriptSemanticDerivedAnalysis.js +41 -0
  124. package/dist/domain/javascriptSemanticFingerprints.js +174 -0
  125. package/dist/domain/javascriptSemanticIr.js +21 -1
  126. package/dist/domain/javascriptSemanticObjects.js +115 -0
  127. package/dist/domain/javascriptSemanticPromises.js +290 -0
  128. package/dist/domain/javascriptSemanticResources.js +136 -0
  129. package/dist/domain/javascriptStaticAnalysis.js +4 -3
  130. package/dist/domain/javascriptStaticAnalysisBundler.js +38 -0
  131. package/dist/domain/javascriptStaticAnalysisCalls.js +23 -1
  132. package/dist/domain/javascriptStaticAnalysisHelpers.js +8 -0
  133. package/dist/domain/processCapture.js +8 -2
  134. package/dist/domain/processReactiveScenario.js +1 -0
  135. package/dist/domain/processReactiveTransition.js +9 -4
  136. package/dist/domain/providerCleanupError.js +11 -0
  137. package/dist/domain/reconstructionObligationLedgerSchemas.js +278 -0
  138. package/dist/domain/reconstructionReadiness.js +121 -0
  139. package/dist/domain/reconstructionReadinessFindingHelpers.js +17 -0
  140. package/dist/domain/reconstructionReadinessIntegrityFindings.js +118 -0
  141. package/dist/domain/reconstructionReadinessJourneyFindings.js +193 -0
  142. package/dist/domain/reconstructionReadinessSchemas.js +240 -0
  143. package/dist/domain/sourceToBundleComparison.js +335 -0
  144. package/dist/domain/sourceToBundleComparisonSchemas.js +133 -0
  145. package/dist/domain/sourceToBundleSignals.js +202 -0
  146. package/dist/domain/webCaptureDiff.js +141 -27
  147. package/dist/domain/webScreenshot.js +1 -1
  148. package/dist/generatedPackageMetadata.js +1 -1
  149. package/dist/ghidra/GhidraDefaults.js +1 -1
  150. package/dist/ghidra/GhidraFunctionValues.js +13 -1
  151. package/dist/ghidra/GhidraProviderCapabilities.js +7 -0
  152. package/dist/hopper/HopperCleanup.js +158 -0
  153. package/dist/hopper/HopperClient.js +69 -99
  154. package/dist/hopper/HopperProvider.js +20 -1
  155. package/dist/hopper/HopperRequestQueue.js +187 -0
  156. package/dist/hopper/HopperResponseStream.js +46 -0
  157. package/dist/hopper/protocol.js +1 -0
  158. package/dist/main/transport.js +24 -24
  159. package/dist/main.js +6 -0
  160. package/dist/mcpDoctor.js +25 -2
  161. package/dist/process/ProcessCommandIdentity.js +19 -0
  162. package/dist/process/ProcessOwnership.js +11 -8
  163. package/dist/server/DynamicToolAvailability.js +70 -0
  164. package/dist/server/createServer.js +46 -1
  165. package/dist/server/lifecycleProgress.js +10 -0
  166. package/dist/server/permissionFailure.js +12 -0
  167. package/dist/server/registerApplicationTools/compareSourceToBundle.js +35 -0
  168. package/dist/server/registerApplicationTools/helpers.js +11 -7
  169. package/dist/server/registerApplicationTools/obligationLedger.js +27 -0
  170. package/dist/server/registerApplicationTools/readiness.js +55 -0
  171. package/dist/server/registerApplicationTools.js +6 -0
  172. package/dist/server/registerArtifactTools.js +9 -1
  173. package/dist/server/registerBrowserScenarioTool.js +25 -0
  174. package/dist/server/registerBrowserTools.js +2 -2
  175. package/dist/server/registerCloseLifecycleTool.js +57 -0
  176. package/dist/server/registerEnhancedTools.js +16 -5
  177. package/dist/server/registerEvidenceResources.js +4 -0
  178. package/dist/server/registerEvidenceTools.js +9 -2
  179. package/dist/server/registerJavaScriptRuntimeObservationTools.js +37 -0
  180. package/dist/server/registerReconstructionObligationLedgerResource.js +39 -0
  181. package/dist/server/registerReconstructionReadinessResource.js +39 -0
  182. package/dist/server/registerSessionStatusTool.js +11 -8
  183. package/dist/server/registerSessionTools.js +11 -57
  184. package/dist/server/sessionAvailabilityPolicy.js +10 -8
  185. package/package.json +12 -6
  186. package/skills/reverse-engineer-anything/SKILL.md +2 -2
@@ -45,9 +45,9 @@ const captureSummaries = (captures, runtime) => captures.map((capture) => {
45
45
  kind: capture.kind,
46
46
  target_node_id: targetNode.node_id,
47
47
  target_key: capture.inspection.target.target_id,
48
- target_location: capture.kind === "browser"
49
- ? capture.inspection.target.url.slice(0, 4_096)
50
- : capture.inspection.target.file_path.slice(0, 4_096),
48
+ target_location: ("url" in capture.inspection.target
49
+ ? capture.inspection.target.url
50
+ : capture.inspection.target.file_path).slice(0, 4_096),
51
51
  frames: capture.inspection.frames.length,
52
52
  scripts: capture.inspection.scripts.items.length,
53
53
  workers: capture.inspection.workers.length,
@@ -197,7 +197,7 @@ const runtimeEvidence = (capture, input) => ({
197
197
  name: capture.evidence.provider.id,
198
198
  version: capture.evidence.provider.version ??
199
199
  (capture.kind === "browser" ? "2" : "1"),
200
- operation: capture.kind === "browser" ? "inspect_web_page" : "inspect_electron_page",
200
+ operation: capture.evidence.operation,
201
201
  executable_sha256: null,
202
202
  },
203
203
  coverage: runtimeSectionCoverage(capture, input.section),
@@ -260,7 +260,7 @@ const runtimeRelationshipEvidence = (capture, entity) => ({
260
260
  name: capture.evidence.provider.id,
261
261
  version: capture.evidence.provider.version ??
262
262
  (capture.kind === "browser" ? "2" : "1"),
263
- operation: capture.kind === "browser" ? "inspect_web_page" : "inspect_electron_page",
263
+ operation: capture.evidence.operation,
264
264
  executable_sha256: null,
265
265
  },
266
266
  coverage: runtimeSectionCoverage(capture, entity.section),
@@ -133,7 +133,7 @@ const layerSummarySchema = z.strictObject({
133
133
  const captureSummarySchema = z.strictObject({
134
134
  evidence_id: evidenceIdSchema,
135
135
  capture_sha256: digestSchema,
136
- kind: z.enum(["browser", "electron"]),
136
+ kind: z.enum(["browser", "electron", "v8-inspector"]),
137
137
  target_node_id: nodeIdSchema,
138
138
  target_key: boundedTextSchema,
139
139
  target_location: boundedTextSchema,
@@ -5,7 +5,7 @@ import { collectSemanticModuleLink, collectSemanticCallable, collectSemanticRefe
5
5
  import { currentSemanticScope, resolveSemanticBindingFromScope, semanticScopeId, semanticVariableScope, } from "./javascriptSemanticState.js";
6
6
  import { traverseJavaScriptAst } from "./javascriptSemanticTraversal.js";
7
7
  import { collectSemanticReturns, resolveSemanticModuleCallables, } from "./javascriptSemanticReturns.js";
8
- import { collectJavaScriptSemanticCalls } from "./javascriptSemanticCalls.js";
8
+ import { collectJavaScriptDerivedSemantics } from "./javascriptSemanticDerivedAnalysis.js";
9
9
  import { compareCodePoints, propertyName, range, } from "./javascriptStaticAnalysisHelpers.js";
10
10
  /** Recover bounded lexical bindings, aliases, constants, and module links. */
11
11
  export const analyzeJavaScriptSemantics = (source, inputLimits = {}) => {
@@ -28,16 +28,16 @@ export const analyzeJavaScriptSemantics = (source, inputLimits = {}) => {
28
28
  const bindings = immutableSemanticBindings(state);
29
29
  const callables = collectSemanticReturns(file.program, state, parserPartial);
30
30
  const moduleLinks = resolveSemanticModuleCallables(state, callables);
31
- const calls = collectJavaScriptSemanticCalls(file.program, state, callables);
31
+ const derived = collectJavaScriptDerivedSemantics(file.program, state, callables, parserPartial);
32
32
  return {
33
33
  schema: "JavaScriptSemanticIR",
34
- schemaVersion: 3,
34
+ schemaVersion: 4,
35
35
  scopes: immutableSemanticScopes(state),
36
36
  bindings,
37
37
  callables,
38
38
  references,
39
39
  moduleLinks,
40
- ...calls,
40
+ ...derived,
41
41
  coverage: {
42
42
  status: state.limitsReached.size > 0
43
43
  ? "truncated"
@@ -59,6 +59,7 @@ export const analyzeJavaScriptSemantics = (source, inputLimits = {}) => {
59
59
  "Values and aliases were recovered from inert syntax only; no JavaScript was executed.",
60
60
  "Return sites include only direct callable returns; nested callable returns remain separate.",
61
61
  "Local call, argument, return, and closure relations are static candidates and do not prove runtime invocation.",
62
+ "Promise ownership covers explicit unshadowed Promise construction, static factories, aggregation, chaining, and await syntax only.",
62
63
  "Cross-function mutation and dynamic property resolution remain unknown.",
63
64
  ],
64
65
  };
@@ -0,0 +1,253 @@
1
+ import * as t from "@babel/types";
2
+ import { reachSemanticLimit, semanticCallableIdForNode, semanticStaticPropertyName, } from "./javascriptSemanticProjection.js";
3
+ import { resolveSemanticBindingState, semanticResolutionBlocked, } from "./javascriptSemanticState.js";
4
+ import { traverseJavaScriptAst } from "./javascriptSemanticTraversal.js";
5
+ import { range } from "./javascriptStaticAnalysisHelpers.js";
6
+ const EVENT_REGISTER_METHODS = [
7
+ "on",
8
+ "once",
9
+ "addListener",
10
+ "prependListener",
11
+ "prependOnceListener",
12
+ ];
13
+ const EVENT_REMOVE_METHODS = [
14
+ "off",
15
+ "removeListener",
16
+ "removeAllListeners",
17
+ ];
18
+ const EVENT_METHODS = [
19
+ ...EVENT_REGISTER_METHODS,
20
+ ...EVENT_REMOVE_METHODS,
21
+ "emit",
22
+ ];
23
+ const TIMER_SCHEDULE_METHODS = [
24
+ "setTimeout",
25
+ "setInterval",
26
+ "setImmediate",
27
+ ];
28
+ const TIMER_CANCEL_METHODS = [
29
+ "clearTimeout",
30
+ "clearInterval",
31
+ "clearImmediate",
32
+ ];
33
+ /** Recover explicit EventEmitter and Node timer candidates from inert syntax. */
34
+ export const collectJavaScriptSemanticAsyncEffects = (program, state, callables) => {
35
+ const events = [];
36
+ const timers = [];
37
+ const callableIds = new Set(callables.map(({ callableId }) => callableId));
38
+ const callableStack = [];
39
+ traverseJavaScriptAst(program, {
40
+ enter: (node, parent) => {
41
+ const callableId = semanticCallableIdForNode(node);
42
+ if (callableId !== null && callableIds.has(callableId))
43
+ callableStack.push(callableId);
44
+ if (!t.isCallExpression(node) && !t.isOptionalCallExpression(node))
45
+ return;
46
+ const ownerCallableId = callableStack.at(-1) ?? null;
47
+ collectEvent(node, ownerCallableId, state, events);
48
+ collectTimer({ node, parent, ownerCallableId, state, output: timers });
49
+ },
50
+ exit: (node) => {
51
+ const callableId = semanticCallableIdForNode(node);
52
+ if (callableId !== null && callableStack.at(-1) === callableId)
53
+ callableStack.pop();
54
+ },
55
+ });
56
+ return {
57
+ eventOperations: events,
58
+ timerOperations: resolveTimers(timers),
59
+ };
60
+ };
61
+ const collectEvent = (node, ownerCallableId, state, output) => {
62
+ const member = memberCallee(node);
63
+ if (member === null)
64
+ return;
65
+ const method = EVENT_METHODS.find((candidate) => candidate ===
66
+ semanticStaticPropertyName(member.property, member.computed));
67
+ if (method === undefined)
68
+ return;
69
+ const kind = eventKind(method);
70
+ if (output.length >= state.limits.maxEventOperations) {
71
+ reachSemanticLimit(state, "maxEventOperations");
72
+ return;
73
+ }
74
+ const eventArgument = node.arguments[0];
75
+ const listenerArgument = node.arguments[1];
76
+ const emitter = emitterIdentity(member.object, state);
77
+ const eventName = literalString(eventArgument);
78
+ output.push({
79
+ eventId: `event:${kind}:${String(node.start ?? -1)}:${String(node.end ?? -1)}`,
80
+ kind,
81
+ method,
82
+ location: range(node),
83
+ ownerCallableId,
84
+ emitterKey: emitter.key,
85
+ emitterBindingId: emitter.bindingId,
86
+ eventName,
87
+ listenerBindingId: bindingId(listenerArgument, state),
88
+ listenerLocation: t.isNode(listenerArgument)
89
+ ? range(listenerArgument)
90
+ : null,
91
+ resolution: eventName === null
92
+ ? "unresolved"
93
+ : emitter.complete
94
+ ? "complete"
95
+ : "partial",
96
+ });
97
+ };
98
+ const eventKind = (method) => {
99
+ if (EVENT_REGISTER_METHODS.some((candidate) => candidate === method))
100
+ return "register";
101
+ if (EVENT_REMOVE_METHODS.some((candidate) => candidate === method))
102
+ return "remove";
103
+ return "dispatch";
104
+ };
105
+ const emitterIdentity = (node, state) => {
106
+ if (t.isIdentifier(node)) {
107
+ const binding = resolveSemanticBindingState(state, node, node.name);
108
+ if (binding !== undefined)
109
+ return {
110
+ key: `binding:${binding.bindingId}`,
111
+ bindingId: binding.bindingId,
112
+ complete: true,
113
+ };
114
+ return {
115
+ key: `global:${node.name}`,
116
+ bindingId: null,
117
+ complete: !semanticResolutionBlocked(state, node, node.name),
118
+ };
119
+ }
120
+ if (t.isThisExpression(node))
121
+ return { key: "this", bindingId: null, complete: true };
122
+ return {
123
+ key: `expression:${String(node.start ?? -1)}:${String(node.end ?? -1)}`,
124
+ bindingId: null,
125
+ complete: false,
126
+ };
127
+ };
128
+ const collectTimer = (input) => {
129
+ const { node, parent, ownerCallableId, state, output } = input;
130
+ const method = timerMethod(node.callee, state);
131
+ if (method === null)
132
+ return;
133
+ if (output.length >= state.limits.maxTimerOperations) {
134
+ reachSemanticLimit(state, "maxTimerOperations");
135
+ return;
136
+ }
137
+ const kind = TIMER_SCHEDULE_METHODS.some((candidate) => candidate === method)
138
+ ? "schedule"
139
+ : "cancel";
140
+ output.push({
141
+ node,
142
+ method,
143
+ kind,
144
+ ownerCallableId,
145
+ handleBindingId: kind === "schedule"
146
+ ? assignedBindingId(parent, node, state)
147
+ : bindingId(node.arguments[0], state),
148
+ });
149
+ };
150
+ const resolveTimers = (candidates) => {
151
+ const schedulesByBinding = new Map();
152
+ for (const candidate of candidates) {
153
+ if (candidate.kind !== "schedule" || candidate.handleBindingId === null)
154
+ continue;
155
+ const existing = schedulesByBinding.get(candidate.handleBindingId) ?? [];
156
+ existing.push(candidate);
157
+ schedulesByBinding.set(candidate.handleBindingId, existing);
158
+ }
159
+ return candidates.map((candidate) => {
160
+ const linked = candidate.kind === "cancel" && candidate.handleBindingId !== null
161
+ ? (schedulesByBinding.get(candidate.handleBindingId) ?? [])
162
+ : [];
163
+ return {
164
+ timerId: timerId(candidate),
165
+ kind: candidate.kind,
166
+ method: candidate.method,
167
+ location: range(candidate.node),
168
+ ownerCallableId: candidate.ownerCallableId,
169
+ handleBindingId: candidate.handleBindingId,
170
+ linkedTimerId: linked.length === 1 && linked[0] !== undefined
171
+ ? timerId(linked[0])
172
+ : null,
173
+ delayMilliseconds: timerDelay(candidate),
174
+ resolution: candidate.kind === "schedule"
175
+ ? "complete"
176
+ : linked.length === 1
177
+ ? "complete"
178
+ : linked.length > 1
179
+ ? "partial"
180
+ : "unresolved",
181
+ };
182
+ });
183
+ };
184
+ const timerMethod = (callee, state) => {
185
+ if (t.isIdentifier(callee)) {
186
+ const binding = resolveSemanticBindingState(state, callee, callee.name);
187
+ if (binding === undefined) {
188
+ const method = admittedTimerMethod(callee.name);
189
+ if (method === null)
190
+ return null;
191
+ return semanticResolutionBlocked(state, callee, callee.name)
192
+ ? null
193
+ : method;
194
+ }
195
+ return timerMethodFromBinding(binding);
196
+ }
197
+ if (!t.isMemberExpression(callee) && !t.isOptionalMemberExpression(callee))
198
+ return null;
199
+ const method = admittedTimerMethod(semanticStaticPropertyName(callee.property, callee.computed));
200
+ return method !== null &&
201
+ t.isIdentifier(callee.object) &&
202
+ timerNamespaceBinding(resolveSemanticBindingState(state, callee.object, callee.object.name))
203
+ ? method
204
+ : null;
205
+ };
206
+ const admittedTimerMethod = (value) => [...TIMER_SCHEDULE_METHODS, ...TIMER_CANCEL_METHODS].find((candidate) => candidate === value) ?? null;
207
+ const timerMethodFromBinding = (binding) => {
208
+ for (const { specifier, importedPath } of binding.directOrigins) {
209
+ if (!timerModule(specifier))
210
+ continue;
211
+ const method = admittedTimerMethod(importedPath.at(-1) ?? "");
212
+ if (method !== null)
213
+ return method;
214
+ }
215
+ return null;
216
+ };
217
+ const timerNamespaceBinding = (binding) => binding?.directOrigins.some(({ specifier, importedPath }) => timerModule(specifier) && importedPath.length === 0) ?? false;
218
+ const timerModule = (specifier) => specifier === "timers" || specifier === "node:timers";
219
+ const assignedBindingId = (parent, node, state) => {
220
+ const identifier = t.isVariableDeclarator(parent) &&
221
+ parent.init === node &&
222
+ t.isIdentifier(parent.id)
223
+ ? parent.id
224
+ : t.isAssignmentExpression(parent) &&
225
+ parent.right === node &&
226
+ t.isIdentifier(parent.left)
227
+ ? parent.left
228
+ : null;
229
+ return identifier === null
230
+ ? null
231
+ : (resolveSemanticBindingState(state, identifier, identifier.name)
232
+ ?.bindingId ?? null);
233
+ };
234
+ const bindingId = (node, state) => t.isIdentifier(node)
235
+ ? (resolveSemanticBindingState(state, node, node.name)?.bindingId ?? null)
236
+ : null;
237
+ const literalString = (node) => {
238
+ if (t.isStringLiteral(node))
239
+ return node.value;
240
+ if (t.isTemplateLiteral(node) && node.expressions.length === 0)
241
+ return node.quasis[0]?.value.cooked ?? node.quasis[0]?.value.raw ?? null;
242
+ return null;
243
+ };
244
+ const timerDelay = (candidate) => {
245
+ if (candidate.kind !== "schedule" || candidate.method === "setImmediate")
246
+ return null;
247
+ const delay = candidate.node.arguments[1];
248
+ return t.isNumericLiteral(delay) ? delay.value : null;
249
+ };
250
+ const timerId = (candidate) => `timer:${candidate.kind}:${String(candidate.node.start ?? -1)}:${String(candidate.node.end ?? -1)}`;
251
+ const memberCallee = (node) => t.isMemberExpression(node.callee) || t.isOptionalMemberExpression(node.callee)
252
+ ? node.callee
253
+ : null;
@@ -0,0 +1,237 @@
1
+ import * as t from "@babel/types";
2
+ import { reachSemanticLimit, semanticCallableIdForNode, semanticStaticPropertyName, } from "./javascriptSemanticProjection.js";
3
+ import { resolveSemanticBindingState, } from "./javascriptSemanticState.js";
4
+ import { traverseJavaScriptAst } from "./javascriptSemanticTraversal.js";
5
+ import { compareCodePoints, propertyName, range, } from "./javascriptStaticAnalysisHelpers.js";
6
+ const CHILD_PROCESS_METHODS = ["spawn", "exec", "execFile", "fork"];
7
+ const CHILD_LISTENER_METHODS = ["on", "once", "addListener"];
8
+ /** Recover explicit asynchronous node:child_process ownership candidates. */
9
+ export const collectJavaScriptSemanticChildProcesses = (program, state, callables) => {
10
+ const candidates = collectSpawns(program, state, callables);
11
+ const childProcessSpawns = candidates.map((candidate) => immutableSpawn(candidate));
12
+ return {
13
+ childProcessSpawns,
14
+ childProcessInteractions: collectInteractions(program, state, callables, candidates),
15
+ };
16
+ };
17
+ const collectSpawns = (program, state, callables) => {
18
+ const output = [];
19
+ const admittedCallables = new Set(callables.map(({ callableId }) => callableId));
20
+ const callableStack = [];
21
+ traverseJavaScriptAst(program, {
22
+ enter: (node, parent) => {
23
+ const callableId = semanticCallableIdForNode(node);
24
+ if (callableId !== null && admittedCallables.has(callableId))
25
+ callableStack.push(callableId);
26
+ if (!t.isCallExpression(node) && !t.isOptionalCallExpression(node))
27
+ return;
28
+ const method = childProcessMethod(node.callee, state);
29
+ if (method === null)
30
+ return;
31
+ if (operationLimitReached(output.length, state))
32
+ return;
33
+ output.push({
34
+ node,
35
+ method,
36
+ ownerCallableId: callableStack.at(-1) ?? null,
37
+ resultBindingId: assignedBindingId(parent, node, state),
38
+ });
39
+ },
40
+ exit: (node) => popCallable(node, callableStack),
41
+ });
42
+ return output;
43
+ };
44
+ const immutableSpawn = (candidate) => {
45
+ const options = spawnOptions(candidate);
46
+ const command = literalString(candidate.node.arguments[0]);
47
+ return {
48
+ processId: spawnId(candidate),
49
+ method: candidate.method,
50
+ location: range(candidate.node),
51
+ ownerCallableId: candidate.ownerCallableId,
52
+ resultBindingId: candidate.resultBindingId,
53
+ command,
54
+ argvCount: spawnArgvCount(candidate),
55
+ environmentSupplied: objectHasProperty(options, "env"),
56
+ stdioMode: stdioMode(options),
57
+ resolution: command === null ? "partial" : "complete",
58
+ };
59
+ };
60
+ const collectInteractions = (program, state, callables, spawns) => {
61
+ const output = [];
62
+ const callableIds = new Set(callables.map(({ callableId }) => callableId));
63
+ const callableStack = [];
64
+ const spawnByNode = new WeakMap();
65
+ const spawnsByBinding = new Map();
66
+ for (const spawn of spawns) {
67
+ spawnByNode.set(spawn.node, spawn);
68
+ if (spawn.resultBindingId === null)
69
+ continue;
70
+ const existing = spawnsByBinding.get(spawn.resultBindingId) ?? [];
71
+ existing.push(spawn);
72
+ spawnsByBinding.set(spawn.resultBindingId, existing);
73
+ }
74
+ const context = {
75
+ state,
76
+ spawnByNode,
77
+ spawnsByBinding,
78
+ };
79
+ traverseJavaScriptAst(program, {
80
+ enter: (node) => {
81
+ const callableId = semanticCallableIdForNode(node);
82
+ if (callableId !== null && callableIds.has(callableId))
83
+ callableStack.push(callableId);
84
+ if (!t.isCallExpression(node) && !t.isOptionalCallExpression(node))
85
+ return;
86
+ const interaction = childInteraction(node, callableStack.at(-1) ?? null, context);
87
+ if (interaction === null)
88
+ return;
89
+ if (operationLimitReached(spawns.length + output.length, state))
90
+ return;
91
+ output.push(interaction);
92
+ },
93
+ exit: (node) => popCallable(node, callableStack),
94
+ });
95
+ return output;
96
+ };
97
+ const childInteraction = (node, ownerCallableId, context) => {
98
+ const { state, spawnByNode, spawnsByBinding } = context;
99
+ const member = memberCallee(node);
100
+ if (member === null)
101
+ return null;
102
+ const method = semanticStaticPropertyName(member.property, member.computed);
103
+ const binding = objectBinding(member.object, state);
104
+ const linked = resolveChildCandidates(member.object, binding, spawnByNode, spawnsByBinding);
105
+ if (linked.length === 0)
106
+ return null;
107
+ const classification = classifyInteraction(method, node.arguments[0]);
108
+ if (classification === null)
109
+ return null;
110
+ const { kind, interactionMethod, eventName } = classification;
111
+ const listener = node.arguments[1];
112
+ return {
113
+ interactionId: `child:${kind}:${String(node.start ?? -1)}:${String(node.end ?? -1)}`,
114
+ kind,
115
+ method: interactionMethod,
116
+ location: range(node),
117
+ ownerCallableId,
118
+ processBindingId: binding?.bindingId ?? null,
119
+ linkedProcessIds: linked
120
+ .map((candidate) => spawnId(candidate))
121
+ .sort(compareCodePoints),
122
+ eventName: kind === "listener" ? eventName : null,
123
+ signalName: kind === "signal"
124
+ ? (literalString(node.arguments[0]) ?? "SIGTERM")
125
+ : null,
126
+ listenerLocation: kind === "listener" && t.isNode(listener) ? range(listener) : null,
127
+ resolution: linked.length === 1 ? "complete" : "partial",
128
+ };
129
+ };
130
+ const classifyInteraction = (method, argument) => {
131
+ if (method === "kill")
132
+ return { kind: "signal", interactionMethod: "kill", eventName: null };
133
+ const listenerMethod = CHILD_LISTENER_METHODS.find((candidate) => candidate === method);
134
+ const eventName = literalString(argument);
135
+ return listenerMethod !== undefined &&
136
+ (eventName === "exit" || eventName === "error")
137
+ ? { kind: "listener", interactionMethod: listenerMethod, eventName }
138
+ : null;
139
+ };
140
+ const childProcessMethod = (callee, state) => {
141
+ if (t.isIdentifier(callee)) {
142
+ const binding = resolveSemanticBindingState(state, callee, callee.name);
143
+ return binding === undefined ? null : methodFromBinding(binding);
144
+ }
145
+ if (!t.isMemberExpression(callee) && !t.isOptionalMemberExpression(callee))
146
+ return null;
147
+ const method = admittedChildMethod(semanticStaticPropertyName(callee.property, callee.computed));
148
+ if (method === null || !t.isIdentifier(callee.object))
149
+ return null;
150
+ const binding = resolveSemanticBindingState(state, callee.object, callee.object.name);
151
+ return childProcessNamespace(binding) ? method : null;
152
+ };
153
+ const methodFromBinding = (binding) => {
154
+ for (const { specifier, importedPath } of binding.directOrigins) {
155
+ if (!childProcessModule(specifier))
156
+ continue;
157
+ const method = admittedChildMethod(importedPath.at(-1) ?? "");
158
+ if (method !== null)
159
+ return method;
160
+ }
161
+ return null;
162
+ };
163
+ const admittedChildMethod = (value) => CHILD_PROCESS_METHODS.find((candidate) => candidate === value) ?? null;
164
+ const childProcessNamespace = (binding) => binding?.directOrigins.some(({ specifier, importedPath }) => childProcessModule(specifier) && importedPath.length === 0) ?? false;
165
+ const childProcessModule = (specifier) => specifier === "child_process" || specifier === "node:child_process";
166
+ const resolveChildCandidates = (node, binding, spawnByNode, spawnsByBinding) => {
167
+ const direct = spawnByNode.get(node);
168
+ if (direct !== undefined)
169
+ return [direct];
170
+ return binding === undefined
171
+ ? []
172
+ : (spawnsByBinding.get(binding.bindingId) ?? []);
173
+ };
174
+ const spawnOptions = (candidate) => {
175
+ const index = candidate.method === "exec" ? 1 : 2;
176
+ const value = candidate.node.arguments[index];
177
+ return t.isObjectExpression(value) ? value : null;
178
+ };
179
+ const spawnArgvCount = (candidate) => {
180
+ if (candidate.method === "exec")
181
+ return null;
182
+ const args = candidate.node.arguments[1];
183
+ return t.isArrayExpression(args) ? args.elements.length : null;
184
+ };
185
+ const objectHasProperty = (object, name) => objectProperty(object, name) !== null;
186
+ const stdioMode = (options) => {
187
+ const property = objectProperty(options, "stdio");
188
+ if (property === null || !t.isObjectProperty(property))
189
+ return "default";
190
+ if (t.isStringLiteral(property.value))
191
+ return property.value.value;
192
+ if (t.isArrayExpression(property.value))
193
+ return "array";
194
+ return "dynamic";
195
+ };
196
+ const objectProperty = (object, name) => object?.properties.find((property) => (t.isObjectMethod(property) || t.isObjectProperty(property)) &&
197
+ propertyName(property.key) === name) ?? null;
198
+ const objectBinding = (node, state) => t.isIdentifier(node)
199
+ ? resolveSemanticBindingState(state, node, node.name)
200
+ : undefined;
201
+ const assignedBindingId = (parent, node, state) => {
202
+ const identifier = t.isVariableDeclarator(parent) &&
203
+ parent.init === node &&
204
+ t.isIdentifier(parent.id)
205
+ ? parent.id
206
+ : t.isAssignmentExpression(parent) &&
207
+ parent.right === node &&
208
+ t.isIdentifier(parent.left)
209
+ ? parent.left
210
+ : null;
211
+ return identifier === null
212
+ ? null
213
+ : (resolveSemanticBindingState(state, identifier, identifier.name)
214
+ ?.bindingId ?? null);
215
+ };
216
+ const literalString = (node) => {
217
+ if (t.isStringLiteral(node))
218
+ return node.value;
219
+ if (t.isTemplateLiteral(node) && node.expressions.length === 0)
220
+ return node.quasis[0]?.value.cooked ?? node.quasis[0]?.value.raw ?? null;
221
+ return null;
222
+ };
223
+ const operationLimitReached = (retained, state) => {
224
+ if (retained < state.limits.maxChildProcessOperations)
225
+ return false;
226
+ reachSemanticLimit(state, "maxChildProcessOperations");
227
+ return true;
228
+ };
229
+ const popCallable = (node, stack) => {
230
+ const callableId = semanticCallableIdForNode(node);
231
+ if (callableId !== null && stack.at(-1) === callableId)
232
+ stack.pop();
233
+ };
234
+ const spawnId = (candidate) => `child:spawn:${String(candidate.node.start ?? -1)}:${String(candidate.node.end ?? -1)}`;
235
+ const memberCallee = (node) => t.isMemberExpression(node.callee) || t.isOptionalMemberExpression(node.callee)
236
+ ? node.callee
237
+ : null;