rea-agents 6.0.0 → 6.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (611) hide show
  1. package/README.md +26 -18
  2. package/bridge/android/ReaJadxBridge.java +14 -5
  3. package/bridge/ghidra/ReaGhidraBridge.java +205 -51
  4. package/bridge/hopper_bridge.py +314 -127
  5. package/bridge/mitmproxy/capture.py +0 -2
  6. package/bridge/native/rea_lldb_tracer.py +630 -94
  7. package/bridge/process/ProcessRunTokenReader.swift +39 -5
  8. package/bridge/pwntools/recorded_crash_impl.py +4 -0
  9. package/dist/android/JadxConfiguration.js +274 -31
  10. package/dist/android/JadxMcpTransport.js +18 -2
  11. package/dist/android/JadxProtocol.js +6 -0
  12. package/dist/android/JadxProvider.js +76 -37
  13. package/dist/android/JadxSession.js +23 -4
  14. package/dist/application/AnalysisContextQueries.js +8 -35
  15. package/dist/application/AnalysisDocument.js +32 -0
  16. package/dist/application/AppBundleExecutable.js +67 -21
  17. package/dist/application/BinaryTargetResolver.js +5 -3
  18. package/dist/application/BrowserEvidence.js +3 -8
  19. package/dist/application/BrowserScenarioEvidence.js +2 -18
  20. package/dist/application/CallPathTracing.js +19 -3
  21. package/dist/application/CapabilityInventory.js +48 -11
  22. package/dist/application/ClientConfigurationDocument.js +917 -8
  23. package/dist/application/ClientRegistrationIdentity.js +26 -5
  24. package/dist/application/ClientRegistrationStatus.js +29 -14
  25. package/dist/application/DirectAnalysis.js +56 -51
  26. package/dist/application/DirectAnalysisCleanup.js +72 -0
  27. package/dist/application/DirectAnalysisStatus.js +4 -5
  28. package/dist/application/Doctor.js +19 -21
  29. package/dist/application/DoctorDiagnostics.js +1 -2
  30. package/dist/application/DoctorScope.js +25 -13
  31. package/dist/application/EnhancedLiteralTracing.js +22 -25
  32. package/dist/application/EnhancedTools.js +64 -17
  33. package/dist/application/EvidenceBundleFiles.js +30 -13
  34. package/dist/application/FunctionAnalysisEvidence.js +2 -3
  35. package/dist/application/InventoryProjectionEvidence.js +4 -3
  36. package/dist/application/InvestigationProviders.js +11 -2
  37. package/dist/application/JsonFiles.js +83 -28
  38. package/dist/application/LinuxHopper.js +9 -6
  39. package/dist/application/MacHopper.js +15 -18
  40. package/dist/application/ProcedureAddressResolution.js +11 -0
  41. package/dist/application/RealHopperAssertions.js +0 -11
  42. package/dist/application/ReconstructionCoverageService.js +7 -4
  43. package/dist/application/ReconstructionObligationCandidates.js +1 -1
  44. package/dist/application/ReconstructionObligationLedgerService.js +4 -3
  45. package/dist/application/ReferenceSourceImportEntries.js +1 -1
  46. package/dist/application/Setup.js +21 -17
  47. package/dist/application/SetupClientConfiguration.js +10 -15
  48. package/dist/application/SetupHost.js +21 -29
  49. package/dist/application/SetupPlan.js +11 -9
  50. package/dist/application/SupportedClients.js +34 -1
  51. package/dist/application/Uninstall.js +82 -29
  52. package/dist/application/Update.js +18 -3
  53. package/dist/application/UpdateMaintenance.js +7 -5
  54. package/dist/application/UpdateRuntime.js +57 -36
  55. package/dist/application/WebModuleTraceService.js +4 -2
  56. package/dist/application/WebNetworkCaptureService.js +4 -2
  57. package/dist/application/WebRuntimeService.js +5 -3
  58. package/dist/application/WebScriptExportService.js +32 -6
  59. package/dist/application/WebSourceLocationService.js +4 -2
  60. package/dist/application/WorkflowEvidence.js +105 -0
  61. package/dist/application/XrefsAddressResolution.js +45 -0
  62. package/dist/application/analysisView/AnalysisViewService.js +76 -0
  63. package/dist/application/binary/AnalysisClientCleanup.js +30 -5
  64. package/dist/application/binary/AnalysisProviderEvaluation.js +22 -7
  65. package/dist/application/binary/AnalysisProviderRegistry.js +4 -6
  66. package/dist/application/binary/AnalysisSnapshotCache.js +13 -14
  67. package/dist/application/binary/BinarySession.js +161 -93
  68. package/dist/application/binary/BinarySessionComposition.js +1 -3
  69. package/dist/application/binary/BinarySessionExecution.js +3 -5
  70. package/dist/application/binary/BinarySessionOpen.js +4 -4
  71. package/dist/application/binary/BinarySessionRecords.js +26 -4
  72. package/dist/application/binary/BinarySessionStatus.js +18 -7
  73. package/dist/application/binary/CompositeProvider.js +16 -17
  74. package/dist/application/binary/LazyAnalysisProvider.js +8 -5
  75. package/dist/application/binary/SessionProviderRouter.js +27 -135
  76. package/dist/application/binaryDiagnostics/BinaryLayoutService.js +4 -2
  77. package/dist/application/binaryDiagnostics/RecordedCrashService.js +4 -2
  78. package/dist/application/evm/EvmInterfaceService.js +4 -2
  79. package/dist/application/homebrew.js +48 -0
  80. package/dist/application/investigation/EvidenceLedger.js +21 -7
  81. package/dist/application/investigation/InvestigationRecords.js +8 -0
  82. package/dist/application/javascript/ElectronActiveEvidence.js +9 -13
  83. package/dist/application/javascript/JavaScriptAnalysisControl.js +34 -0
  84. package/dist/application/javascript/JavaScriptApplicationEvidence.js +21 -4
  85. package/dist/application/javascript/JavaScriptApplicationEvidenceGraph.js +19 -2
  86. package/dist/application/javascript/JavaScriptApplicationService.js +27 -15
  87. package/dist/application/javascript/JavaScriptApplicationWorkflowService.js +5 -5
  88. package/dist/application/javascript/JavaScriptArtifactAnalysis.js +10 -7
  89. package/dist/application/javascript/JavaScriptArtifactGraphBuilder.js +44 -10
  90. package/dist/application/javascript/JavaScriptArtifactGraphBundlers.js +11 -3
  91. package/dist/application/javascript/JavaScriptArtifactGraphContext.js +9 -0
  92. package/dist/application/javascript/JavaScriptArtifactGraphDocuments.js +8 -5
  93. package/dist/application/javascript/JavaScriptArtifactGraphFindings.js +12 -3
  94. package/dist/application/javascript/JavaScriptArtifactGraphStructure.js +0 -1
  95. package/dist/application/javascript/JavaScriptArtifactPathResolution.js +49 -19
  96. package/dist/application/javascript/JavaScriptArtifactReconstruction.js +56 -8
  97. package/dist/application/javascript/JavaScriptModuleRelationships.js +38 -9
  98. package/dist/application/javascript/JavaScriptRecoveryService.js +4 -2
  99. package/dist/application/javascript/JavaScriptRuntimeObservationService.js +6 -4
  100. package/dist/application/javascript/JavaScriptRuntimeReconciliationService.js +3 -3
  101. package/dist/application/javascript/JavaScriptSemanticGraphAsyncProjection.js +2 -3
  102. package/dist/application/javascript/JavaScriptSemanticGraphBuilder.js +63 -61
  103. package/dist/application/javascript/JavaScriptSemanticGraphChildProcessProjection.js +1 -2
  104. package/dist/application/javascript/JavaScriptSemanticGraphDataProjection.js +2 -3
  105. package/dist/application/javascript/JavaScriptSemanticGraphFlowProjection.js +1 -5
  106. package/dist/application/javascript/JavaScriptSemanticGraphObjectProjection.js +75 -46
  107. package/dist/application/javascript/JavaScriptSemanticGraphResourceProjection.js +2 -3
  108. package/dist/application/javascript/JavaScriptSemanticGraphValueProjection.js +64 -19
  109. package/dist/application/managed/ManagedApplicationGraphService.js +4 -3
  110. package/dist/application/managed/ManagedMemberComparisonService.js +9 -5
  111. package/dist/application/managed/ManagedNativeVerificationService.js +6 -4
  112. package/dist/application/managed/ManagedReconstructionService.js +4 -3
  113. package/dist/application/process/ProcessCli.js +25 -17
  114. package/dist/application/process/ProcessEvidence.js +4 -4
  115. package/dist/application/workflowInputError.js +18 -0
  116. package/dist/artifacts/ArtifactProvider.js +48 -13
  117. package/dist/artifacts/ArtifactReader.js +31 -0
  118. package/dist/artifacts/DirectoryArtifactReader.js +2 -1
  119. package/dist/artifacts/MachOSliceArtifactReader.js +106 -15
  120. package/dist/artifacts/NativeDmgArtifactReader.js +73 -21
  121. package/dist/artifacts/SafeOutputTree.js +16 -2
  122. package/dist/artifacts/ZipArtifactReader.js +11 -4
  123. package/dist/artifacts/apple/AppleAssetCatalogAnalysis.js +26 -5
  124. package/dist/artifacts/apple/DylibResolutionReader.js +17 -7
  125. package/dist/artifacts/apple/InterfaceBuilderAnalysis.js +176 -56
  126. package/dist/artifacts/apple/InterfaceBuilderDecodeBudget.js +195 -0
  127. package/dist/artifacts/apple/KeyedArchiveReader.js +55 -23
  128. package/dist/artifacts/apple/MachoLoadCommandReader.js +92 -31
  129. package/dist/artifacts/apple/NibArchive.js +81 -14
  130. package/dist/artifacts/apple/NibViewHierarchy.js +10 -3
  131. package/dist/artifacts/apple/PlistNumberLiterals.js +164 -0
  132. package/dist/artifacts/extraction/ArtifactExtraction.js +78 -21
  133. package/dist/artifacts/inventory/ArtifactGraphConstruction.js +79 -68
  134. package/dist/artifacts/inventory/ArtifactInventory.js +0 -1
  135. package/dist/artifacts/inventory/reader.js +3 -3
  136. package/dist/artifacts/inventory/scanCanonical.js +59 -38
  137. package/dist/artifacts/inventory/scanReader.js +5 -9
  138. package/dist/artifacts/javascript/JavaScriptArtifactFiles.js +6 -2
  139. package/dist/browser/BrowserScenarioPartialObservation.js +106 -0
  140. package/dist/browser/CdpBrowserProvider.js +2 -3
  141. package/dist/browser/CdpCaptureCompleteness.js +2 -0
  142. package/dist/browser/CdpCaptureEventHandlers.js +78 -15
  143. package/dist/browser/CdpCaptureEventTypes.js +5 -1
  144. package/dist/browser/CdpCaptureEvents.js +25 -1
  145. package/dist/browser/CdpCaptureValues.js +20 -0
  146. package/dist/browser/CdpConnection.js +30 -11
  147. package/dist/browser/CdpElectronProvider.js +0 -2
  148. package/dist/browser/CdpElectronScriptEvents.js +2 -8
  149. package/dist/browser/CdpElectronScripts.js +2 -3
  150. package/dist/browser/CdpOptionalCommand.js +2 -3
  151. package/dist/browser/CdpPageCapture.js +23 -1
  152. package/dist/browser/CdpPageCaptureScripts.js +2 -3
  153. package/dist/browser/CdpSafeMetadata.js +10 -10
  154. package/dist/browser/PlaywrightBrowserScenarioProvider.js +59 -44
  155. package/dist/browser/PlaywrightElectronActiveActions.js +34 -79
  156. package/dist/browser/PlaywrightElectronActiveProjection.js +141 -0
  157. package/dist/browser/PlaywrightElectronActiveProvider.js +133 -130
  158. package/dist/browser/PlaywrightScenarioArtifacts.js +1 -1
  159. package/dist/browser/PlaywrightScenarioBrowser.js +139 -39
  160. package/dist/browser/PlaywrightScenarioEvents.js +1 -1
  161. package/dist/browser/PlaywrightScenarioSession.js +39 -22
  162. package/dist/browser/WebSourceMapFetcher.js +426 -72
  163. package/dist/browser/assets/LocalWebScriptArtifacts.js +2 -2
  164. package/dist/browser/assets/ScriptCaptureAdapters.js +1 -1
  165. package/dist/browser/history/CaptureFailures.js +41 -1
  166. package/dist/browser/history/CaptureRelease.js +5 -0
  167. package/dist/browser/history/HarCaptureAdapter.js +3 -3
  168. package/dist/browser/history/HarJson.js +3 -2
  169. package/dist/browser/history/HistoricalCaptureDecoder.js +4 -2
  170. package/dist/browser/history/MitmproxyCaptureAdapter.js +4 -2
  171. package/dist/catalogIdentity.js +0 -1
  172. package/dist/cli/analysisSelector.js +23 -0
  173. package/dist/cli/analysisViewCommands.js +36 -0
  174. package/dist/cli/applicationCommands.js +2 -3
  175. package/dist/cli/artifactCommands.js +11 -12
  176. package/dist/cli/coreAnalysisCommands.js +3 -3
  177. package/dist/cli/coreBinaryCommands.js +100 -37
  178. package/dist/cli/coreNativeCommands.js +9 -10
  179. package/dist/cli/electronCommands.js +10 -9
  180. package/dist/cli/interactiveSetup.js +6 -6
  181. package/dist/cli/javascriptApplicationAnalysis.js +7 -3
  182. package/dist/cli/jsonOutput.js +4 -99
  183. package/dist/cli/managedCommands.js +7 -8
  184. package/dist/cli/nativeCallCommands.js +3 -5
  185. package/dist/cli/omittableDefaults.js +50 -0
  186. package/dist/cli/processCommands.js +3 -2
  187. package/dist/cli/setupCommands.js +16 -15
  188. package/dist/cli/utilityCommands.js +9 -9
  189. package/dist/cli/webRuntimeCommands.js +8 -8
  190. package/dist/cli.js +18 -9
  191. package/dist/cliBrowserScenarioCommands.js +2 -2
  192. package/dist/cliCommandNames.js +1 -1
  193. package/dist/cliJsonInput.js +24 -5
  194. package/dist/cliOutput.js +0 -38
  195. package/dist/composition/auxiliaryAnalysisProviders.js +27 -23
  196. package/dist/composition/binary.js +9 -9
  197. package/dist/composition/browserScenario.js +2 -1
  198. package/dist/composition/directAnalysis.js +20 -13
  199. package/dist/composition/electronScenario.js +1 -1
  200. package/dist/composition/optionalObservationProviders.js +12 -7
  201. package/dist/config/environment.js +10 -0
  202. package/dist/config/ghidraStartupTimeout.js +12 -0
  203. package/dist/config/homeDirectory.js +12 -0
  204. package/dist/config/mcpResponseBudget.js +30 -0
  205. package/dist/config/parseConfig.js +8 -3
  206. package/dist/config/parsers.js +19 -22
  207. package/dist/contracts/analysisViewToolContracts.js +56 -0
  208. package/dist/contracts/applicationToolContracts.js +5 -4
  209. package/dist/contracts/artifactComparisonExample.js +2 -2
  210. package/dist/contracts/artifactToolContracts.js +1 -1
  211. package/dist/contracts/browserScenarioToolContracts.js +1 -1
  212. package/dist/contracts/browserToolContracts.js +19 -15
  213. package/dist/contracts/enhancedInputs.js +7 -5
  214. package/dist/contracts/enhancedToolContracts.js +7 -6
  215. package/dist/contracts/evm/evmToolContracts.js +1 -1
  216. package/dist/contracts/functionWorkflowToolContracts.js +1 -1
  217. package/dist/contracts/hopperMemoryContracts.js +2 -5
  218. package/dist/contracts/inputSchemaPresentation.js +90 -0
  219. package/dist/contracts/investigationExamples.js +4 -3
  220. package/dist/contracts/javascript/electronToolContracts.js +2 -2
  221. package/dist/contracts/javascript/javascriptApplicationWorkflowExamples.js +0 -20
  222. package/dist/contracts/javascript/javascriptExportShapeComparisonExample.js +245 -0
  223. package/dist/contracts/javascript/javascriptRuntimeObservationToolContracts.js +1 -1
  224. package/dist/contracts/managed/managedToolContracts.js +7 -14
  225. package/dist/contracts/managed/managedWorkflowToolContracts.js +1 -1
  226. package/dist/contracts/mobileApplicationGraphExamples.js +3 -3
  227. package/dist/contracts/native/binaryDiagnosticsToolContracts.js +1 -1
  228. package/dist/contracts/native/nativeToolContracts.js +5 -3
  229. package/dist/contracts/officialToolContracts.js +16 -16
  230. package/dist/contracts/promptContracts.js +5 -5
  231. package/dist/contracts/providerSelection.js +1 -1
  232. package/dist/contracts/sessionLifecycleInputs.js +3 -7
  233. package/dist/contracts/sessionStatusContract.js +2 -4
  234. package/dist/contracts/sessionToolContracts.js +18 -8
  235. package/dist/contracts/sessionToolSchemas.js +3 -19
  236. package/dist/contracts/toolContractExamples.js +0 -1
  237. package/dist/contracts/toolContractHelpers.js +4 -1
  238. package/dist/contracts/toolContracts.js +2 -0
  239. package/dist/contracts/toolEffects.js +1 -1
  240. package/dist/contracts/toolOutputSchemaGroups.js +160 -115
  241. package/dist/contracts/toolOutputSchemaPrimitives.js +28 -20
  242. package/dist/contracts/toolSchemaMetadata.js +41 -33
  243. package/dist/contracts/webNetworkCaptureToolContracts.js +2 -2
  244. package/dist/contracts/webRuntimeToolContracts.js +1 -1
  245. package/dist/contracts/webScriptToolContracts.js +1 -1
  246. package/dist/doctorRuntime.js +7 -5
  247. package/dist/domain/analysisErrorBase.js +8 -3
  248. package/dist/domain/analysisErrorCore.js +6 -1
  249. package/dist/domain/analysisErrorPresentation.js +90 -20
  250. package/dist/domain/analysisErrorProjection.js +130 -32
  251. package/dist/domain/analysisSnapshot.js +29 -31
  252. package/dist/domain/analysisView/analysisView.js +243 -0
  253. package/dist/domain/analysisView/analysisViewCoverage.js +19 -0
  254. package/dist/domain/analysisView/binaryLayoutView.js +159 -0
  255. package/dist/domain/analysisView/javascriptApplicationView.js +238 -0
  256. package/dist/domain/android/androidAnalysis.js +2 -4
  257. package/dist/domain/android/androidApplication.js +18 -10
  258. package/dist/domain/apple/appleApplication.js +42 -15
  259. package/dist/domain/apple/dyldEnvironment.js +73 -0
  260. package/dist/domain/apple/dylibResolution.js +130 -12
  261. package/dist/domain/apple/dylibResolutionFindings.js +9 -7
  262. package/dist/domain/apple/interfaceBuilderGraph.js +25 -14
  263. package/dist/domain/apple/interfaceBuilderKeyedArchive.js +13 -3
  264. package/dist/domain/apple/keyedArchive.js +35 -3
  265. package/dist/domain/apple/machoContainer.js +54 -0
  266. package/dist/domain/apple/plistNumbers.js +76 -0
  267. package/dist/domain/artifactComparison.js +15 -6
  268. package/dist/domain/artifactGraph.js +16 -3
  269. package/dist/domain/artifactIdentity.js +29 -0
  270. package/dist/domain/artifactInventoryEvidence.js +28 -40
  271. package/dist/domain/artifactOperationError.js +3 -3
  272. package/dist/domain/artifactPartialObservation.js +1 -0
  273. package/dist/domain/artifactPathSyntax.js +49 -0
  274. package/dist/domain/binaryTarget.js +39 -13
  275. package/dist/domain/bridgeCandidateProjection.js +71 -0
  276. package/dist/domain/browserCaptureComparison.js +13 -49
  277. package/dist/domain/browserCompleteness.js +1 -0
  278. package/dist/domain/browserNetworkEvidence.js +10 -4
  279. package/dist/domain/browserObservation.js +0 -1
  280. package/dist/domain/browserObservationError.js +24 -5
  281. package/dist/domain/browserObservationSchemas.js +14 -3
  282. package/dist/domain/browserScenario.js +0 -1
  283. package/dist/domain/browserScenarioCapture.js +1 -2
  284. package/dist/domain/browserScenarioDiff.js +0 -2
  285. package/dist/domain/browserScenarioDiffEvidence.js +0 -12
  286. package/dist/domain/browserScenarioDiffValues.js +2 -2
  287. package/dist/domain/browserScenarioValues.js +5 -10
  288. package/dist/domain/bundleComparison.js +3 -2
  289. package/dist/domain/callPath.js +8 -9
  290. package/dist/domain/callPathSchemas.js +2 -2
  291. package/dist/domain/canonicalJsonDigestSteps.js +133 -0
  292. package/dist/domain/canonicalOrdering.js +9 -5
  293. package/dist/domain/changedBehavior.js +9 -9
  294. package/dist/domain/comparisonSourceEvidence.js +40 -0
  295. package/dist/domain/completionLedgerGeneration.js +2 -1
  296. package/dist/domain/configurationErrors.js +13 -1
  297. package/dist/domain/conformancePackage.js +4 -3
  298. package/dist/domain/evidence.js +65 -36
  299. package/dist/domain/evidenceBundle.js +47 -8
  300. package/dist/domain/evidenceCompletionLedger.js +2 -1
  301. package/dist/domain/evidenceErrors.js +12 -1
  302. package/dist/domain/explicitSensitiveFailure.js +17 -1
  303. package/dist/domain/firmware/firmwareAnalysis.js +6 -7
  304. package/dist/domain/functionComparison.js +4 -5
  305. package/dist/domain/functionComparisonDimensions.js +120 -87
  306. package/dist/domain/functionComparisonNormalization.js +23 -34
  307. package/dist/domain/functionComparisonResults.js +1 -4
  308. package/dist/domain/functionComparisonSchemas.js +2 -8
  309. package/dist/domain/functionDossierEvidence.js +2 -37
  310. package/dist/domain/hopperErrors.js +23 -2
  311. package/dist/domain/hopperValues.js +51 -8
  312. package/dist/domain/immutableJson.js +114 -0
  313. package/dist/domain/inputIssueProjection.js +3 -0
  314. package/dist/domain/javascript/electronActiveObservation.js +47 -64
  315. package/dist/domain/javascript/electronObservation.js +14 -3
  316. package/dist/domain/javascript/electronStaticAnalysisBrowser.js +10 -9
  317. package/dist/domain/javascript/electronStaticAnalysisIpc.js +2 -1
  318. package/dist/domain/javascript/electronStaticAnalysisNative.js +2 -1
  319. package/dist/domain/javascript/electronStaticAnalysisValues.js +6 -12
  320. package/dist/domain/javascript/javascriptApplicationAnalysis.js +71 -23
  321. package/dist/domain/javascript/javascriptApplicationEvidenceSchemas.js +2 -0
  322. package/dist/domain/javascript/javascriptApplicationGraph.js +39 -3
  323. package/dist/domain/javascript/javascriptApplicationGraphSchemas.js +9 -2
  324. package/dist/domain/javascript/javascriptApplicationVersionItems.js +5 -2
  325. package/dist/domain/javascript/javascriptApplicationVersionKeys.js +2 -1
  326. package/dist/domain/javascript/javascriptAstValues.js +20 -0
  327. package/dist/{application/javascript/JavaScriptReturnShapeProjection.js → domain/javascript/javascriptExportReturnShapeProjection.js} +4 -4
  328. package/dist/domain/javascript/javascriptExportShapeComparison.js +7 -1
  329. package/dist/domain/javascript/javascriptExportShapeComparisonIdentity.js +6 -1
  330. package/dist/domain/javascript/javascriptExportShapeComparisonSchemas.js +29 -1
  331. package/dist/domain/javascript/javascriptExportShapeSelection.js +5 -5
  332. package/dist/domain/javascript/javascriptExportShapeVariants.js +67 -14
  333. package/dist/domain/javascript/javascriptFeatureSeed.js +20 -0
  334. package/dist/domain/javascript/javascriptFeatureTraversal.js +0 -1
  335. package/dist/domain/javascript/javascriptNativeHandoff.js +1 -1
  336. package/dist/domain/javascript/javascriptRecovery.js +3 -8
  337. package/dist/domain/javascript/javascriptRuntimeLoadState.js +0 -1
  338. package/dist/domain/javascript/javascriptRuntimeObservation.js +10 -2
  339. package/dist/domain/javascript/javascriptRuntimeReconciliationMatching.js +1 -1
  340. package/dist/domain/javascript/javascriptRuntimeReconciliationParsing.js +3 -2
  341. package/dist/domain/javascript/javascriptRuntimeStaticCandidates.js +0 -1
  342. package/dist/domain/javascript/javascriptSemanticAnalysis.js +106 -5
  343. package/dist/domain/javascript/javascriptSemanticAsyncEffects.js +2 -1
  344. package/dist/domain/javascript/javascriptSemanticCalls.js +2 -1
  345. package/dist/domain/javascript/javascriptSemanticChildProcesses.js +2 -1
  346. package/dist/domain/javascript/javascriptSemanticCoverage.js +6 -5
  347. package/dist/domain/javascript/javascriptSemanticDataEffectHelpers.js +9 -17
  348. package/dist/domain/javascript/javascriptSemanticDataEffects.js +23 -6
  349. package/dist/domain/javascript/javascriptSemanticGraph.js +98 -43
  350. package/dist/domain/javascript/javascriptSemanticGraphSchemas.js +1 -0
  351. package/dist/domain/javascript/javascriptSemanticMemberMutations.js +77 -20
  352. package/dist/domain/javascript/javascriptSemanticMutationValues.js +21 -27
  353. package/dist/domain/javascript/javascriptSemanticObjects.js +83 -42
  354. package/dist/domain/javascript/javascriptSemanticPrimitives.js +18 -21
  355. package/dist/domain/javascript/javascriptSemanticProjection.js +2 -3
  356. package/dist/domain/javascript/javascriptSemanticPromises.js +2 -1
  357. package/dist/domain/javascript/javascriptSemanticQuerySchemas.js +8 -4
  358. package/dist/domain/javascript/javascriptSemanticResourceLimits.js +77 -0
  359. package/dist/domain/javascript/javascriptSemanticResources.js +1 -1
  360. package/dist/domain/javascript/javascriptSemanticReturns.js +32 -17
  361. package/dist/domain/javascript/javascriptSemanticSlots.js +72 -0
  362. package/dist/domain/javascript/javascriptSemanticValues.js +126 -61
  363. package/dist/domain/javascript/javascriptSourceMapPaths.js +104 -0
  364. package/dist/domain/javascript/javascriptSourceParser.js +6 -2
  365. package/dist/domain/javascript/javascriptStaticAnalysis.js +22 -17
  366. package/dist/domain/javascript/javascriptStaticAnalysisBundler.js +5 -3
  367. package/dist/domain/javascript/javascriptStaticAnalysisCalls.js +16 -5
  368. package/dist/domain/javascript/javascriptStaticAnalysisFindings.js +66 -7
  369. package/dist/domain/javascript/javascriptStaticAnalysisHelpers.js +28 -17
  370. package/dist/domain/javascript/javascriptStaticAnalysisState.js +3 -2
  371. package/dist/domain/javascript/sourceToBundleComparison.js +12 -3
  372. package/dist/domain/javascript/sourceToBundleSignals.js +47 -19
  373. package/dist/domain/jsonSerialization.js +109 -0
  374. package/dist/domain/jsonValue.js +90 -6
  375. package/dist/domain/localPath.js +6 -0
  376. package/dist/domain/managed/managedApplicationGraph.js +10 -10
  377. package/dist/domain/managed/managedArtifact.js +6 -4
  378. package/dist/domain/managed/managedInspectionEvidence.js +16 -0
  379. package/dist/domain/managed/managedMemberComparison.js +2 -9
  380. package/dist/domain/managed/managedMemberComparisonMatch.js +5 -9
  381. package/dist/domain/managed/managedNativeVerification.js +3 -11
  382. package/dist/domain/managed/managedNativeVerificationMatch.js +9 -3
  383. package/dist/domain/native/binaryLayout.js +2 -3
  384. package/dist/domain/native/fileOffsetPartialObservation.js +1 -0
  385. package/dist/domain/native/nativeApiBoundary.js +1 -1
  386. package/dist/domain/native/nativeCallObservation.js +37 -13
  387. package/dist/domain/native/nativeCallPartialObservation.js +33 -0
  388. package/dist/domain/native/nativeInstruction.js +7 -0
  389. package/dist/domain/native/recordedCrash.js +2 -3
  390. package/dist/domain/offsetDateTime.js +17 -0
  391. package/dist/domain/process/processCapture.js +21 -16
  392. package/dist/domain/process/processCaptureCoverage.js +67 -0
  393. package/dist/domain/process/processCaptureExample.js +2 -1
  394. package/dist/domain/process/processCaptureParsing.js +1 -34
  395. package/dist/domain/process/processCaptureValidation.js +81 -13
  396. package/dist/domain/process/processComparison.js +79 -26
  397. package/dist/domain/process/processObservation.js +10 -2
  398. package/dist/domain/process/processScenario.js +35 -12
  399. package/dist/domain/process/processTraceComparison.js +0 -2
  400. package/dist/domain/process/processTraceDimensionProjection.js +1 -1
  401. package/dist/domain/process/processTraceEvaluation.js +15 -11
  402. package/dist/domain/process/processTraceSpecification.js +3 -4
  403. package/dist/domain/propertyListKeys.js +1 -1
  404. package/dist/domain/propertyListXmlText.js +19 -0
  405. package/dist/domain/providerAdapterError.js +3 -1
  406. package/dist/domain/providerOperationHealth.js +54 -0
  407. package/dist/domain/reconstructionCoverage.js +5 -2
  408. package/dist/domain/reconstructionObligationLedgerSchemas.js +2 -1
  409. package/dist/domain/reconstructionReadinessSchemas.js +2 -1
  410. package/dist/domain/reconstructionVerification.js +9 -46
  411. package/dist/domain/reconstructionVerificationSchemas.js +4 -3
  412. package/dist/domain/referenceSourceGraph.js +20 -21
  413. package/dist/domain/staticRuntimeCorrelation.js +8 -45
  414. package/dist/domain/stringPatterns.js +6 -0
  415. package/dist/domain/swiftSymbolClassification.js +61 -0
  416. package/dist/domain/symbolAnalysis.js +66 -18
  417. package/dist/domain/webBundleAnalysis.js +20 -8
  418. package/dist/domain/webBundleAnalyzerInspection.js +13 -8
  419. package/dist/domain/webBundleAnalyzerResult.js +41 -2
  420. package/dist/domain/webCaptureDiff.js +0 -1
  421. package/dist/domain/webCaptureDiffSchemas.js +6 -3
  422. package/dist/domain/webEventListeners.js +3 -2
  423. package/dist/domain/webExecution.js +3 -2
  424. package/dist/domain/webRuntime.js +19 -7
  425. package/dist/domain/webScriptExport.js +1 -1
  426. package/dist/domain/webSourceLocation.js +1 -0
  427. package/dist/dotnet/ManagedArtifactInspector.js +2 -19
  428. package/dist/dotnet/ManagedMemberInspector.js +12 -18
  429. package/dist/dotnet/ManagedMemberInspectorCore.js +47 -44
  430. package/dist/dotnet/ManagedMemberRows.js +75 -62
  431. package/dist/dotnet/ManagedMetadataInventory.js +16 -22
  432. package/dist/dotnet/ManagedMethodBodyReader.js +4 -0
  433. package/dist/dotnet/ManagedNativeBoundaryHelpers.js +109 -94
  434. package/dist/dotnet/ManagedNativeBoundaryInspector.js +12 -21
  435. package/dist/dotnet/ManagedReaderFailure.js +16 -0
  436. package/dist/dotnet/ManagedStaticProvider.js +3 -5
  437. package/dist/evaluation/AgentEvaluationFixtures.js +250 -0
  438. package/dist/evaluation/AgentEvaluationReport.js +28 -0
  439. package/dist/evaluation/CodexAgentEval.js +12 -8
  440. package/dist/evaluation/KnownAnswerEvaluation.js +281 -0
  441. package/dist/evm/EvmoleInterfaceProvider.js +2 -1
  442. package/dist/firmware/FirmwareCommand.js +9 -5
  443. package/dist/firmware/FirmwareFiles.js +1 -1
  444. package/dist/firmware/FirmwareProvider.js +4 -4
  445. package/dist/generatedPackageMetadata.js +2 -2
  446. package/dist/ghidra/GhidraAnalysisProfile.js +7 -5
  447. package/dist/ghidra/GhidraClient.js +126 -34
  448. package/dist/ghidra/GhidraClientStartup.js +0 -2
  449. package/dist/ghidra/GhidraDiagnostics.js +7 -7
  450. package/dist/ghidra/GhidraDoctor.js +6 -5
  451. package/dist/ghidra/GhidraFunctionValues.js +13 -12
  452. package/dist/ghidra/GhidraInstallation.js +58 -33
  453. package/dist/ghidra/GhidraInventoryValues.js +14 -4
  454. package/dist/ghidra/GhidraJavaLaunch.js +66 -0
  455. package/dist/ghidra/GhidraLauncher.js +47 -22
  456. package/dist/ghidra/GhidraProvider.js +6 -2
  457. package/dist/ghidra/GhidraProviderCapabilities.js +20 -2
  458. package/dist/ghidra/GhidraProviderClient.js +36 -15
  459. package/dist/ghidra/GhidraSessionError.js +13 -7
  460. package/dist/ghidra/GhidraSessionRoot.js +39 -0
  461. package/dist/ghidra/GhidraTargetSnapshot.js +47 -12
  462. package/dist/ghidra/GhidraTransport.js +20 -0
  463. package/dist/ghidra/extensions/GhidraExtensions.js +17 -10
  464. package/dist/ghidra/extensions/nativeaot/NativeAotAdapter.js +8 -2
  465. package/dist/hopper/BridgeLauncher.js +65 -56
  466. package/dist/hopper/HopperAnalysisProfile.js +65 -12
  467. package/dist/hopper/HopperCleanup.js +63 -22
  468. package/dist/hopper/HopperClient.js +288 -59
  469. package/dist/hopper/HopperFileOffset.js +237 -0
  470. package/dist/hopper/HopperMachOImage.js +197 -0
  471. package/dist/hopper/HopperOperationStage.js +6 -0
  472. package/dist/hopper/HopperProvider.js +71 -19
  473. package/dist/hopper/HopperProviderCapabilities.js +19 -1
  474. package/dist/hopper/HopperRegexSearch.js +134 -0
  475. package/dist/hopper/HopperRegexWorker.js +27 -0
  476. package/dist/hopper/HopperRequestQueue.js +68 -19
  477. package/dist/hopper/HopperTargetLease.js +133 -49
  478. package/dist/hopper/LinuxPrivateDisplayProbe.js +6 -0
  479. package/dist/ida/IdaConfiguration.js +8 -3
  480. package/dist/ida/IdaInput.js +1 -7
  481. package/dist/ida/IdaMcpConnection.js +8 -4
  482. package/dist/ida/IdaOperationRunner.js +3 -3
  483. package/dist/ida/IdaProvider.js +8 -12
  484. package/dist/ida/IdaSessionClient.js +20 -18
  485. package/dist/ida/IdaWorkspace.js +12 -13
  486. package/dist/inspector/JavaScriptRuntimeScope.js +9 -1
  487. package/dist/inspector/V8InspectorCaptureProjection.js +9 -4
  488. package/dist/inspector/V8InspectorProvider.js +82 -36
  489. package/dist/javascript/recovery/WakaruCommand.js +7 -2
  490. package/dist/javascript/recovery/WakaruExecution.js +5 -2
  491. package/dist/javascript/sourceMaps/DecodedSourceMap.js +47 -0
  492. package/dist/javascript/sourceMaps/SourceMapCommand.js +16 -0
  493. package/dist/javascript/sourceMaps/SourceMapFormat.js +79 -31
  494. package/dist/javascript/sourceMaps/TraceSourceMap.js +14 -28
  495. package/dist/main/shutdown.js +11 -3
  496. package/dist/main/startup.js +9 -2
  497. package/dist/main/transport.js +22 -2
  498. package/dist/main.js +11 -18
  499. package/dist/mcpDoctor.js +62 -2
  500. package/dist/native/AppleDispatchCoverage.js +47 -6
  501. package/dist/native/AppleDispatchDecodeFacts.js +6 -0
  502. package/dist/native/AppleDispatchMetadata.js +6 -3
  503. package/dist/native/AppleMachoFixups.js +48 -24
  504. package/dist/native/AppleMachoSelection.js +48 -22
  505. package/dist/native/AppleObjcCategories.js +6 -2
  506. package/dist/native/AppleObjcDispatchFacets.js +5 -2
  507. package/dist/native/AppleObjcProperties.js +2 -1
  508. package/dist/native/AppleSwiftDispatchFacets.js +27 -21
  509. package/dist/native/AppleSwiftVtables.js +21 -15
  510. package/dist/native/CommandRunner.js +87 -88
  511. package/dist/native/LldbCallTracer.js +372 -103
  512. package/dist/native/LldbRetainedObservations.js +178 -0
  513. package/dist/native/NativeCallObservation.js +62 -3
  514. package/dist/native/NativeMacOSProvider.js +122 -31
  515. package/dist/native/NativeUiHelperRuntime.js +11 -2
  516. package/dist/native/NativeUiObservation.js +2 -2
  517. package/dist/native/SignatureTargetBinding.js +155 -0
  518. package/dist/native/parsers/lipo.js +71 -1
  519. package/dist/native/pwndbg/PwndbgCoreContext.js +25 -5
  520. package/dist/native/pwntools/PwntoolsDecoder.js +2 -1
  521. package/dist/native/pwntools/RecordedCrashStructureBindings.js +2 -0
  522. package/dist/process/DarwinProcessRunTokenReader.js +23 -10
  523. package/dist/process/ExecFileOutput.js +108 -6
  524. package/dist/process/OwnedCommand.js +7 -2
  525. package/dist/process/PrivateRuntimeRoot.js +10 -5
  526. package/dist/process/ProcessOwnership.js +204 -57
  527. package/dist/process/ProcessOwnershipObservation.js +60 -9
  528. package/dist/process/ProviderProcess.js +39 -10
  529. package/dist/process/capture/FilesystemSnapshot.js +50 -13
  530. package/dist/process/capture/ProcessCaptureError.js +15 -2
  531. package/dist/process/capture/ProcessCaptureLifecycle.js +235 -81
  532. package/dist/process/capture/ProcessFilesystemEffects.js +33 -3
  533. package/dist/process/capture/ProcessHarness.js +21 -25
  534. package/dist/process/capture/ProcessNormalization.js +20 -2
  535. package/dist/process/capture/ProcessSampling.js +25 -2
  536. package/dist/process/capture/TerminalRenderer.js +14 -0
  537. package/dist/process/snapshotEnvironment.js +14 -0
  538. package/dist/server/EvidenceMcpServer.js +157 -0
  539. package/dist/server/createServer.js +61 -41
  540. package/dist/server/mcpClientMetadata.js +12 -5
  541. package/dist/server/promptCompletion.js +3 -8
  542. package/dist/server/registerAnalysisViewTool.js +15 -0
  543. package/dist/server/registerAndroidTools.js +2 -5
  544. package/dist/server/registerApplicationTools/compareExportShapes.js +4 -5
  545. package/dist/server/registerApplicationTools/compareSourceToBundle.js +4 -5
  546. package/dist/server/registerApplicationTools/compareVersions.js +4 -5
  547. package/dist/server/registerApplicationTools/coverage.js +1 -2
  548. package/dist/server/registerApplicationTools/helpers.js +1 -4
  549. package/dist/server/registerApplicationTools/obligationLedger.js +4 -5
  550. package/dist/server/registerApplicationTools/projectMobileApplicationGraph.js +3 -4
  551. package/dist/server/registerApplicationTools/traceFeature.js +4 -5
  552. package/dist/server/registerApplicationTools/traceSemantics.js +4 -5
  553. package/dist/server/registerArtifactComparisonTool.js +13 -10
  554. package/dist/server/registerBinaryDiagnosticsTools.js +2 -5
  555. package/dist/server/registerBrowserScenarioTool.js +3 -6
  556. package/dist/server/registerBrowserTools.js +13 -18
  557. package/dist/server/registerBundleComparisonTool.js +5 -7
  558. package/dist/server/registerCloseLifecycleTool.js +9 -20
  559. package/dist/server/registerElectronTools.js +10 -15
  560. package/dist/server/registerEnhancedTools.js +36 -77
  561. package/dist/server/registerEvidenceTools.js +3 -6
  562. package/dist/server/registerEvmTools.js +2 -5
  563. package/dist/server/registerFirmwareTools.js +2 -5
  564. package/dist/server/registerFunctionComparisonTool.js +15 -12
  565. package/dist/server/registerInvestigationTools.js +16 -17
  566. package/dist/server/registerJavaScriptRecoveryTool.js +2 -5
  567. package/dist/server/registerJavaScriptRuntimeObservationTools.js +6 -8
  568. package/dist/server/registerManagedWorkflowTools/compareManagedMembers.js +5 -8
  569. package/dist/server/registerManagedWorkflowTools/importManagedReconstruction.js +4 -7
  570. package/dist/server/registerManagedWorkflowTools/projectManagedApplicationGraph.js +6 -9
  571. package/dist/server/registerManagedWorkflowTools/verifyManagedNativeBoundaries.js +6 -9
  572. package/dist/server/registerOfficialTools.js +4 -21
  573. package/dist/server/registerProcessComparisonTool.js +74 -48
  574. package/dist/server/registerRecordedCrashTools.js +2 -5
  575. package/dist/server/registerSessionRecordTools.js +19 -24
  576. package/dist/server/registerSessionStatusTool.js +19 -6
  577. package/dist/server/registerSessionTools.js +20 -18
  578. package/dist/server/registerWebModuleTool.js +2 -5
  579. package/dist/server/registerWebNetworkCaptureTool.js +2 -5
  580. package/dist/server/registerWebRuntimeTools.js +4 -9
  581. package/dist/server/registerWebScriptTool.js +2 -5
  582. package/dist/server/registerWebSourceLocationTool.js +2 -5
  583. package/dist/server/runDerivedOperation.js +15 -3
  584. package/dist/server/sessionAvailabilityPolicy.js +0 -1
  585. package/dist/server/toolRegistrationOptions.js +2 -2
  586. package/dist/server/toolResult.js +75 -51
  587. package/dist/server/toolResultEncoding.js +34 -0
  588. package/dist/windows/WindowsNativeLoader.js +1 -0
  589. package/dist/windows/WindowsOwnedProcess.js +60 -15
  590. package/dist/windows/WindowsPrivateRuntime.js +7 -8
  591. package/install.sh +1 -1
  592. package/native/windows/README.md +3 -1
  593. package/native/windows/build/manifest.json +2 -2
  594. package/native/windows/build/rea-windows-x64.node +0 -0
  595. package/package.json +44 -30
  596. package/scripts/electron-active-hook-boundaries.cjs +18 -20
  597. package/scripts/electron-active-hook.cjs +241 -37
  598. package/scripts/rea.mjs +3 -6
  599. package/skills/reverse-engineer-anything/SKILL.md +12 -3
  600. package/skills/reverse-engineer-anything/references/android-applications.md +6 -3
  601. package/skills/reverse-engineer-anything/references/evidence-workflows.md +8 -4
  602. package/skills/reverse-engineer-anything/references/javascript-applications.md +20 -3
  603. package/skills/reverse-engineer-anything/references/native-and-artifacts.md +9 -0
  604. package/dist/config.js +0 -1
  605. package/dist/contracts/browserCaptureToolInputSchema.js +0 -43
  606. package/dist/contracts/process/processCaptureExample.js +0 -2
  607. package/dist/contracts/toolOutputSchemas.js +0 -2
  608. package/dist/ghidra/GhidraDefaults.js +0 -2
  609. package/dist/main/reload.js +0 -12
  610. package/dist/main/state.js +0 -3
  611. package/dist/server/sessionToolPolicies.js +0 -2
