rea-agents 3.1.0 → 3.2.1

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 (590) hide show
  1. package/README.md +72 -95
  2. package/bridge/ghidra/ReaGhidraBridge.java +609 -841
  3. package/bridge/hopper_bridge.py +99 -344
  4. package/dist/application/AbortablePromise.js +6 -3
  5. package/dist/application/AnalysisSnapshotCache.js +0 -1
  6. package/dist/application/AnalysisSnapshotFiles.js +7 -7
  7. package/dist/application/AndroidApplicationService.js +1 -1
  8. package/dist/application/AppleApplicationService.js +1 -1
  9. package/dist/application/ArtifactExtraction.js +35 -54
  10. package/dist/application/ArtifactExtractionDestination.js +17 -0
  11. package/dist/application/ArtifactGraphConstruction.js +2 -17
  12. package/dist/application/ArtifactInventory/classify.js +2 -6
  13. package/dist/application/ArtifactInventory/hash.js +2 -20
  14. package/dist/application/ArtifactInventory/scanCanonical.js +8 -12
  15. package/dist/application/ArtifactInventory/scanReader.js +69 -51
  16. package/dist/application/ArtifactInventory.js +6 -14
  17. package/dist/application/AuthorizedArtifactInventory.js +3 -13
  18. package/dist/application/BinarySession.js +41 -13
  19. package/dist/application/BinarySessionComposition.js +0 -11
  20. package/dist/application/BinarySessionOpen.js +2 -1
  21. package/dist/application/BinarySessionRecords.js +5 -93
  22. package/dist/application/BinarySessionStatus.js +5 -16
  23. package/dist/application/BinaryTargetResolver.js +216 -0
  24. package/dist/application/BrowserEvidence.js +42 -58
  25. package/dist/application/BrowserObservationService.js +3 -6
  26. package/dist/application/BrowserScenarioCaptureService.js +3 -6
  27. package/dist/application/BrowserScenarioEvidence.js +3 -3
  28. package/dist/application/CallPathTracing.js +2 -36
  29. package/dist/application/CapabilityInventory.js +10 -15
  30. package/dist/application/ClientConfigurationDocument.js +27 -0
  31. package/dist/application/ClientRegistrationStatus.js +16 -13
  32. package/dist/application/CommandShimReplay.js +14 -1
  33. package/dist/application/DirectAnalysis.js +22 -46
  34. package/dist/application/DirectAnalysisStatus.js +5 -61
  35. package/dist/application/Doctor.js +11 -32
  36. package/dist/application/DoctorDiagnostics.js +1 -2
  37. package/dist/application/DoctorScope.js +1 -1
  38. package/dist/application/ElectronActiveEvidence.js +3 -8
  39. package/dist/application/ElectronActiveObservationService.js +3 -6
  40. package/dist/application/ElectronBoundaryAnalysis.js +7 -8
  41. package/dist/application/ElectronBoundaryGraphContext.js +1 -1
  42. package/dist/application/ElectronBoundaryGraphIpc.js +3 -3
  43. package/dist/application/ElectronBoundaryGraphNative.js +1 -2
  44. package/dist/application/ElectronBoundaryGraphWindows.js +6 -8
  45. package/dist/application/ElectronEvidence.js +4 -7
  46. package/dist/application/ElectronObservationService.js +5 -9
  47. package/dist/application/EnhancedLiteralTracing.js +42 -65
  48. package/dist/application/EnhancedTools.js +76 -44
  49. package/dist/application/EvidenceBundleCommands.js +11 -11
  50. package/dist/application/EvidenceBundleFiles.js +9 -15
  51. package/dist/application/EvidenceLedger.js +6 -41
  52. package/dist/application/FilesystemSnapshot.js +2 -3
  53. package/dist/application/InterfaceBuilderAnalysis.js +266 -0
  54. package/dist/application/InventoryProjectionEvidence.js +4 -2
  55. package/dist/application/InvestigationProviders.js +1 -1
  56. package/dist/application/JavaScriptApplicationEvidence.js +2 -5
  57. package/dist/application/JavaScriptApplicationEvidenceGraph.js +6 -10
  58. package/dist/application/JavaScriptApplicationService.js +6 -29
  59. package/dist/application/JavaScriptApplicationWorkflowEvidence.js +5 -5
  60. package/dist/application/JavaScriptApplicationWorkflowService.js +0 -8
  61. package/dist/application/JavaScriptArtifactAnalysis.js +38 -127
  62. package/dist/application/JavaScriptArtifactFiles.js +67 -107
  63. package/dist/application/JavaScriptArtifactGraphAccumulator.js +0 -7
  64. package/dist/application/JavaScriptArtifactGraphBuilder.js +7 -58
  65. package/dist/application/JavaScriptArtifactGraphBundlers.js +1 -6
  66. package/dist/application/JavaScriptArtifactGraphContext.js +12 -39
  67. package/dist/application/JavaScriptArtifactGraphDocuments.js +4 -11
  68. package/dist/application/JavaScriptArtifactGraphEvidence.js +0 -14
  69. package/dist/application/JavaScriptArtifactGraphFindings.js +3 -3
  70. package/dist/application/JavaScriptArtifactGraphStructure.js +16 -17
  71. package/dist/application/JavaScriptArtifactPathResolution.js +61 -49
  72. package/dist/application/JavaScriptArtifactReconstruction.js +5 -10
  73. package/dist/application/JavaScriptArtifactReconstructionInput.js +2 -14
  74. package/dist/application/JavaScriptJsonModules.js +2 -2
  75. package/dist/application/JavaScriptModuleRelationships.js +22 -27
  76. package/dist/application/JavaScriptReplayPlanning.js +9 -3
  77. package/dist/application/JavaScriptReplayService.js +6 -32
  78. package/dist/application/JavaScriptReturnShapeProjection.js +12 -42
  79. package/dist/application/JavaScriptRuntimeObservationEvidence.js +7 -8
  80. package/dist/application/JavaScriptRuntimeObservationService.js +4 -8
  81. package/dist/application/JavaScriptRuntimeReconciliationEvidence.js +2 -3
  82. package/dist/application/JavaScriptSemanticGraphBuilder.js +11 -41
  83. package/dist/application/JavaScriptSemanticGraphConstruction.js +5 -29
  84. package/dist/application/JavaScriptSemanticGraphEvidence.js +1 -1
  85. package/dist/application/JavaScriptSemanticGraphFingerprintProjection.js +1 -1
  86. package/dist/application/JavaScriptSemanticGraphFlowProjection.js +0 -13
  87. package/dist/application/JavaScriptSemanticGraphProjection.js +1 -3
  88. package/dist/application/JavaScriptSemanticTraceService.js +1 -10
  89. package/dist/application/JsonFiles.js +79 -0
  90. package/dist/application/LinuxHopper.js +11 -9
  91. package/dist/application/LoopbackReplay.js +26 -23
  92. package/dist/application/LoopbackReplayRecorder.js +29 -1
  93. package/dist/application/ManagedApplicationGraphService.js +1 -2
  94. package/dist/application/ManagedMemberComparisonService.js +11 -41
  95. package/dist/application/ManagedNativeVerificationService.js +1 -2
  96. package/dist/application/ManagedReconstructionService.js +1 -1
  97. package/dist/application/ManagedRuntimeCorrelationService.js +5 -8
  98. package/dist/application/NativeApiInspection.js +2 -7
  99. package/dist/application/NativeUiActionTrace.js +394 -0
  100. package/dist/application/NodeRuntimeCharacterizationService.js +3 -7
  101. package/dist/application/PermissionAuthority.js +15 -7
  102. package/dist/application/PermissionFailure.js +5 -0
  103. package/dist/application/ProcessCaptureAuthority.js +5 -7
  104. package/dist/application/ProcessCaptureJournal.js +3 -2
  105. package/dist/application/ProcessCaptureLifecycle.js +5 -12
  106. package/dist/application/ProcessCheckpoints.js +29 -11
  107. package/dist/application/ProcessCli.js +6 -16
  108. package/dist/application/ProcessEvidence.js +5 -7
  109. package/dist/application/ProcessHarness.js +52 -17
  110. package/dist/application/ProcessReactiveCoordinator.js +2 -4
  111. package/dist/application/ProcessReactiveEffects.js +9 -3
  112. package/dist/application/ProcessReactiveHarness.js +13 -7
  113. package/dist/application/ProcessSampling.js +2 -4
  114. package/dist/application/ProcessTimer.js +39 -0
  115. package/dist/application/ProjectPermissionStore.js +39 -96
  116. package/dist/application/RealHopperAssertions.js +20 -66
  117. package/dist/application/ReconstructionCoverageService.js +12 -58
  118. package/dist/application/ReconstructionObligationAuthority.js +2 -2
  119. package/dist/application/ReconstructionObligationCandidates.js +4 -4
  120. package/dist/application/ReconstructionObligationLedgerEvaluation.js +8 -15
  121. package/dist/application/ReconstructionObligationLedgerProjection.js +3 -7
  122. package/dist/application/ReconstructionObligationLedgerService.js +19 -38
  123. package/dist/application/ReferenceSourceImport.js +19 -45
  124. package/dist/application/ReferenceSourceImportEntries.js +2 -4
  125. package/dist/application/ReferenceSourceImportPolicy.js +5 -32
  126. package/dist/application/ReferenceSourceImportTypes.js +0 -16
  127. package/dist/application/RuntimeExecutableDiagnostics.js +55 -29
  128. package/dist/application/SessionProviderRouter.js +2 -2
  129. package/dist/application/Setup.js +1 -3
  130. package/dist/application/SetupClientConfiguration.js +22 -93
  131. package/dist/application/SetupRegistrationEnvironment.js +0 -7
  132. package/dist/application/StoreFileLock.js +119 -0
  133. package/dist/application/TerminalRenderer.js +2 -3
  134. package/dist/application/Uninstall.js +9 -14
  135. package/dist/application/UnknownEvidence.js +1 -1
  136. package/dist/artifacts/ArtifactPaths.js +2 -5
  137. package/dist/artifacts/ArtifactProvider.js +36 -58
  138. package/dist/artifacts/DirectoryArtifactReader.js +13 -8
  139. package/dist/artifacts/MachOSliceArtifactReader.js +1 -5
  140. package/dist/artifacts/NativeDmgArtifactReader.js +11 -11
  141. package/dist/artifacts/NibArchive.js +269 -0
  142. package/dist/artifacts/SafeOutputTree.js +15 -24
  143. package/dist/browser/BrowserProcessStartup.js +1 -2
  144. package/dist/browser/CdpAuthorizedMainFrame.js +16 -0
  145. package/dist/browser/CdpBrowserProvider.js +3 -13
  146. package/dist/browser/CdpCanonicalJsonDigest.js +9 -0
  147. package/dist/browser/CdpCaptureDocuments.js +28 -76
  148. package/dist/browser/CdpCaptureEventBodyShapes.js +13 -33
  149. package/dist/browser/CdpCaptureEventHandlers.js +22 -87
  150. package/dist/browser/CdpCaptureEvents.js +2 -8
  151. package/dist/browser/CdpCaptureStorage.js +9 -20
  152. package/dist/browser/CdpCaptureStorageFingerprints.js +85 -98
  153. package/dist/browser/CdpCaptureValues.js +21 -21
  154. package/dist/browser/CdpConnection.js +10 -25
  155. package/dist/browser/CdpElectronInspection.js +26 -50
  156. package/dist/browser/CdpElectronProvider.js +10 -35
  157. package/dist/browser/CdpElectronScriptEvents.js +8 -16
  158. package/dist/browser/CdpElectronScripts.js +6 -35
  159. package/dist/browser/CdpElectronWorkers.js +6 -10
  160. package/dist/browser/CdpEndpoint.js +25 -41
  161. package/dist/browser/CdpObservationSession.js +14 -26
  162. package/dist/browser/CdpPageCapture.js +34 -48
  163. package/dist/browser/CdpPageCaptureScripts.js +1 -19
  164. package/dist/browser/CdpPageCaptureWorkers.js +6 -10
  165. package/dist/browser/CdpSafeMetadata.js +19 -22
  166. package/dist/browser/CdpScreenshot.js +3 -5
  167. package/dist/browser/CdpTargetSession.js +1 -2
  168. package/dist/browser/CdpWebMcpDiscovery.js +13 -22
  169. package/dist/browser/ElectronFileScope.js +8 -19
  170. package/dist/browser/JavaScriptRuntimeScope.js +9 -14
  171. package/dist/browser/PlaywrightBrowserScenarioProvider.js +31 -64
  172. package/dist/browser/PlaywrightElectronActiveActions.js +54 -69
  173. package/dist/browser/PlaywrightElectronActiveProvider.js +14 -34
  174. package/dist/browser/PlaywrightExecutionBoundary.js +16 -5
  175. package/dist/browser/PlaywrightScenarioActions.js +3 -7
  176. package/dist/browser/PlaywrightScenarioArtifacts.js +23 -92
  177. package/dist/browser/PlaywrightScenarioBrowser.js +3 -5
  178. package/dist/browser/PlaywrightScenarioEvents.js +45 -94
  179. package/dist/browser/PlaywrightScenarioSession.js +14 -30
  180. package/dist/browser/PngVisualDiff.js +22 -14
  181. package/dist/browser/SensitiveTextCapture.js +0 -17
  182. package/dist/browser/V8InspectorCaptureProjection.js +7 -8
  183. package/dist/browser/V8InspectorEndpoint.js +12 -14
  184. package/dist/browser/V8InspectorProvider.js +20 -61
  185. package/dist/browser/WebSourceMapFetcher.js +56 -176
  186. package/dist/catalogIdentity.js +0 -54
  187. package/dist/cli/artifactCommands.js +49 -77
  188. package/dist/cli/coreAnalysisCommands.js +83 -133
  189. package/dist/cli/javascriptApplicationAnalysis.js +1 -9
  190. package/dist/cli/managedCommands.js +5 -270
  191. package/dist/cli/setupCommands.js +3 -12
  192. package/dist/cli/utilityCommands.js +7 -41
  193. package/dist/cli.js +0 -2
  194. package/dist/cliApplicationCommands.js +42 -112
  195. package/dist/cliBrowserAdvancedCommands.js +10 -106
  196. package/dist/cliBrowserCommands.js +15 -127
  197. package/dist/cliBrowserContext.js +30 -0
  198. package/dist/cliCommandNames.js +4 -5
  199. package/dist/cliElectronCommands.js +8 -72
  200. package/dist/cliEvidenceCommands.js +8 -77
  201. package/dist/cliJavaScriptRuntimeCommands.js +9 -91
  202. package/dist/cliJsonInput.js +4 -14
  203. package/dist/cliObservationOptions.js +11 -96
  204. package/dist/cliPolicyCommands.js +2 -17
  205. package/dist/cliProcessCommands.js +3 -3
  206. package/dist/cliSetup.js +0 -13
  207. package/dist/config/browserScenario.js +2 -2
  208. package/dist/config/environment.js +0 -9
  209. package/dist/config/parseConfig.js +1 -33
  210. package/dist/config/parsers.js +6 -27
  211. package/dist/config/passiveObservation.js +7 -77
  212. package/dist/config/permissions.js +0 -7
  213. package/dist/contracts/analysisSearchContract.js +2 -5
  214. package/dist/contracts/applicationToolContracts.js +56 -109
  215. package/dist/contracts/applicationWorkflowInputContracts.js +27 -102
  216. package/dist/contracts/artifactComparisonExample.js +58 -60
  217. package/dist/contracts/artifactToolContracts.js +20 -67
  218. package/dist/contracts/browserScenarioToolContracts.js +6 -51
  219. package/dist/contracts/browserToolContracts.js +8 -73
  220. package/dist/contracts/electronToolContracts.js +8 -108
  221. package/dist/contracts/enhancedInputs.js +23 -54
  222. package/dist/contracts/errorSchemas.js +0 -1
  223. package/dist/contracts/functionComparisonExample.js +10 -24
  224. package/dist/contracts/functionInstructionContract.js +1 -3
  225. package/dist/contracts/functionWorkflowToolContracts.js +2 -2
  226. package/dist/contracts/hopperMemoryContracts.js +3 -3
  227. package/dist/contracts/investigationExamples.js +14 -11
  228. package/dist/contracts/javascriptApplicationWorkflowExamples.js +12 -19
  229. package/dist/contracts/javascriptRuntimeObservationToolContracts.js +2 -20
  230. package/dist/contracts/javascriptRuntimeReconciliationExample.js +45 -12
  231. package/dist/contracts/managedToolContracts.js +6 -95
  232. package/dist/contracts/managedWorkflowExamples.js +77 -128
  233. package/dist/contracts/managedWorkflowToolContracts.js +51 -77
  234. package/dist/contracts/nativeToolContracts.js +7 -11
  235. package/dist/contracts/processCaptureExample.js +2 -69
  236. package/dist/contracts/promptContracts.js +49 -67
  237. package/dist/contracts/providerSelection.js +0 -1
  238. package/dist/contracts/reconstructionReadinessExample.js +1 -6
  239. package/dist/contracts/sessionStatusContract.js +1 -17
  240. package/dist/contracts/sessionToolSchemas.js +6 -15
  241. package/dist/contracts/toolContractExamples.js +11 -11
  242. package/dist/contracts/toolContractHelpers.js +0 -4
  243. package/dist/contracts/toolContracts.js +60 -71
  244. package/dist/contracts/toolEffects.js +5 -13
  245. package/dist/contracts/toolOutputSchemaGroups.js +37 -91
  246. package/dist/contracts/toolOutputSchemaPrimitives.js +16 -56
  247. package/dist/contracts/toolSchemaMetadata.js +8 -19
  248. package/dist/contracts/unknownContractExamples.js +0 -2
  249. package/dist/domain/analysisErrorPresentation.js +46 -54
  250. package/dist/domain/analysisErrorProjection.js +28 -37
  251. package/dist/domain/analysisProfile.js +3 -31
  252. package/dist/domain/analysisSnapshot.js +3 -12
  253. package/dist/domain/androidApplication.js +15 -54
  254. package/dist/domain/appleApplication.js +24 -67
  255. package/dist/domain/artifactComparison.js +11 -29
  256. package/dist/domain/artifactGraph.js +14 -44
  257. package/dist/domain/artifactInspection.js +38 -69
  258. package/dist/domain/artifactInventoryEvidence.js +27 -21
  259. package/dist/domain/binaryTarget.js +2 -221
  260. package/dist/domain/browserCaptureComparison.js +47 -24
  261. package/dist/domain/browserObservation.js +28 -193
  262. package/dist/domain/browserObservationSchemas.js +5 -23
  263. package/dist/domain/browserScenario.js +11 -14
  264. package/dist/domain/browserScenarioCapture.js +11 -12
  265. package/dist/domain/browserScenarioCaptureValues.js +146 -84
  266. package/dist/domain/browserScenarioDiff.js +2 -59
  267. package/dist/domain/browserScenarioDiffEvidence.js +51 -0
  268. package/dist/domain/browserScenarioDiffValues.js +21 -38
  269. package/dist/domain/browserScenarioValues.js +80 -109
  270. package/dist/domain/browserSession.js +4 -7
  271. package/dist/domain/bundleComparison.js +6 -32
  272. package/dist/domain/bytecodeProvider.js +20 -19
  273. package/dist/domain/callPath.js +47 -167
  274. package/dist/domain/callPathSchemas.js +79 -0
  275. package/dist/domain/changedBehavior.js +34 -60
  276. package/dist/domain/completionLedgerGeneration.js +8 -14
  277. package/dist/domain/conformancePackage.js +126 -97
  278. package/dist/domain/conformanceReplay.js +36 -8
  279. package/dist/domain/conformanceTrustGate.js +23 -14
  280. package/dist/domain/customProtocolCapture.js +1 -1
  281. package/dist/domain/electronActiveObservation.js +70 -103
  282. package/dist/domain/electronObservation.js +14 -80
  283. package/dist/domain/electronStaticAnalysisBrowser.js +36 -46
  284. package/dist/domain/electronStaticAnalysisIpc.js +38 -14
  285. package/dist/domain/electronStaticAnalysisNative.js +1 -8
  286. package/dist/domain/electronStaticAnalysisValues.js +10 -17
  287. package/dist/domain/errors.js +33 -54
  288. package/dist/domain/eventProcessTree.js +20 -12
  289. package/dist/domain/evidence.js +2 -10
  290. package/dist/domain/evidenceBundle.js +3 -5
  291. package/dist/domain/evidenceCompletionLedger.js +6 -8
  292. package/dist/domain/functionComparison.js +4 -14
  293. package/dist/domain/functionComparisonNormalization.js +4 -11
  294. package/dist/domain/functionComparisonResults.js +25 -15
  295. package/dist/domain/functionComparisonSchemas.js +49 -33
  296. package/dist/domain/functionDossierEvidence.js +31 -156
  297. package/dist/domain/hopperStartupFailure.js +1 -1
  298. package/dist/domain/hopperValues.js +15 -136
  299. package/dist/domain/interfaceBuilderGraph.js +654 -0
  300. package/dist/domain/javascriptApplicationAnalysis.js +8 -112
  301. package/dist/domain/javascriptApplicationChangeGraph.js +22 -82
  302. package/dist/domain/javascriptApplicationEvidenceSchemas.js +59 -47
  303. package/dist/domain/javascriptApplicationGraph.js +3 -13
  304. package/dist/domain/javascriptApplicationGraphSchemas.js +12 -34
  305. package/dist/domain/javascriptApplicationVersionComparison.js +26 -30
  306. package/dist/domain/javascriptApplicationVersionComparisonSchemas.js +123 -68
  307. package/dist/domain/javascriptApplicationVersionItems.js +120 -53
  308. package/dist/domain/javascriptApplicationVersionKeys.js +24 -11
  309. package/dist/domain/javascriptAstFingerprint.js +25 -58
  310. package/dist/domain/javascriptAstValues.js +9 -0
  311. package/dist/domain/javascriptExportInstrumentation.js +2 -4
  312. package/dist/domain/javascriptExportShapeComparison.js +6 -7
  313. package/dist/domain/javascriptExportShapeComparisonSchemas.js +68 -75
  314. package/dist/domain/javascriptExportShapeSelection.js +17 -19
  315. package/dist/domain/javascriptFeatureSeed.js +20 -12
  316. package/dist/domain/javascriptFeatureTrace.js +13 -67
  317. package/dist/domain/javascriptFeatureTraceSchemas.js +24 -52
  318. package/dist/domain/javascriptFeatureTraversal.js +1 -44
  319. package/dist/domain/javascriptNativeHandoff.js +1 -1
  320. package/dist/domain/javascriptReplay.js +24 -41
  321. package/dist/domain/javascriptRuntimeLoadState.js +3 -8
  322. package/dist/domain/javascriptRuntimeObservation.js +42 -103
  323. package/dist/domain/javascriptRuntimeReconciliation.js +1 -3
  324. package/dist/domain/javascriptRuntimeReconciliationActive.js +8 -17
  325. package/dist/domain/javascriptRuntimeReconciliationGraph.js +10 -23
  326. package/dist/domain/javascriptRuntimeReconciliationMatching.js +18 -12
  327. package/dist/domain/javascriptRuntimeReconciliationParsing.js +16 -45
  328. package/dist/domain/javascriptRuntimeReconciliationResult.js +4 -5
  329. package/dist/domain/javascriptRuntimeReconciliationRuntime.js +8 -12
  330. package/dist/domain/javascriptRuntimeReconciliationSchemas.js +33 -48
  331. package/dist/domain/javascriptRuntimeStaticCandidates.js +3 -3
  332. package/dist/domain/javascriptSemanticAnalysis.js +12 -45
  333. package/dist/domain/javascriptSemanticAsyncEffects.js +1 -9
  334. package/dist/domain/javascriptSemanticCallResolution.js +6 -16
  335. package/dist/domain/javascriptSemanticCalls.js +5 -32
  336. package/dist/domain/javascriptSemanticChildProcesses.js +1 -11
  337. package/dist/domain/javascriptSemanticCoverage.js +11 -0
  338. package/dist/domain/javascriptSemanticDataEffectHelpers.js +1 -8
  339. package/dist/domain/javascriptSemanticDataEffects.js +1 -11
  340. package/dist/domain/javascriptSemanticFingerprints.js +3 -5
  341. package/dist/domain/javascriptSemanticGraphSchemas.js +25 -51
  342. package/dist/domain/javascriptSemanticGraphSerialization.js +2 -13
  343. package/dist/domain/javascriptSemanticIr.js +1 -29
  344. package/dist/domain/javascriptSemanticObjects.js +1 -5
  345. package/dist/domain/javascriptSemanticPrimitives.js +2 -9
  346. package/dist/domain/javascriptSemanticProjection.js +0 -18
  347. package/dist/domain/javascriptSemanticPromises.js +9 -16
  348. package/dist/domain/javascriptSemanticProvenance.js +16 -19
  349. package/dist/domain/javascriptSemanticQuery.js +17 -139
  350. package/dist/domain/javascriptSemanticQueryAssessment.js +9 -37
  351. package/dist/domain/javascriptSemanticQueryIdentity.js +2 -23
  352. package/dist/domain/javascriptSemanticQuerySchemas.js +14 -107
  353. package/dist/domain/javascriptSemanticResources.js +1 -6
  354. package/dist/domain/javascriptSemanticReturns.js +10 -27
  355. package/dist/domain/javascriptSemanticValueTypes.js +1 -0
  356. package/dist/domain/javascriptSemanticValues.js +44 -62
  357. package/dist/domain/javascriptStaticAnalysis.js +21 -43
  358. package/dist/domain/javascriptStaticAnalysisBundler.js +34 -93
  359. package/dist/domain/javascriptStaticAnalysisCalls.js +17 -16
  360. package/dist/domain/javascriptStaticAnalysisFindings.js +3 -19
  361. package/dist/domain/javascriptStaticAnalysisHelpers.js +47 -88
  362. package/dist/domain/javascriptStaticAnalysisState.js +1 -5
  363. package/dist/domain/javascriptStaticAnalysisTypes.js +1 -0
  364. package/dist/domain/jsonShape.js +7 -33
  365. package/dist/domain/localPath.js +7 -0
  366. package/dist/domain/managedApplicationGraph.js +9 -41
  367. package/dist/domain/managedApplicationGraphCoverage.js +24 -156
  368. package/dist/domain/managedApplicationGraphNodes.js +10 -13
  369. package/dist/domain/managedArtifact.js +14 -27
  370. package/dist/domain/managedMemberComparison.js +90 -92
  371. package/dist/domain/managedMemberComparisonCoverage.js +11 -33
  372. package/dist/domain/managedMemberComparisonItems.js +221 -0
  373. package/dist/domain/managedMemberComparisonMatch.js +8 -208
  374. package/dist/domain/managedNativeVerification.js +3 -6
  375. package/dist/domain/managedNativeVerificationMatch.js +55 -59
  376. package/dist/domain/managedNativeVerificationSchemas.js +83 -47
  377. package/dist/domain/managedReconstruction.js +19 -23
  378. package/dist/domain/managedRuntimeCorrelation.js +6 -9
  379. package/dist/domain/nativeApiBoundary.js +2 -7
  380. package/dist/domain/nativeInspection.js +0 -2
  381. package/dist/domain/nativeInvestigationGraph.js +351 -0
  382. package/dist/domain/nativeValueFlow.js +61 -0
  383. package/dist/domain/nodeRuntimeCharacterization.js +1 -5
  384. package/dist/domain/objcSwiftMetadata.js +254 -0
  385. package/dist/domain/peInspection.js +1 -1
  386. package/dist/domain/permissionPolicy.js +13 -0
  387. package/dist/domain/processCapture.js +76 -113
  388. package/dist/domain/processCaptureExample.js +68 -0
  389. package/dist/domain/processCaptureParsing.js +21 -0
  390. package/dist/domain/processCaptureReactiveSchema.js +68 -0
  391. package/dist/domain/processCaptureValidation.js +1 -7
  392. package/dist/domain/processComparison.js +3 -12
  393. package/dist/domain/processReactiveMatching.js +29 -50
  394. package/dist/domain/processReactiveRuntime.js +4 -8
  395. package/dist/domain/processReactiveScenario.js +28 -94
  396. package/dist/domain/processReactiveScenarioPreflight.js +5 -30
  397. package/dist/domain/processReactiveTransition.js +33 -18
  398. package/dist/domain/processScenario.js +56 -90
  399. package/dist/domain/processTraceComparison.js +0 -8
  400. package/dist/domain/processTraceEvaluation.js +14 -4
  401. package/dist/domain/processTraceSpecification.js +11 -17
  402. package/dist/domain/protocolCapture.js +3 -3
  403. package/dist/domain/reconstructionCoverage.js +62 -129
  404. package/dist/domain/reconstructionCoverageEvaluation.js +17 -17
  405. package/dist/domain/reconstructionObligationLedgerSchemas.js +45 -72
  406. package/dist/domain/reconstructionReadiness.js +1 -2
  407. package/dist/domain/reconstructionReadinessSchemas.js +36 -38
  408. package/dist/domain/reconstructionUnknowns.js +3 -3
  409. package/dist/domain/reconstructionVerification.js +13 -47
  410. package/dist/domain/reconstructionVerificationSchemas.js +50 -38
  411. package/dist/domain/referenceSourceGraph.js +53 -61
  412. package/dist/domain/referenceSourceImportParsing.js +1 -5
  413. package/dist/domain/replayMachine.js +19 -31
  414. package/dist/domain/replayMachineRun.js +15 -63
  415. package/dist/domain/replayMachineRuntime.js +41 -41
  416. package/dist/domain/replayMachineValues.js +84 -0
  417. package/dist/domain/residualUnknown.js +25 -30
  418. package/dist/domain/runtimeCharacterization.js +15 -16
  419. package/dist/domain/runtimeIdentification.js +48 -47
  420. package/dist/domain/sourceToBundleComparison.js +9 -85
  421. package/dist/domain/sourceToBundleComparisonSchemas.js +17 -42
  422. package/dist/domain/sourceToBundleSignals.js +4 -15
  423. package/dist/domain/staticRuntimeCorrelation.js +37 -49
  424. package/dist/domain/symbolAnalysis.js +10 -19
  425. package/dist/domain/unicodeCodePointOrder.js +12 -0
  426. package/dist/domain/webBundleAnalysis.js +70 -99
  427. package/dist/domain/webBundleAnalyzer.js +2 -4
  428. package/dist/domain/webBundleAnalyzerAst.js +0 -13
  429. package/dist/domain/webBundleAnalyzerInspection.js +26 -52
  430. package/dist/domain/webBundleAnalyzerResult.js +7 -24
  431. package/dist/domain/webCaptureDiff.js +33 -73
  432. package/dist/domain/webCaptureDiffSchemas.js +86 -0
  433. package/dist/domain/webContentArtifact.js +1 -10
  434. package/dist/domain/webMcpDiscovery.js +3 -14
  435. package/dist/domain/webScreenshot.js +58 -34
  436. package/dist/domain/zipPackageFormat.js +7 -0
  437. package/dist/dotnet/ManagedArtifactInspector.js +17 -50
  438. package/dist/dotnet/ManagedMemberInspector.js +27 -58
  439. package/dist/dotnet/ManagedMemberInspectorCore.js +14 -51
  440. package/dist/dotnet/ManagedMemberInstructionDecoder.js +2 -2
  441. package/dist/dotnet/ManagedMemberRows.js +16 -16
  442. package/dist/dotnet/ManagedMetadataHeaps.js +26 -8
  443. package/dist/dotnet/ManagedMetadataInventory.js +21 -30
  444. package/dist/dotnet/ManagedMetadataInventoryRows.js +57 -63
  445. package/dist/dotnet/ManagedMetadataLayout.js +3 -5
  446. package/dist/dotnet/ManagedMethodBodyReader.js +5 -30
  447. package/dist/dotnet/ManagedNativeBoundaryHelpers.js +19 -49
  448. package/dist/dotnet/ManagedNativeBoundaryInspector.js +13 -41
  449. package/dist/dotnet/ManagedPeReader.js +2 -2
  450. package/dist/dotnet/ManagedStaticProvider.js +13 -71
  451. package/dist/generatedMcpToolCatalog.js +1 -1
  452. package/dist/generatedPackageMetadata.js +1 -1
  453. package/dist/ghidra/GhidraAnalysisProfile.js +3 -8
  454. package/dist/ghidra/GhidraClient.js +4 -12
  455. package/dist/ghidra/GhidraClientWire.js +9 -5
  456. package/dist/ghidra/GhidraDefaults.js +0 -18
  457. package/dist/ghidra/GhidraDiagnostics.js +0 -2
  458. package/dist/ghidra/GhidraDoctor.js +16 -12
  459. package/dist/ghidra/GhidraFunctionValues.js +65 -108
  460. package/dist/ghidra/GhidraInstallation.js +98 -42
  461. package/dist/ghidra/GhidraInventoryValues.js +16 -85
  462. package/dist/ghidra/GhidraLauncher.js +0 -8
  463. package/dist/ghidra/GhidraProvider.js +15 -22
  464. package/dist/ghidra/GhidraProviderCapabilities.js +6 -47
  465. package/dist/ghidra/GhidraRequestQueue.js +6 -30
  466. package/dist/ghidra/GhidraResponseBuffer.js +1 -7
  467. package/dist/ghidra/GhidraSessionValues.js +0 -2
  468. package/dist/ghidra/GhidraTransport.js +4 -7
  469. package/dist/ghidra/protocol.js +2 -2
  470. package/dist/hopper/BridgeLauncher.js +112 -36
  471. package/dist/hopper/HopperAnalysisProfile.js +1 -1
  472. package/dist/hopper/HopperCleanup.js +6 -3
  473. package/dist/hopper/HopperClient.js +4 -7
  474. package/dist/hopper/HopperProcessDiagnostic.js +2 -2
  475. package/dist/hopper/HopperProvider.js +0 -16
  476. package/dist/hopper/HopperRequestQueue.js +21 -40
  477. package/dist/hopper/HopperResponseStream.js +1 -9
  478. package/dist/hopper/HopperSessionValues.js +3 -2
  479. package/dist/hopper/HopperTargetLease.js +119 -0
  480. package/dist/hopper/LinuxPrivateDisplayDiagnostic.js +24 -31
  481. package/dist/hopper/LinuxPrivateDisplayProbe.js +5 -16
  482. package/dist/hopper/protocol.js +6 -4
  483. package/dist/main/reload.js +0 -3
  484. package/dist/main/shutdown.js +1 -1
  485. package/dist/main/state.js +0 -9
  486. package/dist/main/transport.js +0 -5
  487. package/dist/mcpDoctor.js +25 -45
  488. package/dist/native/CommandRunner.js +18 -25
  489. package/dist/native/NativeMacOSProvider.js +13 -38
  490. package/dist/native/NativeMachoInspection.js +1 -1
  491. package/dist/process/BoundedFileBytes.js +17 -0
  492. package/dist/process/ExecFileOutput.js +11 -0
  493. package/dist/process/PendingOperations.js +8 -6
  494. package/dist/process/ProcessOwnership.js +3 -2
  495. package/dist/process/ProcessOwnershipObservation.js +0 -1
  496. package/dist/process/ProviderProcess.js +7 -28
  497. package/dist/reference/ReferenceSourceReader.js +8 -24
  498. package/dist/reference/ReferenceSourceReaderEntries.js +11 -39
  499. package/dist/reference/ReferenceSourceReaderFile.js +13 -31
  500. package/dist/reference/ReferenceSourceReaderPaths.js +3 -18
  501. package/dist/reference/ReferenceSourceReaderValidate.js +3 -6
  502. package/dist/replay/JavaScriptReplayWorker.js +3 -5
  503. package/dist/replay/LinuxJavaScriptReplayRunner.js +33 -29
  504. package/dist/replay/LinuxJavaScriptReplaySandbox.js +0 -19
  505. package/dist/replay/LinuxRuntimeClosure.js +1 -3
  506. package/dist/replay/ReplayOutcome.js +1 -3
  507. package/dist/replay/ReplayProcessLifecycle.js +32 -7
  508. package/dist/replay/ReplaySeccompFile.js +29 -0
  509. package/dist/replay/ReplayWorkerProtocol.js +0 -2
  510. package/dist/replay/SystemJavaScriptReplayHost.js +45 -74
  511. package/dist/server/ProcessCaptureElicitation.js +4 -8
  512. package/dist/server/createServer.js +6 -44
  513. package/dist/server/promptCompletion.js +37 -61
  514. package/dist/server/recordDerivedEvidence.js +1 -1
  515. package/dist/server/registerApplicationTools/characterization.js +4 -8
  516. package/dist/server/registerApplicationTools/compareExportShapes.js +5 -10
  517. package/dist/server/registerApplicationTools/compareSourceToBundle.js +6 -10
  518. package/dist/server/registerApplicationTools/compareVersions.js +5 -10
  519. package/dist/server/registerApplicationTools/controlledReplay.js +3 -5
  520. package/dist/server/registerApplicationTools/coverage.js +6 -71
  521. package/dist/server/registerApplicationTools/helpers.js +1 -13
  522. package/dist/server/registerApplicationTools/obligationLedger.js +3 -2
  523. package/dist/server/registerApplicationTools/traceFeature.js +5 -9
  524. package/dist/server/registerApplicationTools/traceSemantics.js +5 -8
  525. package/dist/server/registerApplicationTools.js +0 -2
  526. package/dist/server/registerArtifactComparisonTool.js +23 -27
  527. package/dist/server/registerBrowserScenarioTool.js +4 -4
  528. package/dist/server/registerBrowserTools.js +5 -8
  529. package/dist/server/registerBundleComparisonTool.js +5 -7
  530. package/dist/server/registerCloseLifecycleTool.js +2 -16
  531. package/dist/server/registerElectronTools.js +10 -29
  532. package/dist/server/registerEnhancedTools.js +10 -11
  533. package/dist/server/registerEvidenceTools.js +15 -22
  534. package/dist/server/registerFunctionComparisonTool.js +30 -25
  535. package/dist/server/registerInvestigationTools/helpers.js +3 -32
  536. package/dist/server/registerInvestigationTools.js +20 -116
  537. package/dist/server/registerJavaScriptRuntimeObservationTools.js +6 -4
  538. package/dist/server/registerManagedWorkflowTools/compareManagedMembers.js +15 -17
  539. package/dist/server/registerManagedWorkflowTools/evidence.js +19 -35
  540. package/dist/server/registerManagedWorkflowTools/importManagedReconstruction.js +3 -8
  541. package/dist/server/registerManagedWorkflowTools/planManagedRuntimeCorrelation.js +3 -8
  542. package/dist/server/registerManagedWorkflowTools/projectManagedApplicationGraph.js +10 -14
  543. package/dist/server/registerManagedWorkflowTools/verifyManagedNativeBoundaries.js +9 -15
  544. package/dist/server/registerOfficialTools.js +1 -43
  545. package/dist/server/registerProcessComparisonTool.js +33 -23
  546. package/dist/server/registerPrompts.js +19 -17
  547. package/dist/server/registerSessionRecordTools.js +10 -156
  548. package/dist/server/registerSessionStatusTool.js +6 -69
  549. package/dist/server/registerSessionTools.js +10 -51
  550. package/dist/server/sessionAvailabilityPolicy.js +0 -2
  551. package/dist/server/sessionEvidence.js +9 -15
  552. package/dist/server/sessionToolPolicies.js +0 -8
  553. package/dist/server/toolResult.js +14 -50
  554. package/dist/serverIdentity.js +0 -20
  555. package/install.sh +1 -1
  556. package/package.json +11 -7
  557. package/scripts/electron-active-hook.cjs +23 -55
  558. package/scripts/hopper-demo-x11.py +13 -32
  559. package/skills/reverse-engineer-anything/SKILL.md +47 -12
  560. package/skills/reverse-engineer-anything/references/evidence-workflows.md +6 -4
  561. package/skills/reverse-engineer-anything/references/javascript-applications.md +13 -13
  562. package/skills/reverse-engineer-anything/references/native-and-artifacts.md +12 -8
  563. package/dist/application/ApplicationWorkflowEvidenceResolver.js +0 -188
  564. package/dist/application/BoundedJsonFiles.js +0 -86
  565. package/dist/application/CrossVersionInventory.js +0 -129
  566. package/dist/application/CrossVersionInvestigation.js +0 -301
  567. package/dist/application/CrossVersionInvestigationCollections.js +0 -23
  568. package/dist/application/CrossVersionInvestigationEvidence.js +0 -64
  569. package/dist/application/CrossVersionInvestigationReplay.js +0 -170
  570. package/dist/application/DeferredFileAuthorization.js +0 -61
  571. package/dist/application/DoctorProjection.js +0 -43
  572. package/dist/application/EnhancedToolPagination.js +0 -22
  573. package/dist/application/EvidenceReferenceResolver.js +0 -26
  574. package/dist/application/InvestigationWorkspaceStore.js +0 -204
  575. package/dist/application/ProcessPairedExperiment.js +0 -72
  576. package/dist/application/ReconstructionCoverageWorkspaceStore.js +0 -31
  577. package/dist/application/ReconstructionReadinessService.js +0 -48
  578. package/dist/cliInvestigationCommands.js +0 -173
  579. package/dist/domain/boundedCartesianProjection.js +0 -15
  580. package/dist/domain/investigationWorkspace.js +0 -347
  581. package/dist/domain/jsonLimits.js +0 -26
  582. package/dist/domain/knownPageCoverage.js +0 -29
  583. package/dist/domain/processPairedExperiment.js +0 -128
  584. package/dist/server/javascriptApplicationResult.js +0 -106
  585. package/dist/server/permissionFailure.js +0 -12
  586. package/dist/server/registerApplicationTools/readiness.js +0 -50
  587. package/dist/server/registerEvidenceResources.js +0 -315
  588. package/dist/server/registerJavaScriptApplicationGraphResource.js +0 -65
  589. package/dist/server/registerReconstructionObligationLedgerResource.js +0 -32
  590. package/dist/server/registerReconstructionReadinessResource.js +0 -32