@@ -8,12 +8,12 @@ API access on this thread: moving dispatch to a worker can deadlock Hopper.
8
8
  import json
9
9
  import hmac
10
10
  import os
11
- import re
12
11
  import socket
13
12
  from typing import Any, Optional, Protocol, Sequence
14
13
 
15
14
  BAD_ADDRESSES = (-1, 0xFFFFFFFFFFFFFFFF, None)
16
- _selected_document = None
15
+ # The native label setter truncates NSString names at this UTF-16 extent.
16
+ HOPPER_SYMBOL_NAME_UTF16_UNITS = 1024
17
17
  _rea_session_document = None
18
18
  _search_inventory_cache = {}
19
19
  _pseudocode_cache = {}
@@ -89,9 +89,11 @@ def _session_document():
89
89
  global _rea_session_document
90
90
  documents = _api().documents()
91
91
  if _rea_session_document is not None:
92
+ # Hopper recreates Python wrappers; equality compares native handles.
93
+ # Retain our bound wrapper so cache identity stays stable across calls.
92
94
  return (
93
95
  _rea_session_document
94
- if any(document is _rea_session_document for document in documents)
96
+ if any(document == _rea_session_document for document in documents)
95
97
  else None
96
98
  )
97
99
  return _bind_session_document(documents)
@@ -174,46 +176,89 @@ def _json_safe(value):
174
176
 
175
177
 
176
178
  def _document(name=None):
177
- """Resolve an explicit or session-selected document without changing Hopper UI."""
178
- global _selected_document
179
- api = _api()
180
- documents = api.documents()
179
+ """Keep observations and mutations bound to the active REA target."""
180
+ documents = _api().documents()
181
+ session_document = _session_document()
182
+ if session_document is None:
183
+ raise CapabilityUnavailableError("The active REA target's Hopper document is no longer open; reopen the target with open_binary")
181
184
  if name is not None:
182
- matches = [candidate for candidate in documents if candidate.getDocumentName() == name]
183
- if len(matches) == 1:
184
- return matches[0]
185
- if len(matches) > 1:
186
- current = api.current_document()
187
- if current is not None and current.getDocumentName() == name:
188
- return current
189
- raise InvalidRequestError("Hopper document name is ambiguous")
185
+ # A document name is display metadata; scope it to the bound native
186
+ # handle even when another open target happens to have the same name.
187
+ if session_document.getDocumentName() == name:
188
+ return session_document
189
+ if any(candidate.getDocumentName() == name for candidate in documents):
190
+ raise InvalidRequestError("Hopper document belongs to another target; use open_binary to select that target")
190
191
  raise InvalidRequestError("Unknown Hopper document")