@@ -5,7 +5,6 @@ import java.io.BufferedReader;
5
5
  import java.io.BufferedWriter;
6
6
  import java.io.ByteArrayOutputStream;
7
7
  import java.io.IOException;
8
- import java.io.Reader;
9
8
  import java.math.BigInteger;
10
9
  import java.net.InetAddress;
11
10
  import java.net.InetSocketAddress;
@@ -26,8 +25,11 @@ import java.security.MessageDigest;
26
25
  import java.util.ArrayList;
27
26
  import java.util.Comparator;
28
27
  import java.util.HashSet;
28
+ import java.util.IdentityHashMap;
29
+ import java.util.Iterator;
29
30
  import java.util.List;
30
31
  import java.util.Locale;
32
+ import java.util.Map;
31
33
  import java.util.Set;
32
34
  import java.util.TreeMap;
33
35
  import java.util.regex.Pattern;
@@ -75,32 +77,17 @@ import ghidra.program.model.pcode.FunctionPrototype;
75
77
  import ghidra.program.model.pcode.HighFunction;
76
78
  import ghidra.program.model.pcode.HighSymbol;
77
79
  import ghidra.program.model.pcode.JumpTable;
80
+ import ghidra.program.model.pcode.PcodeOp;
81
+ import ghidra.program.model.pcode.PcodeOpAST;
82
+ import ghidra.program.model.pcode.Varnode;
83
+ import ghidra.program.model.pcode.VarnodeAST;
78
84
 