191
- if _selected_document is not None:
192
- for candidate in documents:
193
- if candidate.getDocumentName() == _selected_document:
194
- return candidate
195
- session_document = _session_document()
196
- if session_document is not None:
197
- return session_document
198
- current = api.current_document()
199
- if current is None:
200
- raise CapabilityUnavailableError("No Hopper document is loaded")
201
- return current
192
+ return session_document
193
+
194
+
195
+ def _api_text(value, field):
196
+ """Validate text before passing it to Hopper's native UTF-8 string API."""
197
+ if not isinstance(value, str):
198
+ raise InvalidRequestError("%s must be a string" % field)
199
+ if "\0" in value:
200
+ raise InvalidRequestError("%s contains a NUL character, which Hopper's Python API cannot represent" % field)
201
+ try:
202
+ value.encode("utf-8")
203
+ except UnicodeEncodeError as error:
204
+ raise InvalidRequestError("%s contains an unpaired Unicode surrogate, which Hopper's Python API cannot represent" % field) from error
205
+ return value
206
+
207
+
208
+ def _validate_name_targets(document, names):
209
+ """Keep Hopper's unique-label moves within the explicitly selected addresses."""
210
+ targets = {address for _, address, _ in names}
211
+ if len(targets) != len(names):
212
+ raise InvalidRequestError("Batch rename selects the same address more than once; use one canonical address per target")
213
+ labels = set()
214
+ for _, address, name in names:
215
+ _segment(document, address)
216
+ if not name:
217
+ continue
218
+ if name in labels:
219
+ raise InvalidRequestError("Batch rename assigns the same name more than once: %s" % name)
220
+ labels.add(name)
221
+ owner = document.getAddressForName(name)
222
+ if owner not in BAD_ADDRESSES and owner != address and owner not in targets:
223
+ raise InvalidRequestError("Name %s is already assigned to %s; include that address in the batch with a replacement name" % (name, _hex(owner)))
224
+
225
+
226
+ def _symbol_name(value, field):
227
+ """Reject native label truncation without altering the caller's name."""
228
+ value = _api_text(value, field)
229
+ if len(value.encode("utf-16-le")) // 2 > HOPPER_SYMBOL_NAME_UTF16_UNITS:
230
+ raise InvalidRequestError("%s exceeds Hopper's %s UTF-16 code-unit symbol-name limit" % (field, HOPPER_SYMBOL_NAME_UTF16_UNITS))
231
+ return value
232
+
233
+
234
+ def _name_matches(document, address, name):
235
+ observed = _segment(document, address).getNameAtAddress(address)
236
+ return observed == name or (not name and observed is None)
202
237
 
203
238
 
204
239
  def _address(document, value=None):
205
- """Resolve hexadecimal addresses first, then fall back to Hopper symbol names."""
240
+ """Keep explicit hexadecimal coordinates distinct from symbol names."""
206
241
  if value is None:
207
242
  return document.getCurrentAddress()
208
- if not isinstance(value, str):
209
- raise InvalidRequestError("Address must be a string")
210
- try:
211
- return int(value, 16)
212
- except ValueError:
213
- result = document.getAddressForName(value)
214
- if result in BAD_ADDRESSES:
215
- raise InvalidRequestError("Unknown Hopper address or name")
216
- return result
243
+ _api_text(value, "Address")
244
+ if value.lower().startswith("0x"):
245
+ try:
246
+ address = int(value, 16)
247
+ except ValueError as error:
248
+ raise InvalidRequestError("Invalid hexadecimal Hopper address: %s" % value) from error
249
+ else:
250
+ # Names such as `add` and `face` are valid hexadecimal strings too.
251
+ # Prefer an observed symbol, retaining bare-hex compatibility only
252
+ # when Hopper has no symbol with the supplied name.
253
+ address = document.getAddressForName(value)
254
+ if address in BAD_ADDRESSES:
255
+ try:
256
+ address = int(value, 16)
257
+ except ValueError as error:
258
+ raise InvalidRequestError("Unknown Hopper address or name: %s" % value) from error
259
+ if not 0 <= address < 0xFFFFFFFFFFFFFFFF:
260
+ raise InvalidRequestError("Address is outside Hopper's usable unsigned 64-bit range: %s" % value)
261
+ return address
217
262
 