79
85
  public final class ReaGhidraBridge extends HeadlessScript {
80
- private static final int BRIDGE_VERSION = 6;
81
- private static final int MAX_DESCRIPTOR_BYTES = 16 * 1024;
82
- private static final int MAX_REQUEST_CHARACTERS = 256 * 1024;
83
- private static final int MAX_RESPONSE_BYTES = 1024 * 1024;
84
- private static final int RESPONSE_ENVELOPE_RESERVE_BYTES = 32 * 1024;
85
- private static final int MAX_NATIVE_API_RESPONSE_BYTES = 192 * 1024;
86
- private static final int MAX_INVENTORY_ITEMS = 1_000_000;
87
- private static final int MAX_FUNCTION_ITEMS = 100_000;
88
- private static final int MAX_FUNCTION_INSTRUCTIONS = 100_000;
89
- private static final int MAX_NATIVE_API_PARAMETERS = 64;
90
- private static final int MAX_NATIVE_API_JUMP_TABLES = 4;
91
- private static final int MAX_NATIVE_API_LOAD_TABLES = 4;
92
- private static final int MAX_NATIVE_API_MAPPINGS = 32;
93
- private static final int DECOMPILE_TIMEOUT_SECONDS = 30;
94
- private static final int DECOMPILE_PAYLOAD_MBYTES = 8;
95
- private static final int MAX_LIST_VALUE_CODE_POINTS = 1_024;
96
- private static final int MAX_SEARCH_VALUE_CODE_POINTS = 4_096;
97
- private static final int MAX_REGEX_CANDIDATE_CHARACTERS = 4_096;
98
- private static final long MAX_REGEX_BACKTRACKING_PATHS = 10_000;
99
- private static final long MAX_LITERAL_SEARCH_WORK_UNITS = 1_000_000;
100
- private static final long MAX_REGEX_SEARCH_WORK_UNITS = 1_000_000;
86
+ private static final int MAX_HIGH_PCODE_OPS = 3000;
87
+ private static final int MAX_HIGH_PCODE_INPUTS_PER_OP = 64;
88
+ private static final int MAX_HIGH_PCODE_DEF_USE_EDGES = 12000;
101
89
  private static final Gson GSON = new GsonBuilder().serializeNulls().create();
102
90
  private static final Set<String> DESCRIPTOR_KEYS = Set.of(
103
- "schema_version",
104
91
  "transport",
105
92
  "endpoint_path",
106
93
  "token",
@@ -218,7 +205,6 @@ public final class ReaGhidraBridge extends HeadlessScript {
218
205
  server.bind(new InetSocketAddress(InetAddress.getByName("127.0.0.1"), 0));
219
206
  InetSocketAddress address = (InetSocketAddress) server.getLocalAddress();
220
207
  JsonObject endpoint = new JsonObject();
221
- endpoint.addProperty("schema_version", 1);
222
208
  endpoint.addProperty("host", "127.0.0.1");
223
209
  endpoint.addProperty("port", address.getPort());
224
210
  Files.writeString(
@@ -261,7 +247,7 @@ public final class ReaGhidraBridge extends HeadlessScript {
261
247
  BufferedReader reader,
262
248
  BufferedWriter writer) throws Exception {
263
249
  while (true) {
264
- String line = readBoundedLine(reader, MAX_REQUEST_CHARACTERS);
250
+ String line = reader.readLine();
265
251
  if (line == null) {
266
252
  return;
267
253
  }
@@ -359,7 +345,6 @@ public final class ReaGhidraBridge extends HeadlessScript {
359
345
 
360
346
  JsonObject result = new JsonObject();
361
347
  result.addProperty("name", "REA Ghidra bridge");
362
- result.addProperty("bridge_version", BRIDGE_VERSION);
363
348
  result.addProperty("run_id", descriptor.runId);
364
349
  result.addProperty("profile_digest", descriptor.profileDigest);
365
350
  result.add("provider", provider);
@@ -373,9 +358,7 @@ public final class ReaGhidraBridge extends HeadlessScript {
373
358
 
374
359
  private void initializeDecompiler() {
375
360
  DecompileOptions options = new DecompileOptions();
376
- options.setDefaultTimeout(DECOMPILE_TIMEOUT_SECONDS);
377
- options.setMaxPayloadMBytes(DECOMPILE_PAYLOAD_MBYTES);
378
- options.setMaxInstructions(MAX_FUNCTION_INSTRUCTIONS);
361
+ options.setMaxInstructions(Integer.MAX_VALUE);
379
362
  decompiler = new DecompInterface();
380
363
  decompiler.setOptions(options);
381
364
  decompiler.toggleCCode(true);
@@ -398,43 +381,19 @@ public final class ReaGhidraBridge extends HeadlessScript {
398
381
  requireKeys(params, Set.of("document", "procedure"));
399
382
  requireDocument(params);
400
383
  Function function = resolveProcedure(requireString(params, "procedure"));
401
- InstructionScan scan = scanInstructions(function, MAX_FUNCTION_INSTRUCTIONS);
402
- if (scan.truncated) {
403
- throw functionLimit("Procedure assembly exceeds the 100000-instruction limit");
404
- }
384
+ InstructionScan scan = scanInstructions(function, Integer.MAX_VALUE);
405
385
  return GSON.toJsonTree(renderAssembly(scan.instructions));
406
386
  }
407
387
 
408
388
  private JsonObject readFunctionInstructions(JsonObject params) throws Exception {
409
- requireKeys(params, Set.of("document", "procedure", "offset", "limit"));
389
+ requireKeys(params, Set.of("document", "procedure"));
410
390
  requireDocument(params);
411
391
  Function function = resolveProcedure(requireString(params, "procedure"));
412
- int offset = requireBoundedInteger(params, "offset", 0, 100_000);
413
- int limit = requireBoundedInteger(params, "limit", 1, 500);
414
- InstructionScan scan = scanInstructions(function, offset + limit + 1);
415
- int start = Math.min(offset, scan.instructions.size());
416
- int end = Math.min(scan.instructions.size(), start + limit);
392
+ InstructionScan scan = scanInstructions(function, Integer.MAX_VALUE);
417
393
  JsonArray items = new JsonArray();
418
- for (String line : renderAssemblyLines(scan.instructions.subList(start, end))) {
394
+ for (String line : renderAssemblyLines(scan.instructions)) {
419
395
  items.add(line);
420
396
  }
421
- boolean hasMore = scan.truncated || end < scan.instructions.size();
422
- JsonObject instructions = new JsonObject();
423
- instructions.add("items", items);
424
- if (scan.truncated) {
425
- instructions.add("total", JsonNull.INSTANCE);
426
- }
427
- else {
428
- instructions.addProperty("total", scan.instructions.size());
429
- }
430
- instructions.addProperty("returned", items.size());
431
- instructions.addProperty("truncated", hasMore);
432
- if (hasMore) {
433
- instructions.addProperty("next_offset", end);
434
- }
435
- else {
436
- instructions.add("next_offset", JsonNull.INSTANCE);
437
- }
438
397
 
439
398
  JsonArray limitations = new JsonArray();
440
399
  limitations.add("Instruction text and ordering are Ghidra-specific representations.");
@@ -443,9 +402,7 @@ public final class ReaGhidraBridge extends HeadlessScript {
443
402
  );
444
403
  JsonObject result = new JsonObject();
445
404
  result.add("procedure", procedureIdentity(function));
446
- result.add("instructions", instructions);
447
- result.addProperty("instructions_scanned", scan.instructions.size());
448
- result.addProperty("instruction_scan_truncated", scan.truncated);
405
+ result.add("instructions", items);
449
406
  result.add("limitations", limitations);
450
407
  return result;
451
408
  }
@@ -457,9 +414,6 @@ public final class ReaGhidraBridge extends HeadlessScript {
457
414
  Set<Function> observed = callers
458
415
  ? function.getCallingFunctions(monitor)
459
416
  : function.getCalledFunctions(monitor);
460
- if (observed.size() > MAX_FUNCTION_ITEMS) {
461
- throw functionLimit("Resolved call set exceeds the 100000-item limit");
462
- }
463
417
  List<Function> ordered = new ArrayList<>(observed);
464
418
  ordered.sort(Comparator.comparing(Function::getEntryPoint));
465
419
  JsonArray result = new JsonArray();
@@ -490,10 +444,7 @@ public final class ReaGhidraBridge extends HeadlessScript {
490
444
  Set.of(
491
445
  "document",
492
446
  "procedure",
493
- "direction",
494
- "offset",
495
- "limit",
496
- "max_instructions"
447
+ "direction"
497
448
  )
498
449
  );
499
450
  requireDocument(params);
@@ -502,10 +453,7 @@ public final class ReaGhidraBridge extends HeadlessScript {
502
453
  if (!direction.equals("incoming") && !direction.equals("outgoing")) {
503
454
  throw new RequestFailure("invalid_request", "direction must be incoming or outgoing");
504
455
  }
505
- int offset = requireBoundedInteger(params, "offset", 0, Integer.MAX_VALUE);
506
- int limit = requireBoundedInteger(params, "limit", 1, 500);
507
- int maximum = requireBoundedInteger(params, "max_instructions", 1, 5_000);
508
- InstructionScan scan = scanInstructions(function, maximum);
456
+ InstructionScan scan = scanInstructions(function, Integer.MAX_VALUE);
509
457
  List<Reference> references = collectReferences(scan.instructions, direction);
510
458
  JsonArray edges = new JsonArray();
511
459
  for (Reference reference : references) {
@@ -514,9 +462,7 @@ public final class ReaGhidraBridge extends HeadlessScript {
514
462
  JsonObject result = new JsonObject();
515
463
  result.add("procedure", procedureIdentity(function));
516
464
  result.addProperty("direction", direction);
517
- result.add("references", bounded(edges, offset, limit, !scan.truncated));
518
- result.addProperty("instructions_scanned", scan.instructions.size());
519
- result.addProperty("instruction_scan_truncated", scan.truncated);
465
+ result.add("references", edges);
520
466
  return result;
521
467
  }
522
468
 
@@ -533,10 +479,7 @@ public final class ReaGhidraBridge extends HeadlessScript {
533
479
  continue;
534
480
  }
535
481
  sources.add(reference.getFromAddress());
536
- if (sources.size() > MAX_FUNCTION_ITEMS) {
537
- throw functionLimit("Cross-reference set exceeds the 100000-item limit");
538
482
  }
539
- }
540
483
  List<Address> ordered = new ArrayList<>(sources);
541
484
  ordered.sort(Address::compareTo);
542
485
  JsonArray result = new JsonArray();
@@ -547,48 +490,9 @@ public final class ReaGhidraBridge extends HeadlessScript {
547
490
  }
548
491
 
549
492
  private JsonObject analyzeFunction(JsonObject params) throws Exception {
550
- requireKeys(
551
- params,
552
- Set.of(
553
- "procedure",
554
- "include_assembly",
555
- "limit",
556
- "max_pseudocode_chars",
557
- "max_instructions",
558
- "pseudocode_offset",
559
- "assembly_offset",
560
- "collection_offset"
561
- )
562
- );
493
+ requireKeys(params, Set.of("procedure"));
563
494
  Function function = resolveProcedure(requireString(params, "procedure"));
564
- boolean includeAssembly = requireBoolean(params, "include_assembly");
565
- int limit = requireBoundedInteger(params, "limit", 1, 500);
566
- int maximumCharacters = requireBoundedInteger(
567
- params,
568
- "max_pseudocode_chars",
569
- 1,
570
- 100_000
571
- );
572
- int maximumInstructions = requireBoundedInteger(
573
- params,
574
- "max_instructions",
575
- 1,
576
- 5_000
577
- );
578
- int pseudocodeOffset = requireBoundedInteger(
579
- params,
580
- "pseudocode_offset",
581
- 0,
582
- Integer.MAX_VALUE
583
- );
584
- int assemblyOffset = requireBoundedInteger(
585
- params,
586
- "assembly_offset",
587
- 0,
588
- Integer.MAX_VALUE
589
- );
590
- JsonObject offsets = requireCollectionOffsets(params);
591
- InstructionScan scan = scanInstructions(function, maximumInstructions);
495
+ InstructionScan scan = scanInstructions(function, Integer.MAX_VALUE);
592
496
  DecompileResults decompilation = decompile(function);
593
497
  String pseudocode = decompiledText(decompilation);
594
498
  if (pseudocode == null) {
@@ -607,88 +511,29 @@ public final class ReaGhidraBridge extends HeadlessScript {
607
511
  JsonArray referencedStrings = referencedStrings(outgoingReferences);
608
512
  JsonArray referencedNames = referencedNames(outgoingReferences);
609
513
  JsonArray blocks = basicBlockValues(function);
610
- JsonArray assembly = includeAssembly
611
- ? GSON.toJsonTree(renderAssemblyLines(scan.instructions)).getAsJsonArray()
612
- : new JsonArray();
514
+ JsonArray assembly = GSON.toJsonTree(renderAssemblyLines(scan.instructions))
515
+ .getAsJsonArray();
613
516
 
614
517
  JsonObject procedure = procedureIdentity(function);
615
518
  procedure.addProperty("signature", function.getPrototypeString(false, true));
616
519
  procedure.add("locals", functionLocals(function));
617
520
  JsonObject result = new JsonObject();
618
521
  result.add("procedure", procedure);
619
- result.add(
620
- "pseudocode",
621
- pseudocodePage(pseudocode, pseudocodeOffset, maximumCharacters)
622
- );
623
- result.add(
624
- "assembly",
625
- includeAssembly
626
- ? bounded(assembly, assemblyOffset, maximumInstructions, !scan.truncated)
627
- : bounded(assembly, 0, maximumInstructions, true)
628
- );
629
- result.add(
630
- "comments",
631
- bounded(comments, collectionOffset(offsets, "comments"), limit, !scan.truncated)
632
- );
633
- result.add("callers", bounded(callers, collectionOffset(offsets, "callers"), limit, true));
634
- result.add("callees", bounded(callees, collectionOffset(offsets, "callees"), limit, true));
635
- result.add(
636
- "incoming_references",
637
- bounded(
638
- incoming,
639
- collectionOffset(offsets, "incoming_references"),
640
- limit,
641
- !scan.truncated
642
- )
643
- );
644
- result.add(
645
- "outgoing_references",
646
- bounded(
647
- outgoing,
648
- collectionOffset(offsets, "outgoing_references"),
649
- limit,
650
- !scan.truncated
651
- )
652
- );
653
- result.add(
654
- "referenced_strings",
655
- bounded(
656
- referencedStrings,
657
- collectionOffset(offsets, "referenced_strings"),
658
- limit,
659
- !scan.truncated
660
- )
661
- );
662
- result.add(
663
- "referenced_names",
664
- bounded(
665
- referencedNames,
666
- collectionOffset(offsets, "referenced_names"),
667
- limit,
668
- !scan.truncated
669
- )
670
- );
671
- result.add(
672
- "basic_blocks",
673
- bounded(blocks, collectionOffset(offsets, "basic_blocks"), limit, true)
674
- );
675
- JsonObject instructionScan = new JsonObject();
676
- instructionScan.addProperty("scanned", scan.instructions.size());
677
- instructionScan.addProperty("truncated", scan.truncated);
678
- result.add("instruction_scan", instructionScan);
679
- int nativeApiBudget = Math.min(
680
- MAX_NATIVE_API_RESPONSE_BYTES,
681
- Math.max(
682
- 0,
683
- MAX_RESPONSE_BYTES -
684
- encodedUtf8Length(result) -
685
- RESPONSE_ENVELOPE_RESERVE_BYTES
686
- )
687
- );
522
+ result.addProperty("pseudocode", pseudocode);
523
+ result.add("assembly", assembly);
524
+ result.add("comments", comments);
525
+ result.add("callers", callers);
526
+ result.add("callees", callees);
527
+ result.add("incoming_references", incoming);
528
+ result.add("outgoing_references", outgoing);
529
+ result.add("referenced_strings", referencedStrings);
530
+ result.add("referenced_names", referencedNames);
531
+ result.add("basic_blocks", blocks);
688
532
  result.add(
689
533
  "native_api",
690
- nativeApiBoundary(function, decompilation, nativeApiBudget)
534
+ nativeApiBoundary(function, decompilation)
691
535
  );
536
+ result.add("native_value_flow", nativeValueFlow(decompilation));
692
537
  JsonArray limitations = new JsonArray();
693
538
  limitations.add(
694
539
  "Unresolved computed or indirect flows without target addresses are not represented as reference edges."
@@ -706,6 +551,205 @@ public final class ReaGhidraBridge extends HeadlessScript {
706
551
  return result;
707
552
  }
708
553
 
554
+ private JsonObject nativeValueFlow(DecompileResults decompilation) throws Exception {
555
+ HighFunction highFunction =
556
+ decompilation == null ? null : decompilation.getHighFunction();
557
+ if (highFunction == null) {
558
+ JsonObject unavailable = new JsonObject();
559
+ unavailable.addProperty("available", false);
560
+ unavailable.addProperty(
561
+ "reason",
562
+ "Ghidra has no HighFunction p-code model for this procedure"
563
+ );
564
+ JsonArray limitations = new JsonArray();
565
+ limitations.add(
566
+ "No p-code operations or def-use relationships were recovered for this procedure."
567
+ );
568
+ unavailable.add("limitations", limitations);
569
+ return unavailable;
570
+ }
571
+
572
+ List<PcodeOpAST> selected = new ArrayList<>();
573
+ int omittedOperationsLowerBound = 0;
574
+ Iterator<PcodeOpAST> operations = highFunction.getPcodeOps();
575
+ while (operations.hasNext()) {
576
+ monitor.checkCancelled();
577
+ PcodeOpAST operation = operations.next();
578
+ if (selected.size() >= MAX_HIGH_PCODE_OPS) {
579
+ omittedOperationsLowerBound = 1;
580
+ break;
581
+ }
582
+ selected.add(operation);
583
+ }
584
+
585
+ Map<PcodeOpAST, String> operationIds = new IdentityHashMap<>();
586
+ for (PcodeOpAST operation : selected) {
587
+ operationIds.put(operation, pcodeOperationId(operation));
588
+ }
589
+ JsonArray operationValues = new JsonArray();
590
+ JsonArray defUse = new JsonArray();
591
+ JsonArray effects = new JsonArray();
592
+ int omittedInputs = 0;
593
+ int omittedEdges = 0;
594
+ for (PcodeOpAST operation : selected) {
595
+ monitor.checkCancelled();
596
+ String operationId = operationIds.get(operation);
597
+ JsonObject value = new JsonObject();
598
+ value.addProperty("id", operationId);
599
+ value.addProperty(
600
+ "address",
601
+ canonicalAddress(operation.getSeqnum().getTarget())
602
+ );
603
+ value.addProperty("sequence", operation.getSeqnum().getTime());
604
+ value.addProperty("opcode", operation.getMnemonic());
605
+ value.addProperty("is_dead", operation.isDead());
606
+ JsonArray inputs = new JsonArray();
607
+ int inputCount = operation.getNumInputs();
608
+ int capturedInputs = Math.min(inputCount, MAX_HIGH_PCODE_INPUTS_PER_OP);
609
+ for (int inputIndex = 0; inputIndex < capturedInputs; inputIndex += 1) {
610
+ Varnode input = operation.getInput(inputIndex);
611
+ inputs.add(varnodeValue(input));
612
+ if (input instanceof VarnodeAST astInput) {
613
+ PcodeOp definition = astInput.getDef();
614
+ if (definition instanceof PcodeOpAST astDefinition) {
615
+ String definitionId = operationIds.get(astDefinition);
616
+ if (definitionId == null || defUse.size() >= MAX_HIGH_PCODE_DEF_USE_EDGES) {
617
+ omittedEdges += 1;
618
+ } else {
619
+ JsonObject edge = new JsonObject();
620
+ edge.addProperty("definition", definitionId);
621
+ edge.addProperty("use", operationId);
622
+ edge.addProperty("input_index", inputIndex);
623
+ defUse.add(edge);
624
+ }
625
+ }
626
+ }
627
+ }
628
+ omittedInputs += inputCount - capturedInputs;
629
+ value.add("inputs", inputs);
630
+ Varnode output = operation.getOutput();
631
+ value.add("output", output == null ? JsonNull.INSTANCE : varnodeValue(output));
632
+ operationValues.add(value);
633
+ addPcodeEffect(effects, operation, operationId);
634
+ }
635
+
636
+ JsonObject result = new JsonObject();
637
+ result.addProperty("available", true);
638
+ result.addProperty("provenance", "ghidra-high-pcode");
639
+ result.add("operations", operationValues);
640
+ result.add("def_use", defUse);
641
+ result.add("effects", effects);
642
+ boolean truncated = omittedOperationsLowerBound > 0 ||
643
+ omittedInputs > 0 || omittedEdges > 0;
644
+ result.addProperty("truncated", truncated);
645
+ result.addProperty(
646
+ "omitted_operations_lower_bound",
647
+ omittedOperationsLowerBound
648
+ );
649
+ result.addProperty("known_omitted_inputs", omittedInputs);
650
+ result.addProperty("known_omitted_edges", omittedEdges);
651
+ JsonArray limitations = new JsonArray();
652
+ limitations.add(
653
+ "High p-code is a decompiler-derived intra-function representation, not original source or runtime behavior; dead operations are retained and marked."
654
+ );
655
+ limitations.add(
656
+ "Def-use links do not cross function boundaries and may omit memory aliasing, call side effects, or unresolved indirect flow."
657
+ );
658
+ limitations.add(
659
+ "Memory read/write effects include stack and temporary memory operations; they do not identify persistent state or business meaning."
660
+ );
661
+ limitations.add(
662
+ "Operation, input, and def-use limits can truncate large functions; inspect truncation, omitted lower bounds, and known omitted counts before drawing conclusions."
663
+ );
664
+ limitations.add(
665
+ "Known omitted-edge counts cover links omitted from retained operations; omitted operations may contain additional uncounted links."
666
+ );
667
+ result.add("limitations", limitations);
668
+ return result;
669
+ }
670
+
671
+ private String pcodeOperationId(PcodeOpAST operation) {
672
+ return canonicalAddress(operation.getSeqnum().getTarget()) + "#" +
673
+ operation.getSeqnum().getTime();
674
+ }
675
+
676
+ private JsonObject varnodeValue(Varnode varnode) {
677
+ JsonObject result = new JsonObject();
678
+ String kind = varnode.isConstant()
679
+ ? "constant"
680
+ : varnode.isAddress()
681
+ ? "address"
682
+ : varnode.isRegister()
683
+ ? "register"
684
+ : varnode.isUnique() ? "unique" : "other";
685
+ result.addProperty("kind", kind);
686
+ result.addProperty("size_bytes", varnode.getSize());
687
+ result.add(
688
+ "location",
689
+ varnode.isConstant()
690
+ ? JsonNull.INSTANCE
691
+ : GSON.toJsonTree(canonicalAddress(varnode.getAddress()))
692
+ );
693
+ result.add(
694
+ "constant_hex",
695
+ varnode.isConstant()
696
+ ? GSON.toJsonTree(Long.toUnsignedString(varnode.getOffset(), 16))
697
+ : JsonNull.INSTANCE
698
+ );
699
+ return result;
700
+ }
701
+
702
+ private void addPcodeEffect(
703
+ JsonArray destination,
704
+ PcodeOp operation,
705
+ String operationId
706
+ ) {
707
+ String kind = null;
708
+ Integer operandInput = null;
709
+ Integer valueInput = null;
710
+ switch (operation.getOpcode()) {
711
+ case PcodeOp.LOAD -> {
712
+ kind = "memory_read";
713
+ operandInput = 1;
714
+ }
715
+ case PcodeOp.STORE -> {
716
+ kind = "memory_write";
717
+ operandInput = 1;
718
+ valueInput = 2;
719
+ }
720
+ case PcodeOp.CBRANCH -> {
721
+ kind = "conditional_branch";
722
+ valueInput = 1;
723
+ }
724
+ case PcodeOp.BRANCHIND -> {
725
+ kind = "indirect_branch";
726
+ operandInput = 0;
727
+ }
728
+ case PcodeOp.CALL -> {
729
+ kind = "direct_call";
730
+ operandInput = 0;
731
+ }
732
+ case PcodeOp.CALLIND -> {
733
+ kind = "indirect_call";
734
+ operandInput = 0;
735
+ }
736
+ default -> { }
737
+ }
738
+ if (kind == null) return;
739
+ JsonObject effect = new JsonObject();
740
+ effect.addProperty("operation", operationId);
741
+ effect.addProperty("kind", kind);
742
+ effect.add(
743
+ "operand_input",
744
+ operandInput == null ? JsonNull.INSTANCE : GSON.toJsonTree(operandInput)
745
+ );
746
+ effect.add(
747
+ "value_input",
748
+ valueInput == null ? JsonNull.INSTANCE : GSON.toJsonTree(valueInput)
749
+ );
750
+ destination.add(effect);
751
+ }
752
+
709
753
  private JsonElement addressName(JsonObject params) {
710
754
  requireKeys(params, Set.of("document", "address"));
711
755
  requireDocument(params);
@@ -721,8 +765,8 @@ public final class ReaGhidraBridge extends HeadlessScript {
721
765
  return result;
722
766
  }
723
767
 
724
- private JsonObject listNames(JsonObject params) throws Exception {
725
- requireKeys(params, Set.of("document", "address", "offset", "limit"));
768
+ private JsonArray listNames(JsonObject params) throws Exception {
769
+ requireKeys(params, Set.of("document", "address"));
726
770
  requireDocument(params);
727
771
  List<InventoryItem> inventory = names();
728
772
  String requested = optionalString(params, "address");
@@ -732,26 +776,16 @@ public final class ReaGhidraBridge extends HeadlessScript {
732
776
  .filter(item -> item.address.equals(address))
733
777
  .toList();
734
778
  }
735
- return page(
736
- inventory,
737
- requireBoundedInteger(params, "offset", 0, Integer.MAX_VALUE),
738
- requireBoundedInteger(params, "limit", 1, 500),
739
- "symbol"
740
- );
779
+ return inventory(inventory, "symbol");
741
780
  }
742
781
 
743
- private JsonObject listProcedures(JsonObject params) throws Exception {
744
- requireKeys(params, Set.of("document", "offset", "limit"));
782
+ private JsonArray listProcedures(JsonObject params) throws Exception {
783
+ requireKeys(params, Set.of("document"));
745
784
  requireDocument(params);
746
785
  List<InventoryItem> inventory = procedures().stream()
747
786
  .map(FunctionEntry::item)
748
787
  .toList();
749
- return page(
750
- inventory,
751
- requireBoundedInteger(params, "offset", 0, Integer.MAX_VALUE),
752
- requireBoundedInteger(params, "limit", 1, 500),
753
- "procedure"
754
- );
788
+ return inventory(inventory, "procedure");
755
789
  }
756
790
 
757
791
  private JsonArray listSegments(JsonObject params) {
@@ -760,9 +794,6 @@ public final class ReaGhidraBridge extends HeadlessScript {
760
794
  JsonArray result = new JsonArray();
761
795
  String imageBase = canonicalAddress(currentProgram.getImageBase());
762
796
  MemoryBlock[] blocks = currentProgram.getMemory().getBlocks();
763
- if (blocks.length > MAX_INVENTORY_ITEMS) {
764
- throw inventoryLimit();
765
- }
766
797
  List<MemoryBlock> ordered = new ArrayList<>(List.of(blocks));
767
798
  ordered.sort(Comparator.comparing(MemoryBlock::getStart));
768
799
  for (MemoryBlock block : ordered) {
@@ -773,8 +804,8 @@ public final class ReaGhidraBridge extends HeadlessScript {
773
804
  return result;
774
805
  }
775
806
 
776
- private JsonObject listStrings(JsonObject params) throws Exception {
777
- requireKeys(params, Set.of("document", "address", "offset", "limit"));
807
+ private JsonArray listStrings(JsonObject params) throws Exception {
808
+ requireKeys(params, Set.of("document", "address"));
778
809
  requireDocument(params);
779
810
  List<InventoryItem> inventory = strings();
780
811
  String requested = optionalString(params, "address");
@@ -784,12 +815,7 @@ public final class ReaGhidraBridge extends HeadlessScript {
784
815
  .filter(item -> item.address.equals(address))
785
816
  .toList();
786
817
  }
787
- return page(
788
- inventory,
789
- requireBoundedInteger(params, "offset", 0, Integer.MAX_VALUE),
790
- requireBoundedInteger(params, "limit", 1, 500),
791
- "string"
792
- );
818
+ return inventory(inventory, "string");
793
819
  }
794
820
 
795
821
  private JsonElement procedureAddress(JsonObject params) throws Exception {
@@ -820,26 +846,18 @@ public final class ReaGhidraBridge extends HeadlessScript {
820
846
  return result;
821
847
  }
822
848
 
823
- private JsonObject search(JsonObject params, boolean procedureSearch) throws Exception {
849
+ private JsonArray search(JsonObject params, boolean procedureSearch) throws Exception {
824
850
  requireKeys(
825
851
  params,
826
- Set.of("pattern", "mode", "case_sensitive", "offset", "limit", "document")
852
+ Set.of("pattern", "mode", "case_sensitive", "document")
827
853
  );
828
854
  requireDocument(params);
829
855
  String expression = requireString(params, "pattern");
830
- if (expression.codePointCount(0, expression.length()) > 256) {
831
- throw new RequestFailure(
832
- "invalid_request",
833
- "pattern must contain between 1 and 256 characters"
834
- );
835
- }
836
856
  String mode = requireString(params, "mode");
837
857
  if (!mode.equals("literal") && !mode.equals("regex")) {
838
858
  throw new RequestFailure("invalid_request", "mode must be literal or regex");
839
859
  }
840
860
  boolean caseSensitive = requireBoolean(params, "case_sensitive");
841
- int offset = requireBoundedInteger(params, "offset", 0, Integer.MAX_VALUE);
842
- int limit = requireBoundedInteger(params, "limit", 1, 100);
843
861
  List<InventoryItem> inventory = procedureSearch
844
862
  ? procedures().stream().map(FunctionEntry::item).toList()
845
863
  : strings();
@@ -847,18 +865,13 @@ public final class ReaGhidraBridge extends HeadlessScript {
847
865
  ? literalMatcher(expression, caseSensitive)
848
866
  : regexMatcher(expression, caseSensitive);
849
867
  JsonArray items = new JsonArray();
850
- int total = 0;
851
- long pageEnd = (long) offset + limit;
852
868
  for (InventoryItem item : inventory) {
853
869
  if (!matcher.matches(item.value)) {
854
870
  continue;
855
871
  }
856
- if (total >= offset && total < pageEnd) {
857
- items.add(resultItem(item, null, MAX_SEARCH_VALUE_CODE_POINTS));
858
- }
859
- total += 1;
872
+ items.add(resultItem(item, null));
860
873
  }
861
- return pageResult(items, offset, limit, total);
874
+ return items;
862
875
  }
863
876
 
864
877
  private List<InventoryItem> names() throws Exception {
@@ -880,10 +893,7 @@ public final class ReaGhidraBridge extends HeadlessScript {
880
893
  facts.addProperty("external", symbol.isExternal());
881
894
  facts.addProperty("type", normalizedSymbolType(symbol));
882
895
  facts.addProperty("source", normalizedSource(symbol.getSource()));
883
- addBounded(
884
- result,
885
- new InventoryItem(address, symbol.getName(true), facts)
886
- );
896
+ result.add(new InventoryItem(address, symbol.getName(true), facts));
887
897
  }
888
898
  result.sort(INVENTORY_ORDER);
889
899
  nameInventory = List.copyOf(result);
@@ -929,9 +939,6 @@ public final class ReaGhidraBridge extends HeadlessScript {
929
939
  symbol == null ? function.getName() : symbol.getName(true),
930
940
  facts
931
941
  );
932
- if (destination.size() >= MAX_INVENTORY_ITEMS) {
933
- throw inventoryLimit();
934
- }
935
942
  destination.add(new FunctionEntry(function, item));
936
943
  }
937
944
  }
@@ -961,7 +968,7 @@ public final class ReaGhidraBridge extends HeadlessScript {
961
968
  instance.isMissingNullTerminator() ? "missing" : "present_or_not_required"
962
969
  );
963
970
  facts.addProperty("byte_length", Math.max(0, data.getLength()));
964
- addBounded(result, new InventoryItem(data.getAddress(), value, facts));
971
+ result.add(new InventoryItem(data.getAddress(), value, facts));
965
972
  }
966
973
  result.sort(INVENTORY_ORDER);
967
974
  stringInventory = List.copyOf(result);
@@ -1000,8 +1007,7 @@ public final class ReaGhidraBridge extends HeadlessScript {
1000
1007
 
1001
1008
  private JsonObject nativeApiBoundary(
1002
1009
  Function function,
1003
- DecompileResults decompilation,
1004
- int maximumBytes
1010
+ DecompileResults decompilation
1005
1011
  ) throws Exception {
1006
1012
  if (decompilation == null || decompilation.getHighFunction() == null) {
1007
1013
  return unavailableNativeApiBoundary(
@@ -1041,11 +1047,9 @@ public final class ReaGhidraBridge extends HeadlessScript {
1041
1047
 
1042
1048
  JsonArray parameters = new JsonArray();
1043
1049
  result.add("parameters", parameters);
1044
- result.addProperty("parameters_truncated", false);
1045
1050
 
1046
1051
  JsonArray jumpTables = new JsonArray();
1047
1052
  result.add("jump_tables", jumpTables);
1048
- result.addProperty("jump_tables_truncated", false);
1049
1053
 
1050
1054
  JsonObject pseudocode = new JsonObject();
1051
1055
  pseudocode.addProperty("classification", "decompiler-generated-non-source");
@@ -1071,17 +1075,7 @@ public final class ReaGhidraBridge extends HeadlessScript {
1071
1075
  "Pseudocode is neither original source nor guaranteed to compile."
1072
1076
  );
1073
1077
  result.add("limitations", limitations);
1074
- if (encodedUtf8Length(result) > maximumBytes) {
1075
- return unavailableNativeApiBoundary(
1076
- "The remaining Ghidra response budget cannot contain a structured native API boundary"
1077
- );
1078
- }
1079
-
1080
- int parameterCount = Math.min(
1081
- prototype.getNumParams(),
1082
- MAX_NATIVE_API_PARAMETERS
1083
- );
1084
- for (int index = 0; index < parameterCount; index += 1) {
1078
+ for (int index = 0; index < prototype.getNumParams(); index += 1) {
1085
1079
  HighSymbol parameter = prototype.getParam(index);
1086
1080
  if (parameter == null) {
1087
1081
  continue;
@@ -1097,32 +1091,12 @@ public final class ReaGhidraBridge extends HeadlessScript {
1097
1091
  signatureSource
1098
1092
  )
1099
1093
  );
1100
- if (encodedUtf8Length(result) > maximumBytes) {
1101
- parameters.remove(parameters.size() - 1);
1102
- break;
1103
- }
1104
1094
  }
1105
- result.addProperty(
1106
- "parameters_truncated",
1107
- prototype.getNumParams() > parameters.size()
1108
- );
1109
1095
 
1110
1096
  JumpTable[] recoveredJumpTables = highFunction.getJumpTables();
1111
- int jumpTableCount = Math.min(
1112
- recoveredJumpTables.length,
1113
- MAX_NATIVE_API_JUMP_TABLES
1114
- );
1115
- for (int index = 0; index < jumpTableCount; index += 1) {
1097
+ for (int index = 0; index < recoveredJumpTables.length; index += 1) {
1116
1098
  jumpTables.add(inferredJumpTable(recoveredJumpTables[index]));
1117
- if (encodedUtf8Length(result) > maximumBytes) {
1118
- jumpTables.remove(jumpTables.size() - 1);
1119
- break;
1120
- }
1121
1099
  }
1122
- result.addProperty(
1123
- "jump_tables_truncated",
1124
- recoveredJumpTables.length > jumpTables.size()
1125
- );
1126
1100
  return result;
1127
1101
  }
1128
1102
 
@@ -1137,10 +1111,6 @@ public final class ReaGhidraBridge extends HeadlessScript {
1137
1111
  return unavailable;
1138
1112
  }
1139
1113
 
1140
- private static int encodedUtf8Length(JsonElement value) {
1141
- return GSON.toJson(value).getBytes(StandardCharsets.UTF_8).length;
1142
- }
1143
-
1144
1114
  private static JsonObject inferredBoundaryType(
1145
1115
  String role,
1146
1116
  Integer ordinal,
@@ -1228,22 +1198,30 @@ public final class ReaGhidraBridge extends HeadlessScript {
1228
1198
  canonicalAddress(jumpTable.getSwitchAddress())
1229
1199
  );
1230
1200
  JumpTable.LoadTable[] loadTables = jumpTable.getLoadTables();
1201
+ RecoveredAArch64ByteTable recoveredAArch64 =
1202
+ loadTables.length == 0
1203
+ ? recoverAArch64ByteTable(jumpTable)
1204
+ : null;
1231
1205
  List<Address> referencedDataAddresses = loadTables.length == 0
1232
1206
  ? dispatchPathDataReferences(jumpTable)
1233
1207
  : List.of();
1234
1208
  JsonArray dataSources = new JsonArray();
1235
- int loadTableCount = Math.min(
1236
- loadTables.length,
1237
- MAX_NATIVE_API_LOAD_TABLES
1238
- );
1239
- for (int index = 0; index < loadTableCount; index += 1) {
1209
+ for (int index = 0; index < loadTables.length; index += 1) {
1210
+ monitor.checkCancelled();
1240
1211
  JumpTable.LoadTable loadTable = loadTables[index];
1241
1212
  JsonObject source = new JsonObject();
1242
1213
  source.addProperty("address", canonicalAddress(loadTable.getAddress()));
1243
1214
  source.addProperty("provenance", "ghidra-decompiler-load-table");
1244
- source.addProperty("entry_size_bytes", loadTable.getSize());
1245
- source.addProperty("entry_count", loadTable.getNum());
1246
- source.addProperty("confidence", "high");
1215
+ int entrySize = loadTable.getSize();
1216
+ int entryCount = loadTable.getNum();
1217
+ if (entrySize > 0) source.addProperty("entry_size_bytes", entrySize);
1218
+ else source.add("entry_size_bytes", JsonNull.INSTANCE);
1219
+ if (entryCount > 0) source.addProperty("entry_count", entryCount);
1220
+ else source.add("entry_count", JsonNull.INSTANCE);
1221
+ source.addProperty(
1222
+ "confidence",
1223
+ entrySize > 0 && entryCount > 0 ? "high" : "medium"
1224
+ );
1247
1225
  JsonArray evidence = new JsonArray();
1248
1226
  evidence.add(
1249
1227
  inferenceEvidence(
@@ -1257,12 +1235,37 @@ public final class ReaGhidraBridge extends HeadlessScript {
1257
1235
  source.add("evidence", evidence);
1258
1236
  dataSources.add(source);
1259
1237
  }
1260
- int referencedDataCount = Math.min(
1261
- referencedDataAddresses.size(),
1262
- MAX_NATIVE_API_LOAD_TABLES - loadTableCount
1263
- );
1264
- for (int index = 0; index < referencedDataCount; index += 1) {
1238
+ if (recoveredAArch64 != null) {
1239
+ JsonObject source = new JsonObject();
1240
+ source.addProperty(
1241
+ "address",
1242
+ canonicalAddress(recoveredAArch64.tableAddress)
1243
+ );
1244
+ source.addProperty("provenance", "ghidra-aarch64-byte-table");
1245
+ source.addProperty("entry_size_bytes", 1);
1246
+ source.addProperty("entry_count", recoveredAArch64.mappings.size());
1247
+ source.addProperty("confidence", "high");
1248
+ JsonArray evidence = new JsonArray();
1249
+ evidence.add(
1250
+ inferenceEvidence(
1251
+ "jump-table",
1252
+ "ghidra-instruction-and-memory",
1253
+ "Decoded " + recoveredAArch64.mappings.size() +
1254
+ " byte entries from " + canonicalAddress(recoveredAArch64.tableAddress) +
1255
+ "; the AArch64 LDRB/ADR/ADD/BR chain uses a selector guarded by an unsigned upper-bound branch."
1256
+ )
1257
+ );
1258
+ source.add("evidence", evidence);
1259
+ dataSources.add(source);
1260
+ }
1261
+ for (int index = 0; index < referencedDataAddresses.size(); index += 1) {
1265
1262
  Address address = referencedDataAddresses.get(index);
1263
+ if (
1264
+ recoveredAArch64 != null &&
1265
+ recoveredAArch64.tableAddress.equals(address)
1266
+ ) {
1267
+ continue;
1268
+ }
1266
1269
  JsonObject source = new JsonObject();
1267
1270
  source.addProperty("address", canonicalAddress(address));
1268
1271
  source.addProperty(
@@ -1286,19 +1289,39 @@ public final class ReaGhidraBridge extends HeadlessScript {
1286
1289
  dataSources.add(source);
1287
1290
  }
1288
1291
  result.add("data_sources", dataSources);
1289
- result.addProperty(
1290
- "data_sources_truncated",
1291
- loadTables.length > loadTableCount ||
1292
- referencedDataAddresses.size() > referencedDataCount
1293
- );
1294
-
1295
1292
  Address[] targets = jumpTable.getCases();
1296
1293
  Integer[] labels = jumpTable.getLabelValues();
1294
+ boolean labelsAligned = labels != null && labels.length == targets.length;
1297
1295
  JsonArray mappings = new JsonArray();
1298
- int mappingCount = Math.min(targets.length, MAX_NATIVE_API_MAPPINGS);
1299
- for (int index = 0; index < mappingCount; index += 1) {
1296
+ if (recoveredAArch64 != null) {
1297
+ for (int index = 0; index < recoveredAArch64.mappings.size(); index += 1) {
1298
+ monitor.checkCancelled();
1299
+ RecoveredAArch64Mapping recovered = recoveredAArch64.mappings.get(index);
1300
+ JsonObject mapping = new JsonObject();
1301
+ mapping.addProperty("case_value", recovered.caseValue);
1302
+ String targetAddress = canonicalAddress(recovered.targetAddress);
1303
+ mapping.addProperty("target_address", targetAddress);
1304
+ mapping.addProperty("confidence", "high");
1305
+ JsonArray evidence = new JsonArray();
1306
+ evidence.add(
1307
+ inferenceEvidence(
1308
+ "jump-table",
1309
+ "ghidra-instruction-and-memory",
1310
+ "Case value " + recovered.caseValue + " indexes byte " +
1311
+ recovered.caseValue + " at " + canonicalAddress(recoveredAArch64.tableAddress) +
1312
+ "; the loaded byte selects a four-byte instruction offset from " +
1313
+ canonicalAddress(recoveredAArch64.branchBase) + ", reaching " + targetAddress + "."
1314
+ )
1315
+ );
1316
+ mapping.add("evidence", evidence);
1317
+ mappings.add(mapping);
1318
+ }
1319
+ }
1320
+ else {
1321
+ for (int index = 0; index < targets.length; index += 1) {
1322
+ monitor.checkCancelled();
1300
1323
  JsonObject mapping = new JsonObject();
1301
- if (labels == null || index >= labels.length || labels[index] == null) {
1324
+ if (!labelsAligned || labels[index] == null) {
1302
1325
  mapping.add("case_value", JsonNull.INSTANCE);
1303
1326
  }
1304
1327
  else {
@@ -1306,61 +1329,299 @@ public final class ReaGhidraBridge extends HeadlessScript {
1306
1329
  }
1307
1330
  String targetAddress = canonicalAddress(targets[index]);
1308
1331
  mapping.addProperty("target_address", targetAddress);
1309
- JsonArray dataAddresses = new JsonArray();
1310
- for (int sourceIndex = 0; sourceIndex < loadTableCount; sourceIndex += 1) {
1311
- dataAddresses.add(
1312
- canonicalAddress(loadTables[sourceIndex].getAddress())
1313
- );
1314
- }
1315
- mapping.add("data_addresses", dataAddresses);
1316
- mapping.addProperty("confidence", "medium");
1332
+ // Ghidra's recovered case-to-target relation does not identify
1333
+ // which backing load table produced an individual case. Keep the
1334
+ // table inventory and recovered mappings separate instead of
1335
+ // attaching every table address to every target.
1336
+ mapping.addProperty(
1337
+ "confidence",
1338
+ labelsAligned && labels[index] != null
1339
+ ? "high"
1340
+ : "medium"
1341
+ );
1317
1342
  JsonArray evidence = new JsonArray();
1318
1343
  evidence.add(
1319
1344
  inferenceEvidence(
1320
1345
  "jump-table",
1321
1346
  "ghidra-high-function",
1322
- "Recovered target " +
1323
- targetAddress +
1324
- " from dispatch " +
1325
- canonicalAddress(jumpTable.getSwitchAddress()) +
1326
- "."
1347
+ labelsAligned && labels[index] != null
1348
+ ? "Ghidra paired case value " +
1349
+ labels[index] +
1350
+ " with target " +
1351
+ targetAddress +
1352
+ " at dispatch " +
1353
+ canonicalAddress(jumpTable.getSwitchAddress()) +
1354
+ "."
1355
+ : "Ghidra recovered target " +
1356
+ targetAddress +
1357
+ " at dispatch " +
1358
+ canonicalAddress(jumpTable.getSwitchAddress()) +
1359
+ ", but no aligned case value was available."
1327
1360
  )
1328
1361
  );
1329
1362
  mapping.add("evidence", evidence);
1330
1363
  mappings.add(mapping);
1331
1364
  }
1365
+ }
1332
1366
  result.add("mappings", mappings);
1333
- result.addProperty("mappings_truncated", targets.length > mappingCount);
1334
-
1335
1367
  JsonArray limitations = new JsonArray();
1336
1368
  if (loadTables.length == 0 && referencedDataAddresses.isEmpty()) {
1337
1369
  limitations.add(
1338
1370
  "Ghidra recovered targets but exposed no backing load-table or dispatch-block data address."
1339
1371
  );
1340
1372
  }
1341
- if (loadTables.length == 0 && !referencedDataAddresses.isEmpty()) {
1373
+ if (
1374
+ recoveredAArch64 == null &&
1375
+ loadTables.length == 0 &&
1376
+ !referencedDataAddresses.isEmpty()
1377
+ ) {
1342
1378
  limitations.add(
1343
1379
  "Load-table metadata was absent; table-level candidate ownership is inferred from typed data references in the dispatch block and its immediate control-flow predecessors, but candidates are not assigned to individual mappings."
1344
1380
  );
1345
1381
  }
1346
- if (labels == null || labels.length != targets.length) {
1382
+ if (recoveredAArch64 == null && !labelsAligned) {
1347
1383
  limitations.add(
1348
- "Case labels were unavailable or incomplete; null labels are preserved."
1384
+ "Case-label and target counts differ or labels are unavailable; all case values are left unknown because index alignment cannot be established."
1349
1385
  );
1350
1386
  }
1351
- if (
1352
- loadTables.length > loadTableCount ||
1353
- referencedDataAddresses.size() > referencedDataCount ||
1354
- targets.length > mappingCount
1355
- ) {
1387
+ if (targets.length == 0) {
1356
1388
  limitations.add(
1357
- "Native API jump-table observations reached REA's bounded output limits."
1389
+ "Ghidra exposed a jump-table dispatch without recovered case targets."
1358
1390
  );
1359
1391
  }
1360
1392
  result.add("limitations", limitations);
1361
1393
  return result;
1362
1394
  }
1363
1395
 
1396
+ private static final class RecoveredAArch64Mapping {
1397
+ private final int caseValue;
1398
+ private final Address targetAddress;
1399
+
1400
+ private RecoveredAArch64Mapping(int caseValue, Address targetAddress) {
1401
+ this.caseValue = caseValue;
1402
+ this.targetAddress = targetAddress;
1403
+ }
1404
+ }
1405
+
1406
+ private static final class RecoveredAArch64ByteTable {
1407
+ private final Address tableAddress;
1408
+ private final Address branchBase;
1409
+ private final List<RecoveredAArch64Mapping> mappings;
1410
+
1411
+ private RecoveredAArch64ByteTable(
1412
+ Address tableAddress,
1413
+ Address branchBase,
1414
+ List<RecoveredAArch64Mapping> mappings
1415
+ ) {
1416
+ this.tableAddress = tableAddress;
1417
+ this.branchBase = branchBase;
1418
+ this.mappings = mappings;
1419
+ }
1420
+ }
1421
+
1422
+ /**
1423
+ * Recover only the directly verified AArch64 byte-indexed relative branch
1424
+ * table form. Other instruction sequences keep their case labels unknown.
1425
+ */
1426
+ private RecoveredAArch64ByteTable recoverAArch64ByteTable(
1427
+ JumpTable jumpTable
1428
+ ) throws Exception {
1429
+ if (!currentProgram.getLanguageID().getIdAsString().startsWith("AARCH64:")) {
1430
+ return null;
1431
+ }
1432
+ Function function = containingFunction(jumpTable.getSwitchAddress());
1433
+ if (function == null) return null;
1434
+
1435
+ List<Instruction> instructions = new ArrayList<>();
1436
+ InstructionIterator iterator = currentProgram.getListing().getInstructions(
1437
+ function.getBody(), true
1438
+ );
1439
+ while (iterator.hasNext()) {
1440
+ monitor.checkCancelled();
1441
+ Instruction instruction = iterator.next();
1442
+ instructions.add(instruction);
1443
+ if (instruction.getAddress().equals(jumpTable.getSwitchAddress())) break;
1444
+ }
1445
+ int branchIndex = instructions.size() - 1;
1446
+ if (branchIndex < 1 ||
1447
+ !instructions.get(branchIndex).getMnemonicString().equalsIgnoreCase("BR")) {
1448
+ return null;
1449
+ }
1450
+
1451
+ Address[] recoveredTargets = jumpTable.getCases();
1452
+ Set<String> recoveredTargetSet = new HashSet<>();
1453
+ for (Address target : recoveredTargets) recoveredTargetSet.add(canonicalAddress(target));
1454
+ List<Address> dataAddresses = dispatchPathDataReferences(jumpTable);
1455
+ if (dataAddresses.isEmpty()) return null;
1456
+
1457
+ for (int loadIndex = 0; loadIndex < branchIndex; loadIndex += 1) {
1458
+ Instruction load = instructions.get(loadIndex);
1459
+ if (!load.getMnemonicString().equalsIgnoreCase("LDRB")) continue;
1460
+ String loadOperands = operands(load);
1461
+ java.util.regex.Matcher loadMatcher = java.util.regex.Pattern
1462
+ .compile("(?i)^[wx]\\d+,\\s*\\[([wx]\\d+),\\s*([wx]\\d+)\\]$")
1463
+ .matcher(loadOperands);
1464
+ if (!loadMatcher.matches()) continue;
1465
+ String loadedRegister = normalizedRegister(loadOperands.substring(0, loadOperands.indexOf(',')));
1466
+ String tableBaseRegister = normalizedRegister(loadMatcher.group(1));
1467
+ String indexRegister = normalizedRegister(loadMatcher.group(2));
1468
+
1469
+ Address tableAddress = null;
1470
+ for (Address candidate : dataAddresses) {
1471
+ if (currentProgram.getMemory().contains(candidate)) {
1472
+ tableAddress = candidate;
1473
+ break;
1474
+ }
1475
+ }
1476
+ if (tableAddress == null) continue;
1477
+
1478
+ Address resolvedTableAddress = null;
1479
+ for (int index = 0; index < loadIndex; index += 1) {
1480
+ Instruction instruction = instructions.get(index);
1481
+ String mnemonic = instruction.getMnemonicString().toUpperCase(Locale.ROOT);
1482
+ String text = operands(instruction);
1483
+ if (mnemonic.equals("ADRP") &&
1484
+ text.matches("(?i)^[wx]\\d+,\\s*0x[0-9a-f]+$")) {
1485
+ String[] parts = text.split(",");
1486
+ if (normalizedRegister(parts[0]).equals(tableBaseRegister)) {
1487
+ resolvedTableAddress = addressFromOperand(parts[1]);
1488
+ }
1489
+ }
1490
+ else if (mnemonic.equals("ADD") && resolvedTableAddress != null &&
1491
+ text.matches("(?i)^[wx]\\d+,\\s*[wx]\\d+,\\s*#?(?:0x[0-9a-f]+|[0-9]+)$")) {
1492
+ String[] parts = text.split(",");
1493
+ if (normalizedRegister(parts[0]).equals(tableBaseRegister) &&
1494
+ normalizedRegister(parts[1]).equals(tableBaseRegister)) {
1495
+ resolvedTableAddress = resolvedTableAddress.add(parseInteger(parts[2].trim().replaceFirst("^#", "")));
1496
+ }
1497
+ }
1498
+ }
1499
+ if (!tableAddress.equals(resolvedTableAddress)) continue;
1500
+
1501
+ Address branchBase = null;
1502
+ boolean addressChain = false;
1503
+ for (int index = 0; index < branchIndex; index += 1) {
1504
+ Instruction instruction = instructions.get(index);
1505
+ String mnemonic = instruction.getMnemonicString().toUpperCase(Locale.ROOT);
1506
+ String text = operands(instruction);
1507
+ if (index < loadIndex && mnemonic.equals("ADR") && text.matches("(?i)^[wx]\\d+,\\s*0x[0-9a-f]+$")) {
1508
+ int comma = text.indexOf(',');
1509
+ String destination = normalizedRegister(text.substring(0, comma));
1510
+ if (destination.equals(tableBaseRegister)) continue;
1511
+ branchBase = addressFromOperand(text.substring(comma + 1));
1512
+ }
1513
+ if (index > loadIndex && mnemonic.equals("ADD") && branchBase != null &&
1514
+ text.matches("(?i)^[wx]\\d+,\\s*[wx]\\d+,\\s*[wx]\\d+,\\s*LSL\\s*#(?:0x)?2$")) {
1515
+ String[] parts = text.split(",");
1516
+ String destination = normalizedRegister(parts[0]);
1517
+ String base = normalizedRegister(parts[1]);
1518
+ String offset = normalizedRegister(parts[2]);
1519
+ if (destination.equals(base) &&
1520
+ base.equals(normalizedRegister(operands(instructions.get(branchIndex)))) &&
1521
+ offset.equals(loadedRegister)) {
1522
+ addressChain = true;
1523
+ }
1524
+ }
1525
+ }
1526
+ if (!addressChain || branchBase == null) continue;
1527
+
1528
+ int entryCount = -1;
1529
+ Address defaultTarget = null;
1530
+ for (int compareIndex = 0; compareIndex < loadIndex; compareIndex += 1) {
1531
+ Instruction compare = instructions.get(compareIndex);
1532
+ if (!compare.getMnemonicString().equalsIgnoreCase("CMP")) continue;
1533
+ java.util.regex.Matcher compareMatcher = java.util.regex.Pattern
1534
+ .compile("(?i)^([wx]\\d+),\\s*#?(0x[0-9a-f]+|[0-9]+)$")
1535
+ .matcher(operands(compare));
1536
+ if (!compareMatcher.matches()) continue;
1537
+ String comparedRegister = normalizedRegister(compareMatcher.group(1));
1538
+ long upperBound = parseInteger(compareMatcher.group(2));
1539
+ if (upperBound < 0 || upperBound >= 4096) continue;
1540
+ for (int guardIndex = compareIndex + 1; guardIndex < loadIndex; guardIndex += 1) {
1541
+ Instruction guard = instructions.get(guardIndex);
1542
+ String mnemonic = guard.getMnemonicString().toUpperCase(Locale.ROOT);
1543
+ if (!mnemonic.equals("B.HI")) continue;
1544
+ Address[] flows = guard.getFlows();
1545
+ if (flows.length != 1 || !recoveredTargetSet.contains(canonicalAddress(flows[0]))) continue;
1546
+ boolean selectorFeedsTable = indexRegister.equals(comparedRegister);
1547
+ boolean supportedSequence = true;
1548
+ for (int sequenceIndex = compareIndex + 1; sequenceIndex < loadIndex; sequenceIndex += 1) {
1549
+ String sequenceMnemonic = instructions.get(sequenceIndex)
1550
+ .getMnemonicString().toUpperCase(Locale.ROOT);
1551
+ if (!Set.of("B.HI", "MOV", "ADRP", "ADD", "ADR").contains(sequenceMnemonic)) {
1552
+ supportedSequence = false;
1553
+ break;
1554
+ }
1555
+ }
1556
+ for (int moveIndex = compareIndex + 1; moveIndex < loadIndex && !selectorFeedsTable; moveIndex += 1) {
1557
+ Instruction move = instructions.get(moveIndex);
1558
+ if (!move.getMnemonicString().equalsIgnoreCase("MOV")) continue;
1559
+ String[] moveParts = operands(move).split(",");
1560
+ if (moveParts.length == 2 &&
1561
+ normalizedRegister(moveParts[0]).equals(indexRegister) &&
1562
+ normalizedRegister(moveParts[1]).equals(comparedRegister)) {
1563
+ selectorFeedsTable = true;
1564
+ }
1565
+ }
1566
+ if (selectorFeedsTable && supportedSequence) {
1567
+ entryCount = (int) upperBound + 1;
1568
+ defaultTarget = flows[0];
1569
+ break;
1570
+ }
1571
+ }
1572
+ if (entryCount > 0) break;
1573
+ }
1574
+ if (entryCount <= 0 || entryCount > 4096 || defaultTarget == null) continue;
1575
+
1576
+ List<RecoveredAArch64Mapping> mappings = new ArrayList<>();
1577
+ Set<String> mappedTargets = new HashSet<>();
1578
+ boolean valid = true;
1579
+ for (int caseValue = 0; caseValue < entryCount; caseValue += 1) {
1580
+ int encodedOffset = currentProgram.getMemory().getByte(tableAddress.add(caseValue)) & 0xff;
1581
+ Address target = branchBase.add((long) encodedOffset * 4L);
1582
+ String canonicalTarget = canonicalAddress(target);
1583
+ if (!recoveredTargetSet.contains(canonicalTarget)) {
1584
+ valid = false;
1585
+ break;
1586
+ }
1587
+ mappedTargets.add(canonicalTarget);
1588
+ mappings.add(new RecoveredAArch64Mapping(caseValue, target));
1589
+ }
1590
+ if (!valid || mappedTargets.contains(canonicalAddress(defaultTarget))) continue;
1591
+ Set<String> accountedTargets = new HashSet<>(mappedTargets);
1592
+ accountedTargets.add(canonicalAddress(defaultTarget));
1593
+ if (!accountedTargets.equals(recoveredTargetSet)) continue;
1594
+ return new RecoveredAArch64ByteTable(tableAddress, branchBase, List.copyOf(mappings));
1595
+ }
1596
+ return null;
1597
+ }
1598
+
1599
+ private static String operands(Instruction instruction) {
1600
+ List<String> values = new ArrayList<>();
1601
+ for (int index = 0; index < instruction.getNumOperands(); index += 1) {
1602
+ values.add(instruction.getDefaultOperandRepresentation(index));
1603
+ }
1604
+ return String.join(", ", values);
1605
+ }
1606
+
1607
+ private static String normalizedRegister(String value) {
1608
+ String register = value.trim().toLowerCase(Locale.ROOT);
1609
+ return register.matches("[wx]\\d+") ? "r" + register.substring(1) : register;
1610
+ }
1611
+
1612
+ private static long parseInteger(String value) {
1613
+ String number = value.toLowerCase(Locale.ROOT);
1614
+ return number.startsWith("0x")
1615
+ ? Long.parseUnsignedLong(number.substring(2), 16)
1616
+ : Long.parseLong(number);
1617
+ }
1618
+
1619
+ private Address addressFromOperand(String value) {
1620
+ String hex = value.trim().replaceFirst("(?i)^0x", "");
1621
+ return currentProgram.getAddressFactory().getDefaultAddressSpace()
1622
+ .getAddress(Long.parseUnsignedLong(hex, 16));
1623
+ }
1624
+
1364
1625
  private List<Address> dispatchPathDataReferences(JumpTable jumpTable)
1365
1626
  throws Exception {
1366
1627
  BasicBlockModel model = new BasicBlockModel(currentProgram, false);
@@ -1443,22 +1704,16 @@ public final class ReaGhidraBridge extends HeadlessScript {
1443
1704
  }
1444
1705
  DecompileResults results = decompiler.decompileFunction(
1445
1706
  function,
1446
- DECOMPILE_TIMEOUT_SECONDS,
1707
+ 0,
1447
1708
  monitor
1448
1709
  );
1449
- if (results.isTimedOut()) {
1450
- throw new RequestFailure(
1451
- "decompile_timeout",
1452
- "Ghidra decompilation reached its 30-second deadline"
1453
- );
1454
- }
1455
1710
  if (results.isCancelled()) {
1456
1711
  throw new RequestFailure("decompile_cancelled", "Ghidra decompilation was cancelled");
1457
1712
  }
1458
1713
  if (!results.decompileCompleted()) {
1459
1714
  throw new RequestFailure(
1460
1715
  "decompile_failed",
1461
- "Ghidra decompilation failed: " + boundedMessage(results.getErrorMessage())
1716
+ "Ghidra decompilation failed: " + diagnosticMessage(results.getErrorMessage())
1462
1717
  );
1463
1718
  }
1464
1719
  return results;
@@ -1540,9 +1795,6 @@ public final class ReaGhidraBridge extends HeadlessScript {
1540
1795
  reference.getOperandIndex() + "\u0000" +
1541
1796
  reference.isPrimary();
1542
1797
  observed.putIfAbsent(key, reference);
1543
- if (observed.size() > MAX_FUNCTION_ITEMS) {
1544
- throw functionLimit("Reference set exceeds the 100000-item limit");
1545
- }
1546
1798
  }
1547
1799
 
1548
1800
  private JsonArray referenceEdges(List<Reference> references) {
@@ -1623,9 +1875,6 @@ public final class ReaGhidraBridge extends HeadlessScript {
1623
1875
 
1624
1876
  private static JsonArray functionLocals(Function function) {
1625
1877
  Variable[] variables = function.getLocalVariables();
1626
- if (variables.length > MAX_FUNCTION_ITEMS) {
1627
- throw functionLimit("Local-variable set exceeds the 100000-item limit");
1628
- }
1629
1878
  List<Variable> ordered = new ArrayList<>(List.of(variables));
1630
1879
  ordered.sort(
1631
1880
  Comparator.comparing(Variable::getName)
@@ -1646,9 +1895,6 @@ public final class ReaGhidraBridge extends HeadlessScript {
1646
1895
  }
1647
1896
 
1648
1897
  private JsonArray procedureIdentities(Set<Function> functions) {
1649
- if (functions.size() > MAX_FUNCTION_ITEMS) {
1650
- throw functionLimit("Resolved call set exceeds the 100000-item limit");
1651
- }
1652
1898
  List<Function> ordered = new ArrayList<>(functions);
1653
1899
  ordered.sort(Comparator.comparing(Function::getEntryPoint));
1654
1900
  JsonArray result = new JsonArray();
@@ -1664,9 +1910,6 @@ public final class ReaGhidraBridge extends HeadlessScript {
1664
1910
  List<CodeBlock> result = new ArrayList<>();
1665
1911
  while (iterator.hasNext()) {
1666
1912
  monitor.checkCancelled();
1667
- if (result.size() >= MAX_FUNCTION_ITEMS) {
1668
- throw functionLimit("Basic-block set exceeds the 100000-item limit");
1669
- }
1670
1913
  result.add(iterator.next());
1671
1914
  }
1672
1915
  result.sort(Comparator.comparing(CodeBlock::getFirstStartAddress));
@@ -1723,10 +1966,7 @@ public final class ReaGhidraBridge extends HeadlessScript {
1723
1966
  String key = canonicalAddress(instruction.getAddress()) + "\u0000" +
1724
1967
  kind + "\u0000" + type.name() + "\u0000" + text;
1725
1968
  observed.put(key, item);
1726
- if (observed.size() > MAX_FUNCTION_ITEMS) {
1727
- throw functionLimit("Comment set exceeds the 100000-item limit");
1728
- }
1729
- }
1969
+ }
1730
1970
  }
1731
1971
  JsonArray result = new JsonArray();
1732
1972
  observed.values().forEach(result::add);
@@ -1753,10 +1993,7 @@ public final class ReaGhidraBridge extends HeadlessScript {
1753
1993
  String key = canonicalAddress(data.getAddress()) + "\u0000" + value + "\u0000" +
1754
1994
  canonicalAddress(reference.getFromAddress());
1755
1995
  observed.put(key, item);
1756
- if (observed.size() > MAX_FUNCTION_ITEMS) {
1757
- throw functionLimit("Referenced-string set exceeds the 100000-item limit");
1758
1996
  }
1759
- }
1760
1997
  JsonArray result = new JsonArray();
1761
1998
  observed.values().forEach(result::add);
1762
1999
  return result;
@@ -1778,96 +2015,12 @@ public final class ReaGhidraBridge extends HeadlessScript {
1778
2015
  String key = canonicalAddress(reference.getToAddress()) + "\u0000" +
1779
2016
  symbol.getName(true) + "\u0000" + canonicalAddress(reference.getFromAddress());
1780
2017
  observed.put(key, item);
1781
- if (observed.size() > MAX_FUNCTION_ITEMS) {
1782
- throw functionLimit("Referenced-name set exceeds the 100000-item limit");
1783
2018
  }
1784
- }
1785
2019
  JsonArray result = new JsonArray();
1786
2020
  observed.values().forEach(result::add);
1787
2021
  return result;
1788
2022
  }
1789
2023
 
1790
- private static JsonObject pseudocodePage(String value, int offset, int limit) {
1791
- int total = value.codePointCount(0, value.length());
1792
- int start = Math.min(offset, total);
1793
- int returned = Math.min(limit, total - start);
1794
- int startIndex = value.offsetByCodePoints(0, start);
1795
- int endIndex = value.offsetByCodePoints(startIndex, returned);
1796
- int next = start + returned;
1797
- JsonObject result = new JsonObject();
1798
- result.addProperty("text", value.substring(startIndex, endIndex));
1799
- result.addProperty("total_chars", total);
1800
- result.addProperty("returned_chars", returned);
1801
- result.addProperty("truncated", next < total);
1802
- if (next < total) {
1803
- result.addProperty("next_offset", next);
1804
- }
1805
- else {
1806
- result.add("next_offset", JsonNull.INSTANCE);
1807
- }
1808
- return result;
1809
- }
1810
-
1811
- private static JsonObject bounded(
1812
- JsonArray values,
1813
- int offset,
1814
- int limit,
1815
- boolean complete) {
1816
- int start = Math.min(offset, values.size());
1817
- int end = Math.min(values.size(), start + limit);
1818
- JsonArray items = new JsonArray();
1819
- for (int index = start; index < end; index += 1) {
1820
- items.add(values.get(index).deepCopy());
1821
- }
1822
- boolean hasMore = end < values.size();
1823
- JsonObject result = new JsonObject();
1824
- result.add("items", items);
1825
- if (complete) {
1826
- result.addProperty("total", values.size());
1827
- }
1828
- else {
1829
- result.add("total", JsonNull.INSTANCE);
1830
- }
1831
- result.addProperty("returned", items.size());
1832
- result.addProperty("truncated", !complete || hasMore);
1833
- if (complete && hasMore) {
1834
- result.addProperty("next_offset", end);
1835
- }
1836
- else {
1837
- result.add("next_offset", JsonNull.INSTANCE);
1838
- }
1839
- return result;
1840
- }
1841
-
1842
- private static JsonObject requireCollectionOffsets(JsonObject params) {
1843
- JsonElement value = params.get("collection_offset");
1844
- if (value == null || !value.isJsonObject()) {
1845
- throw new RequestFailure("invalid_request", "collection_offset must be an object");
1846
- }
1847
- JsonObject result = value.getAsJsonObject();
1848
- requireKeys(
1849
- result,
1850
- Set.of(
1851
- "comments",
1852
- "callers",
1853
- "callees",
1854
- "incoming_references",
1855
- "outgoing_references",
1856
- "referenced_strings",
1857
- "referenced_names",
1858
- "basic_blocks"
1859
- )
1860
- );
1861
- for (String name : result.keySet()) {
1862
- requireBoundedInteger(result, name, 0, Integer.MAX_VALUE);
1863
- }
1864
- return result;
1865
- }
1866
-
1867
- private static int collectionOffset(JsonObject offsets, String name) {
1868
- return requireBoundedInteger(offsets, name, 0, Integer.MAX_VALUE);
1869
- }
1870
-
1871
2024
  private static String exclusiveAddress(Address inclusive) {
1872
2025
  Address next = inclusive.next();
1873
2026
  if (next != null && next.getAddressSpace().equals(inclusive.getAddressSpace())) {
@@ -1877,58 +2030,28 @@ public final class ReaGhidraBridge extends HeadlessScript {
1877
2030
  return canonicalOffset(inclusive.getAddressSpace(), offset.add(BigInteger.ONE));
1878
2031
  }
1879
2032
 
1880
- private static String boundedMessage(String value) {
2033
+ private static String diagnosticMessage(String value) {
1881
2034
  if (value == null || value.isBlank()) {
1882
2035
  return "native decompiler returned no detail";
1883
2036
  }
1884
- return truncate(value.replaceAll("[\\r\\n]+", " "), 512).value;
1885
- }
1886
-
1887
- private static RequestFailure functionLimit(String message) {
1888
- return new RequestFailure("limit_exceeded", message);
2037
+ return value.replaceAll("[\\r\\n]+", " ");
1889
2038
  }
1890
2039
 
1891
- private JsonObject page(
1892
- List<InventoryItem> inventory,
1893
- int offset,
1894
- int limit,
1895
- String factsName) {
1896
- int start = Math.min(offset, inventory.size());
1897
- int end = Math.min(inventory.size(), start + limit);
1898
- JsonArray items = new JsonArray();
1899
- for (int index = start; index < end; index += 1) {
1900
- items.add(resultItem(inventory.get(index), factsName, MAX_LIST_VALUE_CODE_POINTS));
1901
- }
1902
- return pageResult(items, offset, limit, inventory.size());
1903
- }
1904
-
1905
- private static JsonObject pageResult(JsonArray items, int offset, int limit, int total) {
1906
- int next = offset + items.size();
1907
- boolean hasMore = next < total;
1908
- JsonObject result = new JsonObject();
1909
- result.add("items", items);
1910
- result.addProperty("offset", offset);
1911
- result.addProperty("limit", limit);
1912
- result.addProperty("total", total);
1913
- if (hasMore) {
1914
- result.addProperty("next_offset", next);
1915
- }
1916
- else {
1917
- result.add("next_offset", JsonNull.INSTANCE);
2040
+ private JsonArray inventory(
2041
+ List<InventoryItem> inventory,
2042
+ String factsName) throws Exception {
2043
+ JsonArray result = new JsonArray();
2044
+ for (InventoryItem item : inventory) {
2045
+ monitor.checkCancelled();
2046
+ result.add(resultItem(item, factsName));
1918
2047
  }
1919
- result.addProperty("has_more", hasMore);
1920
2048
  return result;
1921
2049
  }
1922
2050
 
1923
- private static JsonObject resultItem(
1924
- InventoryItem item,
1925
- String factsName,
1926
- int maximumCodePoints) {
1927
- TruncatedValue truncated = truncate(item.value, maximumCodePoints);
2051
+ private static JsonObject resultItem(InventoryItem item, String factsName) {
1928
2052
  JsonObject result = new JsonObject();
1929
2053
  result.addProperty("address", canonicalAddress(item.address));
1930
- result.addProperty("value", truncated.value);
1931
- result.addProperty("value_truncated", truncated.truncated);
2054
+ result.addProperty("value", item.value);
1932
2055
  if (factsName != null) {
1933
2056
  result.add(factsName, item.facts.deepCopy());
1934
2057
  }
@@ -2088,55 +2211,17 @@ public final class ReaGhidraBridge extends HeadlessScript {
2088
2211
  return source.name().toLowerCase(Locale.ROOT);
2089
2212
  }
2090
2213
 
2091
- private static void addBounded(List<InventoryItem> destination, InventoryItem item) {
2092
- if (destination.size() >= MAX_INVENTORY_ITEMS) {
2093
- throw inventoryLimit();
2094
- }
2095
- destination.add(item);
2096
- }
2097
-
2098
- private static RequestFailure inventoryLimit() {
2099
- return new RequestFailure(
2100
- "limit_exceeded",
2101
- "Ghidra inventory exceeds the 1000000-item safety limit"
2102
- );
2103
- }
2104
-
2105
- private static TruncatedValue truncate(String value, int maximumCodePoints) {
2106
- int codePoints = value.codePointCount(0, value.length());
2107
- if (codePoints <= maximumCodePoints) {
2108
- return new TruncatedValue(value, false);
2109
- }
2110
- int end = value.offsetByCodePoints(0, maximumCodePoints);
2111
- return new TruncatedValue(value.substring(0, end), true);
2112
- }
2113
-
2114
2214
  private static ValueMatcher literalMatcher(String pattern, boolean caseSensitive) {
2115
2215
  String needle = caseSensitive ? pattern : pattern.toLowerCase(Locale.ROOT);
2116
- return new ValueMatcher() {
2117
- private long remaining = MAX_LITERAL_SEARCH_WORK_UNITS;
2118
-
2119
- @Override
2120
- public boolean matches(String value) {
2121
- long required = (long) Math.max(value.length(), 1) + needle.length();
2122
- if (required > remaining) {
2123
- throw new RequestFailure(
2124
- "limit_exceeded",
2125
- "Literal search exceeds the 1000000-unit work budget"
2126
- );
2127
- }
2128
- remaining -= required;
2129
- String candidate = caseSensitive ? value : value.toLowerCase(Locale.ROOT);
2130
- return candidate.contains(needle);
2131
- }
2216
+ return value -> {
2217
+ String candidate = caseSensitive ? value : value.toLowerCase(Locale.ROOT);
2218
+ return candidate.contains(needle);
2132
2219
  };
2133
2220
  }
2134
2221
 
2135
2222
  private static ValueMatcher regexMatcher(String expression, boolean caseSensitive) {
2136
- RegexMetrics metrics;
2137
2223
  Pattern pattern;
2138
2224
  try {
2139
- metrics = new BoundedRegexParser(expression).parse();
2140
2225
  pattern = Pattern.compile(
2141
2226
  expression,
2142
2227
  caseSensitive ? 0 : Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE
@@ -2145,43 +2230,17 @@ public final class ReaGhidraBridge extends HeadlessScript {
2145
2230
  catch (PatternSyntaxException exception) {
2146
2231
  throw new RequestFailure("invalid_request", "Invalid regex pattern");
2147
2232
  }
2148
- long workPerCharacter = metrics.paths * Math.max(metrics.steps, 1);
2149
- return new ValueMatcher() {
2150
- private long remaining = MAX_REGEX_SEARCH_WORK_UNITS;
2151
-
2152
- @Override
2153
- public boolean matches(String value) {
2154
- if (value.length() > MAX_REGEX_CANDIDATE_CHARACTERS) {
2155
- throw new RequestFailure(
2156
- "limit_exceeded",
2157
- "Regex candidate exceeds the 4096-character safety limit"
2158
- );
2159
- }
2160
- long required = workPerCharacter * Math.max(value.length(), 1);
2161
- if (required > remaining) {
2162
- throw new RequestFailure(
2163
- "limit_exceeded",
2164
- "Regex search exceeds the 1000000-unit work budget"
2165
- );
2166
- }
2167
- remaining -= required;
2168
- return pattern.matcher(value).find();
2169
- }
2170
- };
2233
+ return value -> pattern.matcher(value).find();
2171
2234
  }
2172
2235
 
2173
2236
  private static SessionDescriptor readDescriptor(Path path) throws IOException {
2174
- if (!Files.isRegularFile(path, LinkOption.NOFOLLOW_LINKS) ||
2175
- Files.size(path) > MAX_DESCRIPTOR_BYTES) {
2237
+ if (!Files.isRegularFile(path, LinkOption.NOFOLLOW_LINKS)) {
2176
2238
  throw new IllegalArgumentException("REA session descriptor is invalid");
2177
2239
  }
2178
2240
  JsonObject object = requireObject(
2179
2241
  JsonParser.parseString(Files.readString(path, StandardCharsets.UTF_8)),
2180
2242
  DESCRIPTOR_KEYS
2181
2243
  );
2182
- if (requireInteger(object, "schema_version") != 2) {
2183
- throw new IllegalArgumentException("REA session descriptor version is invalid");
2184
- }
2185
2244
  return new SessionDescriptor(
2186
2245
  requireString(object, "transport"),
2187
2246
  requireString(object, "endpoint_path"),
@@ -2312,25 +2371,6 @@ public final class ReaGhidraBridge extends HeadlessScript {
2312
2371
  );
2313
2372
  }
2314
2373
 
2315
- private static String readBoundedLine(Reader reader, int maximumCharacters) throws IOException {
2316
- StringBuilder line = new StringBuilder();
2317
- while (true) {
2318
- int character = reader.read();
2319
- if (character < 0) {
2320
- return line.isEmpty() ? null : line.toString();
2321
- }
2322
- if (character == '\n') {
2323
- return line.toString();
2324
- }
2325
- if (character != '\r') {
2326
- if (line.length() >= maximumCharacters) {
2327
- throw new IOException("Bridge request exceeded the maximum line size");
2328
- }
2329
- line.append((char) character);
2330
- }
2331
- }
2332
- }
2333
-
2334
2374
  private static void writeSuccess(BufferedWriter writer, int id, JsonElement result)
2335
2375
  throws IOException {
2336
2376
  JsonObject response = new JsonObject();
@@ -2357,17 +2397,6 @@ public final class ReaGhidraBridge extends HeadlessScript {
2357
2397
 
2358
2398
  private static void writeResponse(BufferedWriter writer, JsonObject response) throws IOException {
2359
2399
  String encoded = GSON.toJson(response);
2360
- if (encoded.getBytes(StandardCharsets.UTF_8).length > MAX_RESPONSE_BYTES) {
2361
- int id = response.get("id").getAsInt();
2362
- JsonObject error = new JsonObject();
2363
- error.addProperty("code", "output_limit");
2364
- error.addProperty("message", "Ghidra response exceeds the 1 MiB wire limit");
2365
- JsonObject bounded = new JsonObject();
2366
- bounded.addProperty("id", id);
2367
- bounded.addProperty("ok", false);
2368
- bounded.add("error", error);
2369
- encoded = GSON.toJson(bounded);
2370
- }
2371
2400
  writer.write(encoded);
2372
2401
  writer.newLine();
2373
2402
  writer.flush();
@@ -2396,7 +2425,6 @@ public final class ReaGhidraBridge extends HeadlessScript {
2396
2425
  private record InventoryItem(Address address, String value, JsonObject facts) {}
2397
2426
  private record FunctionEntry(Function function, InventoryItem item) {}
2398
2427
  private record InstructionScan(List<Instruction> instructions, boolean truncated) {}
2399
- private record TruncatedValue(String value, boolean truncated) {}
2400
2428
  private record SessionDescriptor(
2401
2429
  String transport,
2402
2430
  String endpointPath,
@@ -2407,7 +2435,6 @@ public final class ReaGhidraBridge extends HeadlessScript {
2407
2435
  String profileDigest
2408
2436
  ) {}
2409
2437
  private record Request(int id, String method, JsonObject params) {}
2410
- private record RegexMetrics(long paths, long steps, boolean containsRepeat) {}
2411
2438
 
2412
2439
  private static final class RequestFailure extends RuntimeException {
2413
2440
  private final String code;
@@ -2418,263 +2445,4 @@ public final class ReaGhidraBridge extends HeadlessScript {
2418
2445
  }
2419
2446
  }
2420
2447
 
2421
- private static final class BoundedRegexParser {
2422
- private final String expression;
2423
- private int index;
2424
-
2425
- BoundedRegexParser(String expression) {
2426
- this.expression = expression;
2427
- }
2428
-
2429
- RegexMetrics parse() {
2430
- RegexMetrics result = parseExpression(false);
2431
- if (index != expression.length()) {
2432
- throw invalidRegex();
2433
- }
2434
- return result;
2435
- }
2436
-
2437
- private RegexMetrics parseExpression(boolean grouped) {
2438
- RegexMetrics result = parseSequence(grouped);
2439
- while (peek('|')) {
2440
- index += 1;
2441
- RegexMetrics branch = parseSequence(grouped);
2442
- result = new RegexMetrics(
2443
- checkedPaths(result.paths, branch.paths, false),
2444
- Math.max(result.steps, branch.steps),
2445
- result.containsRepeat || branch.containsRepeat
2446
- );
2447
- }
2448
- return result;
2449
- }
2450
-
2451
- private RegexMetrics parseSequence(boolean grouped) {
2452
- RegexMetrics result = new RegexMetrics(1, 0, false);
2453
- while (index < expression.length() && !peek('|') && !(grouped && peek(')'))) {
2454
- RegexMetrics item = parseAtom();
2455
- result = new RegexMetrics(
2456
- checkedPaths(result.paths, item.paths, true),
2457
- result.steps + item.steps,
2458
- result.containsRepeat || item.containsRepeat
2459
- );
2460
- }
2461
- return result;
2462
- }
2463
-
2464
- private RegexMetrics parseAtom() {
2465
- if (index >= expression.length()) {
2466
- throw invalidRegex();
2467
- }
2468
- char item = expression.charAt(index++);
2469
- RegexMetrics atom;
2470
- if (item == '(') {
2471
- if (peek('?')) {
2472
- if (index + 1 >= expression.length() || expression.charAt(index + 1) != ':') {
2473
- throw new RequestFailure(
2474
- "invalid_request",
2475
- "Regex lookarounds and backreferences are not supported"
2476
- );
2477
- }
2478
- index += 2;
2479
- }
2480
- atom = parseExpression(true);
2481
- if (!peek(')')) {
2482
- throw invalidRegex();
2483
- }
2484
- index += 1;
2485
- }
2486
- else if (item == '[') {
2487
- consumeCharacterClass();
2488
- atom = leaf();
2489
- }
2490
- else if (item == '\\') {
2491
- consumeEscape();
2492
- atom = leaf();
2493
- }
2494
- else if (item == '*' || item == '+') {
2495
- throw unboundedRepeat();
2496
- }
2497
- else if (item == '?' || item == '{' || item == '}' || item == ')') {
2498
- throw invalidRegex();
2499
- }
2500
- else {
2501
- atom = leaf();
2502
- }
2503
- if (index >= expression.length()) {
2504
- return atom;
2505
- }
2506
- if (peek('*') || peek('+')) {
2507
- throw unboundedRepeat();
2508
- }
2509
- int minimum;
2510
- int maximum;
2511
- if (peek('?')) {
2512
- index += 1;
2513
- minimum = 0;
2514
- maximum = 1;
2515
- }
2516
- else if (peek('{')) {
2517
- int[] bounds = consumeBounds();
2518
- minimum = bounds[0];
2519
- maximum = bounds[1];
2520
- }
2521
- else {
2522
- return atom;
2523
- }
2524
- if (atom.containsRepeat) {
2525
- throw new RequestFailure(
2526
- "invalid_request",
2527
- "Nested regex repetitions are not supported"
2528
- );
2529
- }
2530
- if (maximum > 1_000) {
2531
- throw unboundedRepeat();
2532
- }
2533
- return new RegexMetrics(
2534
- repeatPaths(atom.paths, minimum, maximum),
2535
- maximum * atom.steps,
2536
- true
2537
- );
2538
- }
2539
-
2540
- private void consumeCharacterClass() {
2541
- boolean escaped = false;
2542
- boolean hasContent = false;
2543
- while (index < expression.length()) {
2544
- char item = expression.charAt(index++);
2545
- if (escaped) {
2546
- escaped = false;
2547
- hasContent = true;
2548
- }
2549
- else if (item == '\\') {
2550
- escaped = true;
2551
- }
2552
- else if (item == ']' && hasContent) {
2553
- return;
2554
- }
2555
- else {
2556
- hasContent = true;
2557
- }
2558
- }
2559
- throw invalidRegex();
2560
- }
2561
-
2562
- private void consumeEscape() {
2563
- if (index >= expression.length()) {
2564
- throw invalidRegex();
2565
- }
2566
- char escaped = expression.charAt(index++);
2567
- if (Character.isDigit(escaped) || escaped == 'k') {
2568
- throw new RequestFailure(
2569
- "invalid_request",
2570
- "Regex lookarounds and backreferences are not supported"
2571
- );
2572
- }
2573
- if (escaped == 'Q' || escaped == 'E' || escaped == 'p' || escaped == 'P') {
2574
- throw new RequestFailure(
2575
- "invalid_request",
2576
- "Regex operation is not supported by the bounded matcher"
2577
- );
2578
- }
2579
- if (escaped == 'x') {
2580
- consumeHexDigits(2);
2581
- }
2582
- else if (escaped == 'u') {
2583
- consumeHexDigits(4);
2584
- }
2585
- }
2586
-
2587
- private void consumeHexDigits(int count) {
2588
- if (index + count > expression.length()) {
2589
- throw invalidRegex();
2590
- }
2591
- for (int current = 0; current < count; current += 1) {
2592
- if (Character.digit(expression.charAt(index + current), 16) < 0) {
2593
- throw invalidRegex();
2594
- }
2595
- }
2596
- index += count;
2597
- }
2598
-
2599
- private int[] consumeBounds() {
2600
- index += 1;
2601
- int minimum = consumeDecimal();
2602
- int maximum = minimum;
2603
- if (peek(',')) {
2604
- index += 1;
2605
- if (peek('}')) {
2606
- throw unboundedRepeat();
2607
- }
2608
- maximum = consumeDecimal();
2609
- }
2610
- if (!peek('}') || minimum > maximum) {
2611
- throw invalidRegex();
2612
- }
2613
- index += 1;
2614
- return new int[] { minimum, maximum };
2615
- }
2616
-
2617
- private int consumeDecimal() {
2618
- int start = index;
2619
- long value = 0;
2620
- while (index < expression.length() && Character.isDigit(expression.charAt(index))) {
2621
- value = value * 10 + Character.digit(expression.charAt(index), 10);
2622
- if (value > Integer.MAX_VALUE) {
2623
- throw unboundedRepeat();
2624
- }
2625
- index += 1;
2626
- }
2627
- if (index == start) {
2628
- throw invalidRegex();
2629
- }
2630
- return (int) value;
2631
- }
2632
-
2633
- private boolean peek(char expected) {
2634
- return index < expression.length() && expression.charAt(index) == expected;
2635
- }
2636
-
2637
- private static RegexMetrics leaf() {
2638
- return new RegexMetrics(1, 1, false);
2639
- }
2640
-
2641
- private static long repeatPaths(long childPaths, int minimum, int maximum) {
2642
- long result = 0;
2643
- long repeated = 1;
2644
- for (int count = 0; count <= maximum; count += 1) {
2645
- if (count >= minimum) {
2646
- result = checkedPaths(result, repeated, false);
2647
- }
2648
- if (count < maximum) {
2649
- repeated = checkedPaths(repeated, childPaths, true);
2650
- }
2651
- }
2652
- return result;
2653
- }
2654
-
2655
- private static long checkedPaths(long left, long right, boolean multiply) {
2656
- boolean exceeded = multiply
2657
- ? right != 0 && left > MAX_REGEX_BACKTRACKING_PATHS / right
2658
- : left > MAX_REGEX_BACKTRACKING_PATHS - right;
2659
- long result = multiply ? left * right : left + right;
2660
- if (exceeded || result > MAX_REGEX_BACKTRACKING_PATHS) {
2661
- throw new RequestFailure(
2662
- "invalid_request",
2663
- "Regex exceeds the 10000-path backtracking budget"
2664
- );
2665
- }
2666
- return result;
2667
- }
2668
-
2669
- private static RequestFailure unboundedRepeat() {
2670
- return new RequestFailure(
2671
- "invalid_request",
2672
- "Unbounded or excessive regex repetitions are not supported"
2673
- );
2674
- }
2675
-
2676
- private static RequestFailure invalidRegex() {
2677
- return new RequestFailure("invalid_request", "Invalid regex pattern");
2678
- }
2679
- }
2680
2448
  }