218
263
 
219
264
  def _segment(document, address):
@@ -249,14 +294,18 @@ def _procedure_identity(procedure):
249
294
 
250
295
 
251
296
  def _procedure_locals(procedure):
252
- """Project opaque Hopper local-variable objects into an exact public shape."""
253
- return [
254
- {
255
- "description": str(local),
297
+ """Preserve public local-variable names and observed stack displacements."""
298
+ result = []
299
+ for local in procedure.getLocalVariableList():
300
+ name = local.name()
301
+ displacement = local.displacement()
302
+ result.append({
303
+ "description": "%s (stack displacement %s)" % (name if name is not None else "Unnamed local", displacement),
304
+ "name": name,
305
+ "stack_displacement": str(displacement) if displacement is not None else None,
256
306
  "provenance": "hopper-public-python-api",
257
- }
258
- for local in procedure.getLocalVariableList()
259
- ]
307
+ })
308
+ return result
260
309
 
261
310
 
262
311
  def _containing_procedure(document, address):
@@ -267,26 +316,75 @@ def _containing_procedure(document, address):
267
316
  return (procedure, None) if procedure is not None else (None, "not_in_procedure")
268
317
 
269
318
 
319
+ def _basic_block_end(procedure, block):
320
+ """Normalize native block endpoints to an exclusive byte address."""
321
+ owner_at = getattr(procedure, "getBasicBlockAtAddress", None)
322
+ if not callable(owner_at):
323
+ raise CapabilityUnavailableError(
324
+ "Instruction enumeration requires Hopper basic-block ownership"
325
+ )
326
+ end = block.getEndingAddress()
327
+ # Hopper 6.1 returns the final instruction address, despite the Python
328
+ # documentation describing an exclusive end. Membership distinguishes
329
+ # that representation from an actual exclusive endpoint in other builds.
330
+ if owner_at(end) == block:
331
+ instruction = procedure.getSegment().getInstructionAtAddress(end)
332
+ if instruction is not None and instruction.getInstructionLength() > 0:
333
+ return end + instruction.getInstructionLength()
334
+ return end
335
+
336
+
337
+ def _block_instruction_addresses(procedure, block):
338
+ """Walk either endpoint convention using Hopper's block ownership API.
339
+
340
+ Some Hopper builds return the final instruction start, despite the public
341
+ API documenting an exclusive end. Equality compares fresh native wrappers;
342
+ probing ownership also keeps an exclusive endpoint in the next block out.
343
+ """
344
+ segment = procedure.getSegment()
345
+ address = block.getStartingAddress()
346
+ end = _basic_block_end(procedure, block)
347
+ while address < end:
348
+ if procedure.getBasicBlockAtAddress(address) != block:
349
+ break
350
+ instruction = segment.getInstructionAtAddress(address)
351
+ if instruction is None:
352
+ break
353
+ length = instruction.getInstructionLength()
354
+ if length <= 0 or address + length > end:
355
+ break
356
+ yield address
357
+ address += length
358
+
359
+
270
360
  def _instruction_addresses(procedure):
271
361
  result = []
272
362
  seen = set()
273
- segment = procedure.getSegment()
274
363
  for block in procedure.basicBlockIterator():
275
- address = block.getStartingAddress()
276
- end = block.getEndingAddress()
277
- while address < end and address not in seen:
364
+ for address in _block_instruction_addresses(procedure, block):
365
+ if address in seen:
366
+ continue
278
367
  seen.add(address)
279
- instruction = segment.getInstructionAtAddress(address)
280
- if instruction is None:
281
- break
282
368
  result.append(address)
283
- length = instruction.getInstructionLength()
284
- if length <= 0:
285
- break
286
- address += length
287
369
  return result
288
370
 
289
371
 
372
+ def _native_calls(procedure, direction):
373
+ references = procedure.getAllCallees() if direction == "outgoing" else procedure.getAllCallers()
374
+ kinds = {0: "none", 1: "unknown", 2: "direct", 3: "objective_c"}
375
+ calls = {}
376
+ unresolved = []
377
+ for reference in references:
378
+ source, target, kind = reference.fromAddress(), reference.toAddress(), reference.type()
379
+ if source in BAD_ADDRESSES:
380
+ continue
381
+ if target in BAD_ADDRESSES:
382
+ unresolved.append({"address": _hex(source), "reason": "Native CallReference has no resolved target (type %s)" % kind})
383
+ continue
384
+ calls[(source, target)] = {"classification": kinds.get(kind, "unknown"), "provider_type": kind, "provenance": "hopper-public-python-api:CallReference"}
385
+ return calls, sorted(unresolved, key=lambda item: int(item["address"], 16))
386
+
387
+
290
388
  def _procedure_references(document, params):
291
389
  procedure = _procedure(document, params.get("procedure"))
292
390
  direction = params.get("direction", "outgoing")
@@ -299,6 +397,8 @@ def _procedure_references(document, params):
299
397
  references = segment.getReferencesFromAddress(address) if direction == "outgoing" else segment.getReferencesOfAddress(address)
300
398
  for reference in references:
301
399
  edges.add((address, reference) if direction == "outgoing" else (reference, address))
400
+ calls, unresolved = _native_calls(procedure, direction)
401
+ edges.update(calls)
302
402
  ordered = sorted(edges)
303
403
  items = []
304
404
  for source, target in ordered:
@@ -309,16 +409,15 @@ def _procedure_references(document, params):
309
409
  "target_address": _hex(target),
310
410
  "source_procedure": _procedure_identity(source_procedure) if source_procedure is not None else None,
311
411
  "target_procedure": _procedure_identity(target_procedure) if target_procedure is not None else None,
312
- "kind": _unavailable("Hopper's public Python API does not classify reference kinds"),
412
+ "kind": _unavailable("Hopper exposes partial CallReference classification, but not detailed reference flags"),
413
+ **({"call": calls[(source, target)]} if (source, target) in calls else {}),
313
414
  })
314
415
  return {
315
416
  "procedure": _procedure_identity(procedure), "direction": direction,
316
417
  "reference_kinds_available": False,
317
- # The public Hopper API returns observed references but does not expose
318
- # enough flow metadata to enumerate calls with no resolved target.
319
- # Keep the required list field explicit; the provider records this as
320
- # unknown coverage rather than claiming the list is exhaustive.
321
- "unresolved_calls": [],
418
+ # These are native CallReference objects, not proof that every unresolved
419
+ # dispatch site has been enumerated by Hopper.
420
+ "unresolved_calls": unresolved,
322
421
  "references": items,
323
422
  }
324
423
 
@@ -333,11 +432,61 @@ def _procedure_map(document):
333
432
 
334
433
 
335
434
  def _strings(document):
336
- result = {}
337
- for segment in document.getSegmentsList():
338
- for value, address in segment.getStringsList():
339
- result[_hex(address)] = value
340
- return result
435
+ return {address: record["value"] for address, record in _string_inventory(document).items()}
436
+
437
+
438
+ def _string_record(segment, address, display):
439
+ """Read one native typed object, retaining its shortened display separately."""
440
+ length = segment.getObjectLength(address)
441
+ end = segment.getStartingAddress() + segment.getLength()
442
+ if isinstance(length, bool) or not isinstance(length, int) or length <= 0 or length > end - address:
443
+ raise CapabilityUnavailableError("Hopper reported an invalid string object extent at %s" % _hex(address))
444
+ raw = segment.readBytes(address, length)
445
+ if not isinstance(raw, (bytes, bytearray)) or len(raw) != length:
446
+ raise CapabilityUnavailableError("Hopper could not read the complete string object at %s" % _hex(address))
447
+ kind = segment.getTypeAtAddress(address)
448
+ encodings = ("utf-8", "latin-1") if kind == segment.TYPE_ASCII else ("utf-16-le", "utf-16-be") if kind == segment.TYPE_UNICODE else ()
449
+ # Native display escaping is not invertible (a literal backslash-n and a
450
+ # newline can render alike). Decode byte strings directly; for UTF-16,
451
+ # compare rendered candidates to establish an unambiguous byte order.
452
+ candidates = []
453
+ for encoding in encodings:
454
+ terminator = b"\x00\x00" if encoding.startswith("utf-16") else b"\x00"
455
+ terminated = raw.endswith(terminator)
456
+ payload = raw[:-len(terminator)] if terminated else raw
457
+ try:
458
+ value = payload.decode(encoding)
459
+ except UnicodeDecodeError:
460
+ continue
461
+ record = {"value": value, "provider_value": display, "string": {"encoding": encoding, "encoding_status": "inferred", "termination": "present_or_not_required" if terminated else "missing", "byte_length": length}}
462
+ if kind == segment.TYPE_ASCII:
463
+ return record
464
+ rendered = value.replace('"', '\\"').replace("\n", "\\n").replace("\r", "\\r").replace("\t", "\\t")
465
+ if rendered == display or (display.endswith("\u2026") and display[:-1] and rendered.startswith(display[:-1])):
466
+ candidates.append(record)
467
+ if candidates and len({candidate["value"] for candidate in candidates}) == 1:
468
+ return candidates[0]
469
+ raise CapabilityUnavailableError("Hopper's string display cannot be reconciled with its typed bytes at %s; inspect the bytes and encoding explicitly" % _hex(address))
470
+
471
+
472
+ def _string_inventory(document, addresses=None):
473
+ key = (id(document), "string_objects")
474
+ objects = _search_inventory_cache.get(key)
475
+ if objects is None:
476
+ objects = {address: (segment, display) for segment in document.getSegmentsList() for display, address in segment.getStringsList()}
477
+ _search_inventory_cache[key] = objects
478
+ key = (id(document), "string_records")
479
+ records = _search_inventory_cache.setdefault(key, {})
480
+ selected = sorted(objects if addresses is None else objects.keys() & addresses)
481
+ for address in selected:
482
+ name = _hex(address)
483
+ if name not in records:
484
+ segment, display = objects[address]
485
+ try:
486
+ records[name] = _string_record(segment, address, display)
487
+ except CapabilityUnavailableError as error:
488
+ records[name] = {"value": display, "provider_value": display, "decoding": _unavailable(str(error))}
489
+ return {_hex(address): records[_hex(address)] for address in selected}
341
490
 
342
491
 
343
492
  def _invalidate_search_inventory(document):
@@ -397,17 +546,18 @@ def _search_results(document, kind, params):
397
546
  needle = pattern if case_sensitive else pattern.casefold()
398
547
  matches = lambda value: needle in (value if case_sensitive else value.casefold())
399
548
  else:
400
- try:
401
- expression = re.compile(pattern, 0 if case_sensitive else re.IGNORECASE)
402
- except (re.error, OverflowError) as error:
403
- raise InvalidRequestError("Invalid regex pattern") from error
404
- matches = lambda value: expression.search(value) is not None
549
+ raise CapabilityUnavailableError("Regex matching requires REA's supervised worker; the native bridge only executes literal searches")
405
550
 
406
551
  selected = []
407
552
  for item in _search_inventory(document, kind):
408
553
  if not matches(item[1]):
409
554
  continue
410
555
  selected.append(item)
556
+ if not selected:
557
+ return []
558
+ if kind == "string":
559
+ records = _string_inventory(document)
560
+ return [{"address": address, **records[address]} for address, _ in selected]
411
561
  return [
412
562
  {
413
563
  "address": address,
@@ -450,25 +600,11 @@ def _render_instruction(segment, address):
450
600
 
451
601
  def _assembly(procedure):
452
602
  """Render assembly while guarding against malformed instruction cycles."""
453
- lines = []
454
603
  segment = procedure.getSegment()
455
- seen = set()
456
- for block in procedure.basicBlockIterator():
457
- address = block.getStartingAddress()
458
- end = block.getEndingAddress()
459
- while address < end and address not in seen:
460
- seen.add(address)
461
- instruction = segment.getInstructionAtAddress(address)
462
- if instruction is None:
463
- break
464
- line = _render_instruction(segment, address)
465
- if line is not None:
466
- lines.append(line)
467
- length = instruction.getInstructionLength()
468
- if length <= 0:
469
- break
470
- address += length
471
- return "\n".join(lines)
604
+ return "\n".join(
605
+ line for address in _instruction_addresses(procedure)
606
+ if (line := _render_instruction(segment, address)) is not None
607
+ )
472
608
 
473
609
 
474
610
  def _read_function_instructions(document, params):
@@ -505,7 +641,7 @@ def _analyze_function(document, params):
505
641
  successors.append(_hex(successor))
506
642
  blocks.append({
507
643
  "start": _hex(block.getStartingAddress()),
508
- "end": _hex(block.getEndingAddress()),
644
+ "end": _hex(_basic_block_end(procedure, block)),
509
645
  "successors": sorted(set(successors), key=lambda value: int(value, 16)),
510
646
  })
511
647
  pseudo = _pseudocode(document, procedure) or ""
@@ -529,6 +665,10 @@ def _analyze_function(document, params):
529
665
  incoming = []
530
666
  outgoing = []
531
667
  procedure_addresses = set(addresses)
668
+ outgoing_calls, unresolved_calls = _native_calls(procedure, "outgoing")
669
+ incoming_calls, _ = _native_calls(procedure, "incoming")
670
+ calls = {**incoming_calls, **outgoing_calls}
671
+ edges.update(calls)
532
672
  for source, target in sorted(edges):
533
673
  source_procedure, _ = _containing_procedure(document, source)
534
674
  target_procedure, _ = _containing_procedure(document, target)
@@ -537,16 +677,14 @@ def _analyze_function(document, params):
537
677
  "target_address": _hex(target),
538
678
  "source_procedure": _procedure_identity(source_procedure) if source_procedure is not None else None,
539
679
  "target_procedure": _procedure_identity(target_procedure) if target_procedure is not None else None,
540
- "kind": _unavailable("Hopper's public Python API does not classify reference kinds"),
680
+ "kind": _unavailable("Hopper exposes partial CallReference classification, but not detailed reference flags"),
681
+ **({"call": calls[(source, target)]} if (source, target) in calls else {}),
541
682
  }
542
683
  if target in procedure_addresses and source not in procedure_addresses:
543
684
  incoming.append(item)
544
685
  if source in procedure_addresses:
545
686
  outgoing.append(item)
546
- string_map = {
547
- int(address, 16): value
548
- for address, value in _search_inventory(document, "string")
549
- }
687
+ string_map = {int(address, 16): record for address, record in _string_inventory(document, {int(edge["target_address"], 16) for edge in outgoing}).items()}
550
688
  name_map = {
551
689
  int(address, 16): value
552
690
  for address, value in _search_inventory(document, "name")
@@ -556,7 +694,7 @@ def _analyze_function(document, params):
556
694
  for edge in outgoing:
557
695
  target = int(edge["target_address"], 16)
558
696
  if target in string_map:
559
- referenced_strings.append({"address": edge["target_address"], "value": string_map[target], "source_address": edge["source_address"]})
697
+ referenced_strings.append({"address": edge["target_address"], **string_map[target], "source_address": edge["source_address"]})
560
698
  if target in name_map:
561
699
  referenced_names.append({"address": edge["target_address"], "value": name_map[target], "source_address": edge["source_address"]})
562
700
  comments.sort(key=lambda item: (int(item["address"], 16), item["kind"]))
@@ -574,13 +712,14 @@ def _analyze_function(document, params):
574
712
  "callers": callers, "callees": callees,
575
713
  "incoming_references": incoming,
576
714
  "outgoing_references": outgoing,
715
+ "unresolved_calls": unresolved_calls,
577
716
  "referenced_strings": referenced_strings,
578
717
  "referenced_names": referenced_names,
579
718
  "basic_blocks": blocks,
580
719
  "limitations": [
581
- "Hopper's public Python API does not classify reference kinds.",
720
+ "Native CallReference classifications are observed where available; detailed reference flags remain unknown.",
582
721
  "Hopper's public Python API does not expose equivalent external or thunk classification in this dossier.",
583
- "Unresolved indirect calls without reported target addresses are not represented as call edges.",
722
+ "Native calls without resolved target addresses are not represented as edges; enumeration beyond reported CallReference objects is unknown.",
584
723
  "Pseudocode and assembly are provider-specific representations, not original source.",
585
724
  ],
586
725
  }
@@ -588,7 +727,6 @@ def _analyze_function(document, params):
588
727
 
589
728
  def _dispatch(method, params):
590
729
  """Dispatch only the closed operation set implemented by REA's public tools."""
591
- global _selected_document
592
730
  if method == "health":
593
731
  return {"name": "REA Hopper bridge", "version": "1.0.0", "run_id": REA_RUN_ID}
594
732
  if method in ("shutdown", "shutdown_document"):
@@ -622,14 +760,9 @@ def _dispatch(method, params):
622
760
  "document_closed": document_closed,
623
761
  }
624
762
  if method == "list_documents":
625
- return [document.getDocumentName() for document in Document.getAllDocuments()]
763
+ return [document.getDocumentName() for document in _api().documents()]
626
764
  if method == "current_document":
627
765
  return _document().getDocumentName()
628
- if method == "set_current_document":
629
- document = _document(params.get("document"))
630
- _selected_document = document.getDocumentName()
631
- return _selected_document
632
-
633
766
  document = _document(params.get("document"))
634
767
  if method in EXHAUSTIVE_ANALYSIS_METHODS:
635
768
  _api().require_analysis_complete(document, method)
@@ -650,10 +783,17 @@ def _dispatch(method, params):
650
783
  "Hopper's public Python API does not expose readBytes"
651
784
  )
652
785
  address = _address(document, params.get("address"))
653
- value = reader(address, length)
786
+ segment = _segment(document, address)
787
+ # Document.readBytes delegates to one Segment and returns False when
788
+ # the requested range crosses its end. Preserve the readable prefix.
789
+ available = segment.getStartingAddress() + segment.getLength() - address
790
+ value = reader(address, min(length, available))
791
+ if value is False or value is None:
792
+ raise InvalidRequestError("Hopper could not read the requested byte range at %s" % _hex(address))
654
793
  if not isinstance(value, (bytes, bytearray)):
655
794
  raise CapabilityUnavailableError(
656
- "Hopper's public Python API returned an unsupported byte representation"
795
+ "Hopper readBytes returned %s for the requested %s-byte range at %s"
796
+ % (type(value).__name__, length, _hex(address))
657
797
  )
658
798
  data = bytes(value)
659
799
  return {
@@ -678,7 +818,26 @@ def _dispatch(method, params):
678
818
  or offset < 0
679
819
  ):
680
820
  raise InvalidRequestError("Address has no authoritative file-offset mapping")
681
- return {"address": _hex(address), "file_offset": offset}
821
+ inverse = getattr(document, "getAddressFromFileOffset", None)
822
+ if not callable(inverse):
823
+ raise CapabilityUnavailableError(
824
+ "Hopper's public Python API cannot verify the file-offset mapping by reverse lookup"
825
+ )
826
+ # Synthetic memory can report offset zero, which belongs to the actual
827
+ # file header. An integer alone does not prove source-file provenance.
828
+ if inverse(offset) != address:
829
+ raise InvalidRequestError("Address has no authoritative file-offset mapping: reverse lookup disagrees at %s" % _hex(address))
830
+ if offset > 9007199254740991:
831
+ raise CapabilityUnavailableError("Provider file offset exceeds the exact JSON integer range")
832
+ header_address = inverse(0)
833
+ reader = getattr(document, "readBytes", None)
834
+ header = reader(header_address, 12) if header_address not in BAD_ADDRESSES and document.getSegmentAtAddress(header_address) is not None and callable(reader) else None
835
+ return {
836
+ "address": _hex(address),
837
+ "provider_file_offset": offset,
838
+ "provider_source_path": document.getExecutableFilePath(),
839
+ "provider_image_header_hex": bytes(header).hex() if isinstance(header, (bytes, bytearray)) and len(header) == 12 else None,
840
+ }
682
841
  if method == "resolve_containing_procedure":
683
842
  address = _address(document, params.get("address"))
684
843
  procedure, reason = _containing_procedure(document, address)
@@ -694,8 +853,13 @@ def _dispatch(method, params):
694
853
  return _procedure_name(_procedure(document))
695
854
  if method == "goto_address":
696
855
  address = _address(document, params.get("address"))
856
+ segment = _segment(document, address)
857
+ expected = segment.getInstructionStart(address)
697
858
  document.moveCursorAtAddress(address)
698
- return _hex(address)
859
+ observed = document.getCurrentAddress()
860
+ if observed != (address if expected in BAD_ADDRESSES else expected):
861
+ raise InvalidRequestError("Hopper cursor did not reach the requested object at %s; observed %s" % (_hex(address), _hex(observed)))
862
+ return _hex(observed)
699
863
  if method in ("address_name", "comment", "inline_comment", "xrefs"):
700
864
  target = _address(document, params.get("address"))
701
865
  segment = _segment(document, target)
@@ -708,12 +872,22 @@ def _dispatch(method, params):
708
872
  return [_hex(value) for value in segment.getReferencesOfAddress(target)]
709
873
  if method in ("next_address", "prev_address"):
710
874
  target = _address(document, params.get("address"))
875
+ segment = _segment(document, target)
711
876
  if method == "next_address":
712
- result = target + max(1, document.getObjectLength(target))
877
+ start = segment.getInstructionStart(target)
878
+ if start in BAD_ADDRESSES:
879
+ raise InvalidRequestError("No analyzed object exists at the requested address")
880
+ length = segment.getObjectLength(start)
881
+ if length in BAD_ADDRESSES or length <= 0:
882
+ raise InvalidRequestError("No next analyzed object exists at the requested address")
883
+ result = start + length
713
884
  else:
714
- result = document.getInstructionStart(max(0, target - 1))
715
- if result in BAD_ADDRESSES:
716
- raise InvalidRequestError("No adjacent address")
885
+ previous_segment = document.getSegmentAtAddress(target - 1) if target > 0 else None
886
+ if previous_segment is None:
887
+ raise InvalidRequestError("No previous address exists in mapped memory")
888
+ result = previous_segment.getInstructionStart(target - 1)
889
+ if result in BAD_ADDRESSES or document.getSegmentAtAddress(result) is None:
890
+ raise InvalidRequestError("No adjacent analyzed object exists in mapped memory")
717
891
  return _hex(result)
718
892
  if method == "list_segments":
719
893
  result = []
@@ -747,12 +921,9 @@ def _dispatch(method, params):
747
921
  if method == "list_procedures":
748
922
  return [{"address": address, "value": value} for address, value in _search_inventory(document, "procedure")]
749
923
  if method == "list_strings":
750
- values = dict(_search_inventory(document, "string"))
751
924
  requested = params.get("address")
752
- if requested is not None:
753
- key = _hex(_address(document, requested))
754
- values = {key: values[key]} if key in values else {}
755
- return [{"address": address, "value": value} for address, value in values.items()]
925
+ values = _string_inventory(document, None if requested is None else {_address(document, requested)})
926
+ return [{"address": address, **record} for address, record in values.items()]
756
927
  if method == "list_names":
757
928
  result = dict(_search_inventory(document, "name"))
758
929
  requested = params.get("address")
@@ -777,7 +948,7 @@ def _dispatch(method, params):
777
948
  return sorted((_hex(item.getEntryPoint()) for item in procedure.getAllCalleeProcedures()), key=lambda value: int(value, 16))
778
949
  if method == "procedure_info":
779
950
  blocks = list(procedure.basicBlockIterator())
780
- length = sum(max(0, block.getEndingAddress() - block.getStartingAddress()) for block in blocks)
951
+ length = sum(max(0, _basic_block_end(procedure, block) - block.getStartingAddress()) for block in blocks)
781
952
  return {
782
953
  "name": _procedure_name(procedure),
783
954
  "entrypoint": _hex(procedure.getEntryPoint()),
@@ -790,35 +961,51 @@ def _dispatch(method, params):
790
961
  }
791
962
  if method == "set_address_name":
792
963
  address = _address(document, params.get("address"))
964
+ name = _symbol_name(params["name"], "Name")
965
+ _validate_name_targets(document, [(params.get("address"), address, name)])
793
966
  try:
794
- result = document.setNameAtAddress(address, params["name"])
967
+ result = document.setNameAtAddress(address, name)
795
968
  finally:
796
969
  _invalidate_search_inventory(document)
797
970
  _invalidate_pseudocode(document)
798
- return result
971
+ return bool(result) and _name_matches(document, address, name)
799
972
  if method == "set_addresses_names":
973
+ # Validate all destinations before applying any annotation. A malformed
974
+ # later address must not discard the result of an earlier mutation.
975
+ names = [(key, _address(document, key), _symbol_name(value, "Name for %s" % key)) for key, value in params["names"].items()]
976
+ _validate_name_targets(document, names)
800
977
  try:
801
- result = {key: document.setNameAtAddress(_address(document, key), value) for key, value in params["names"].items()}
978
+ result = {key: document.setNameAtAddress(address, value) for key, address, value in names}
802
979
  finally:
803
980
  _invalidate_search_inventory(document)
804
981
  _invalidate_pseudocode(document)
805
- return result
982
+ return {key: bool(result[key]) and _name_matches(document, address, value) for key, address, value in names}
806
983
  if method in ("set_comment", "set_inline_comment"):
807
984
  address = _address(document, params.get("address"))
985
+ comment = _api_text(params["comment"], "Comment")
808
986
  segment = _segment(document, address)
809
987
  setter = segment.setCommentAtAddress if method == "set_comment" else segment.setInlineCommentAtAddress
810
988
  getter = segment.getCommentAtAddress if method == "set_comment" else segment.getInlineCommentAtAddress
811
- setter(address, params["comment"])
989
+ setter(address, comment)
812
990
  _invalidate_pseudocode(document)
813
- return getter(address) == params["comment"]
991
+ observed = getter(address)
992
+ return observed == comment or (comment == "" and observed is None)
814
993
  if method == "list_bookmarks":
815
994
  return [{"address": _hex(item), "name": document.getBookmarkName(item)} for item in document.getBookmarks()]
816
995
  if method == "set_bookmark":
817
996
  address = _address(document, params.get("address"))
818
- document.setBookmarkAtAddress(address, params.get("name"))
997
+ _segment(document, address)
998
+ name = params.get("name")
999
+ if name is not None:
1000
+ _api_text(name, "Bookmark name")
1001
+ document.setBookmarkAtAddress(address, name)
819
1002
  return document.hasBookmarkAtAddress(address)
820
1003
  if method == "unset_bookmark":
821
1004
  address = _address(document, params.get("address"))
1005
+ # Permit removing a legacy orphan bookmark, but do not report success
1006
+ # for an unmapped address with no bookmark to remove.
1007
+ if not document.hasBookmarkAtAddress(address):
1008
+ _segment(document, address)
822
1009
  document.removeBookmarkAtAddress(address)
823
1010
  return not document.hasBookmarkAtAddress(address)
824
1011
  raise InvalidRequestError("Unknown bridge method")
@@ -913,14 +1100,14 @@ def _diagnostic_type(error):
913
1100
 
914
1101
 
915
1102
  def _safe_diagnostic(error):
916
- """Project one exception without retaining provider or credential text."""
1103
+ """Preserve bridge validation reasons while excluding transport credentials."""
917
1104
  diagnostic_type = _diagnostic_type(error)
918
1105
  if diagnostic_type == "capability_unavailable":
919
1106
  message = str(error)
920
1107
  elif diagnostic_type == "authorization":
921
1108
  message = "Invalid bridge capability"
922
1109
  elif diagnostic_type == "invalid_request":
923
- message = "Invalid Hopper bridge request"
1110
+ message = str(error)
924
1111
  else:
925
1112
  message = "%s: Hopper bridge operation failed" % type(error).__name__
926
1113
  return {"code": -32000, "message": message, "type": diagnostic_type}