rea-agents 6.0.0 → 6.1.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.
- package/README.md +23 -17
- package/bridge/android/ReaJadxBridge.java +14 -5
- package/bridge/ghidra/ReaGhidraBridge.java +173 -47
- package/bridge/hopper_bridge.py +310 -128
- package/bridge/mitmproxy/capture.py +0 -2
- package/bridge/native/rea_lldb_tracer.py +630 -94
- package/bridge/process/ProcessRunTokenReader.swift +39 -5
- package/bridge/pwntools/recorded_crash_impl.py +4 -0
- package/dist/android/JadxConfiguration.js +196 -33
- package/dist/android/JadxMcpTransport.js +4 -0
- package/dist/android/JadxProtocol.js +6 -0
- package/dist/android/JadxProvider.js +19 -2
- package/dist/android/JadxSession.js +23 -4
- package/dist/application/AnalysisContextQueries.js +8 -35
- package/dist/application/AnalysisDocument.js +32 -0
- package/dist/application/AppBundleExecutable.js +67 -21
- package/dist/application/CallPathTracing.js +19 -3
- package/dist/application/CapabilityInventory.js +6 -3
- package/dist/application/ClientConfigurationDocument.js +871 -6
- package/dist/application/ClientRegistrationIdentity.js +26 -5
- package/dist/application/ClientRegistrationStatus.js +29 -14
- package/dist/application/DirectAnalysis.js +27 -37
- package/dist/application/Doctor.js +4 -2
- package/dist/application/DoctorScope.js +25 -13
- package/dist/application/EnhancedLiteralTracing.js +22 -25
- package/dist/application/EnhancedTools.js +13 -11
- package/dist/application/EvidenceBundleFiles.js +23 -11
- package/dist/application/FunctionAnalysisEvidence.js +2 -3
- package/dist/application/InventoryProjectionEvidence.js +4 -3
- package/dist/application/InvestigationProviders.js +3 -2
- package/dist/application/JsonFiles.js +50 -28
- package/dist/application/ProcedureAddressResolution.js +11 -0
- package/dist/application/ReconstructionCoverageService.js +7 -4
- package/dist/application/ReconstructionObligationLedgerService.js +4 -3
- package/dist/application/Setup.js +13 -1
- package/dist/application/SetupClientConfiguration.js +10 -15
- package/dist/application/SetupHost.js +5 -2
- package/dist/application/SetupPlan.js +5 -3
- package/dist/application/SupportedClients.js +34 -1
- package/dist/application/Uninstall.js +4 -3
- package/dist/application/Update.js +18 -3
- package/dist/application/UpdateRuntime.js +46 -25
- package/dist/application/WebModuleTraceService.js +4 -2
- package/dist/application/WebNetworkCaptureService.js +4 -2
- package/dist/application/WebRuntimeService.js +5 -3
- package/dist/application/WebScriptExportService.js +2 -2
- package/dist/application/WebSourceLocationService.js +4 -2
- package/dist/application/WorkflowEvidence.js +82 -0
- package/dist/application/binary/AnalysisProviderEvaluation.js +21 -5
- package/dist/application/binary/AnalysisProviderRegistry.js +2 -0
- package/dist/application/binary/AnalysisSnapshotCache.js +3 -6
- package/dist/application/binary/BinarySession.js +95 -68
- package/dist/application/binary/BinarySessionOpen.js +3 -3
- package/dist/application/binary/BinarySessionRecords.js +25 -3
- package/dist/application/binary/BinarySessionStatus.js +17 -1
- package/dist/application/binary/CompositeProvider.js +13 -0
- package/dist/application/binary/LazyAnalysisProvider.js +3 -0
- package/dist/application/binaryDiagnostics/BinaryLayoutService.js +4 -2
- package/dist/application/binaryDiagnostics/RecordedCrashService.js +4 -2
- package/dist/application/evm/EvmInterfaceService.js +4 -2
- package/dist/application/investigation/EvidenceLedger.js +21 -7
- package/dist/application/investigation/InvestigationRecords.js +8 -0
- package/dist/application/javascript/JavaScriptAnalysisControl.js +34 -0
- package/dist/application/javascript/JavaScriptApplicationEvidence.js +24 -3
- package/dist/application/javascript/JavaScriptApplicationEvidenceGraph.js +19 -2
- package/dist/application/javascript/JavaScriptApplicationService.js +22 -15
- package/dist/application/javascript/JavaScriptApplicationWorkflowService.js +5 -5
- package/dist/application/javascript/JavaScriptArtifactAnalysis.js +5 -4
- package/dist/application/javascript/JavaScriptArtifactGraphBuilder.js +39 -7
- package/dist/application/javascript/JavaScriptArtifactPathResolution.js +6 -3
- package/dist/application/javascript/JavaScriptArtifactReconstruction.js +11 -3
- package/dist/application/javascript/JavaScriptModuleRelationships.js +37 -8
- package/dist/application/javascript/JavaScriptRecoveryService.js +4 -2
- package/dist/application/javascript/JavaScriptRuntimeObservationService.js +3 -1
- package/dist/application/javascript/JavaScriptRuntimeReconciliationService.js +3 -3
- package/dist/application/javascript/JavaScriptSemanticGraphBuilder.js +63 -61
- package/dist/application/javascript/JavaScriptSemanticGraphValueProjection.js +23 -1
- package/dist/application/managed/ManagedApplicationGraphService.js +4 -3
- package/dist/application/managed/ManagedMemberComparisonService.js +7 -4
- package/dist/application/managed/ManagedNativeVerificationService.js +4 -3
- package/dist/application/managed/ManagedReconstructionService.js +4 -3
- package/dist/application/process/ProcessCli.js +14 -6
- package/dist/application/process/ProcessEvidence.js +4 -3
- package/dist/application/workflowInputError.js +18 -0
- package/dist/artifacts/ArtifactProvider.js +12 -2
- package/dist/artifacts/MachOSliceArtifactReader.js +98 -14
- package/dist/artifacts/NativeDmgArtifactReader.js +13 -6
- package/dist/artifacts/apple/AppleAssetCatalogAnalysis.js +22 -3
- package/dist/artifacts/apple/InterfaceBuilderAnalysis.js +176 -56
- package/dist/artifacts/apple/InterfaceBuilderDecodeBudget.js +195 -0
- package/dist/artifacts/apple/KeyedArchiveReader.js +36 -22
- package/dist/artifacts/apple/MachoLoadCommandReader.js +92 -31
- package/dist/artifacts/apple/NibArchive.js +81 -14
- package/dist/artifacts/apple/NibViewHierarchy.js +10 -3
- package/dist/artifacts/apple/PlistNumberLiterals.js +164 -0
- package/dist/artifacts/inventory/ArtifactGraphConstruction.js +13 -8
- package/dist/artifacts/inventory/scanCanonical.js +11 -14
- package/dist/artifacts/javascript/JavaScriptArtifactFiles.js +6 -2
- package/dist/browser/CdpCaptureCompleteness.js +2 -0
- package/dist/browser/CdpCaptureEventHandlers.js +76 -11
- package/dist/browser/CdpCaptureEventTypes.js +5 -1
- package/dist/browser/CdpCaptureEvents.js +25 -0
- package/dist/browser/CdpConnection.js +30 -11
- package/dist/browser/CdpOptionalCommand.js +2 -3
- package/dist/browser/CdpPageCapture.js +23 -1
- package/dist/browser/PlaywrightElectronActiveActions.js +32 -23
- package/dist/browser/PlaywrightElectronActiveProjection.js +141 -0
- package/dist/browser/PlaywrightElectronActiveProvider.js +123 -124
- package/dist/browser/WebSourceMapFetcher.js +426 -72
- package/dist/browser/assets/LocalWebScriptArtifacts.js +2 -2
- package/dist/browser/history/CaptureFailures.js +37 -1
- package/dist/browser/history/CaptureRelease.js +5 -0
- package/dist/browser/history/HarCaptureAdapter.js +3 -3
- package/dist/browser/history/HarJson.js +3 -2
- package/dist/browser/history/HistoricalCaptureDecoder.js +4 -2
- package/dist/cli/analysisSelector.js +23 -0
- package/dist/cli/applicationCommands.js +2 -3
- package/dist/cli/coreBinaryCommands.js +70 -12
- package/dist/cli/electronCommands.js +7 -6
- package/dist/cli/javascriptApplicationAnalysis.js +7 -3
- package/dist/cli/jsonOutput.js +4 -99
- package/dist/cli/nativeCallCommands.js +2 -3
- package/dist/cli/webRuntimeCommands.js +8 -8
- package/dist/cli.js +2 -1
- package/dist/cliCommandNames.js +0 -1
- package/dist/cliJsonInput.js +9 -4
- package/dist/config/environment.js +4 -0
- package/dist/config/ghidraStartupTimeout.js +17 -0
- package/dist/config/mcpResponseBudget.js +22 -0
- package/dist/config/parseConfig.js +2 -1
- package/dist/contracts/enhancedInputs.js +5 -3
- package/dist/contracts/enhancedToolContracts.js +3 -3
- package/dist/contracts/hopperMemoryContracts.js +2 -5
- package/dist/contracts/javascript/electronToolContracts.js +1 -1
- package/dist/contracts/managed/managedToolContracts.js +2 -4
- package/dist/contracts/native/nativeToolContracts.js +4 -2
- package/dist/contracts/officialToolContracts.js +14 -15
- package/dist/contracts/promptContracts.js +3 -3
- package/dist/contracts/sessionLifecycleInputs.js +3 -7
- package/dist/contracts/sessionStatusContract.js +2 -4
- package/dist/contracts/sessionToolContracts.js +4 -6
- package/dist/contracts/sessionToolSchemas.js +2 -5
- package/dist/contracts/toolContractExamples.js +0 -1
- package/dist/contracts/toolContractHelpers.js +4 -1
- package/dist/contracts/toolEffects.js +0 -1
- package/dist/contracts/toolOutputSchemaGroups.js +28 -27
- package/dist/contracts/toolOutputSchemaPrimitives.js +18 -0
- package/dist/contracts/toolSchemaMetadata.js +69 -10
- package/dist/contracts/webNetworkCaptureToolContracts.js +1 -1
- package/dist/domain/analysisErrorBase.js +8 -3
- package/dist/domain/analysisErrorCore.js +3 -1
- package/dist/domain/analysisErrorPresentation.js +72 -17
- package/dist/domain/analysisErrorProjection.js +106 -27
- package/dist/domain/android/androidAnalysis.js +2 -4
- package/dist/domain/android/androidApplication.js +17 -9
- package/dist/domain/apple/appleApplication.js +41 -14
- package/dist/domain/apple/dyldEnvironment.js +73 -0
- package/dist/domain/apple/dylibResolution.js +120 -9
- package/dist/domain/apple/dylibResolutionFindings.js +9 -7
- package/dist/domain/apple/interfaceBuilderGraph.js +25 -13
- package/dist/domain/apple/interfaceBuilderKeyedArchive.js +13 -3
- package/dist/domain/apple/keyedArchive.js +9 -1
- package/dist/domain/apple/machoContainer.js +54 -0
- package/dist/domain/apple/plistNumbers.js +76 -0
- package/dist/domain/artifactIdentity.js +29 -0
- package/dist/domain/artifactInventoryEvidence.js +19 -24
- package/dist/domain/bridgeCandidateProjection.js +71 -0
- package/dist/domain/browserCompleteness.js +1 -0
- package/dist/domain/browserObservationError.js +24 -5
- package/dist/domain/browserScenarioValues.js +2 -6
- package/dist/domain/bundleComparison.js +3 -2
- package/dist/domain/canonicalJsonDigestSteps.js +133 -0
- package/dist/domain/canonicalOrdering.js +9 -5
- package/dist/domain/changedBehavior.js +6 -6
- package/dist/domain/comparisonSourceEvidence.js +40 -0
- package/dist/domain/configurationErrors.js +1 -1
- package/dist/domain/evidence.js +64 -9
- package/dist/domain/evidenceBundle.js +42 -1
- package/dist/domain/evidenceErrors.js +12 -1
- package/dist/domain/explicitSensitiveFailure.js +17 -1
- package/dist/domain/firmware/firmwareAnalysis.js +6 -7
- package/dist/domain/hopperErrors.js +23 -2
- package/dist/domain/hopperValues.js +51 -8
- package/dist/domain/immutableJson.js +114 -0
- package/dist/domain/inputIssueProjection.js +3 -0
- package/dist/domain/javascript/electronActiveObservation.js +30 -4
- package/dist/domain/javascript/electronStaticAnalysisValues.js +6 -12
- package/dist/domain/javascript/javascriptApplicationAnalysis.js +71 -23
- package/dist/domain/javascript/javascriptApplicationGraph.js +13 -0
- package/dist/domain/javascript/javascriptAstValues.js +20 -0
- package/dist/domain/javascript/javascriptRecovery.js +3 -8
- package/dist/domain/javascript/javascriptSemanticAnalysis.js +97 -2
- package/dist/domain/javascript/javascriptSemanticCoverage.js +6 -5
- package/dist/domain/javascript/javascriptSemanticDataEffectHelpers.js +9 -17
- package/dist/domain/javascript/javascriptSemanticDataEffects.js +22 -5
- package/dist/domain/javascript/javascriptSemanticGraph.js +13 -0
- package/dist/domain/javascript/javascriptSemanticGraphSchemas.js +1 -0
- package/dist/domain/javascript/javascriptSemanticPrimitives.js +25 -17
- package/dist/domain/javascript/javascriptSemanticQuerySchemas.js +8 -4
- package/dist/domain/javascript/javascriptSemanticResourceLimits.js +77 -0
- package/dist/domain/javascript/javascriptSemanticValues.js +83 -11
- package/dist/domain/javascript/javascriptSourceParser.js +6 -2
- package/dist/domain/javascript/javascriptStaticAnalysis.js +22 -17
- package/dist/domain/javascript/javascriptStaticAnalysisBundler.js +5 -3
- package/dist/domain/javascript/javascriptStaticAnalysisCalls.js +14 -4
- package/dist/domain/javascript/javascriptStaticAnalysisFindings.js +66 -7
- package/dist/domain/javascript/javascriptStaticAnalysisHelpers.js +28 -16
- package/dist/domain/javascript/javascriptStaticAnalysisState.js +3 -2
- package/dist/domain/jsonSerialization.js +109 -0
- package/dist/domain/jsonValue.js +90 -6
- package/dist/domain/localPath.js +6 -0
- package/dist/domain/managed/managedApplicationGraph.js +10 -10
- package/dist/domain/managed/managedInspectionEvidence.js +16 -0
- package/dist/domain/managed/managedMemberComparisonMatch.js +5 -9
- package/dist/domain/managed/managedNativeVerification.js +3 -9
- package/dist/domain/native/fileOffsetPartialObservation.js +1 -0
- package/dist/domain/native/nativeCallObservation.js +33 -11
- package/dist/domain/native/nativeCallPartialObservation.js +33 -0
- package/dist/domain/process/processCapture.js +8 -4
- package/dist/domain/process/processComparison.js +38 -4
- package/dist/domain/process/processScenario.js +11 -10
- package/dist/domain/propertyListKeys.js +1 -1
- package/dist/domain/propertyListXmlText.js +19 -0
- package/dist/domain/providerAdapterError.js +1 -1
- package/dist/domain/providerOperationHealth.js +54 -0
- package/dist/domain/reconstructionVerification.js +6 -43
- package/dist/domain/reconstructionVerificationSchemas.js +2 -2
- package/dist/domain/staticRuntimeCorrelation.js +6 -43
- package/dist/domain/webBundleAnalysis.js +20 -8
- package/dist/domain/webBundleAnalyzerInspection.js +13 -8
- package/dist/domain/webBundleAnalyzerResult.js +41 -2
- package/dist/domain/webRuntime.js +19 -5
- package/dist/domain/webSourceLocation.js +1 -0
- package/dist/firmware/FirmwareFiles.js +1 -1
- package/dist/generatedPackageMetadata.js +1 -1
- package/dist/ghidra/GhidraAnalysisProfile.js +5 -0
- package/dist/ghidra/GhidraClient.js +47 -14
- package/dist/ghidra/GhidraDiagnostics.js +7 -7
- package/dist/ghidra/GhidraFunctionValues.js +10 -7
- package/dist/ghidra/GhidraInstallation.js +18 -21
- package/dist/ghidra/GhidraInventoryValues.js +14 -4
- package/dist/ghidra/GhidraJavaLaunch.js +62 -0
- package/dist/ghidra/GhidraLauncher.js +33 -16
- package/dist/ghidra/GhidraProviderCapabilities.js +14 -2
- package/dist/ghidra/GhidraProviderClient.js +23 -12
- package/dist/ghidra/GhidraSessionError.js +11 -7
- package/dist/ghidra/GhidraTargetSnapshot.js +47 -12
- package/dist/ghidra/GhidraTransport.js +20 -0
- package/dist/hopper/BridgeLauncher.js +31 -11
- package/dist/hopper/HopperAnalysisProfile.js +62 -4
- package/dist/hopper/HopperCleanup.js +15 -3
- package/dist/hopper/HopperClient.js +222 -14
- package/dist/hopper/HopperFileOffset.js +237 -0
- package/dist/hopper/HopperMachOImage.js +196 -0
- package/dist/hopper/HopperOperationStage.js +6 -0
- package/dist/hopper/HopperProvider.js +63 -9
- package/dist/hopper/HopperProviderCapabilities.js +19 -1
- package/dist/hopper/HopperRegexSearch.js +137 -0
- package/dist/hopper/HopperRegexWorker.js +27 -0
- package/dist/hopper/HopperRequestQueue.js +68 -19
- package/dist/hopper/HopperTargetLease.js +3 -2
- package/dist/inspector/JavaScriptRuntimeScope.js +9 -1
- package/dist/inspector/V8InspectorCaptureProjection.js +8 -3
- package/dist/inspector/V8InspectorProvider.js +47 -11
- package/dist/javascript/sourceMaps/DecodedSourceMap.js +47 -0
- package/dist/javascript/sourceMaps/SourceMapCommand.js +16 -0
- package/dist/javascript/sourceMaps/SourceMapFormat.js +79 -31
- package/dist/javascript/sourceMaps/TraceSourceMap.js +14 -28
- package/dist/mcpDoctor.js +62 -2
- package/dist/native/AppleDispatchCoverage.js +47 -6
- package/dist/native/AppleDispatchDecodeFacts.js +6 -0
- package/dist/native/AppleDispatchMetadata.js +6 -3
- package/dist/native/AppleMachoFixups.js +48 -24
- package/dist/native/AppleMachoSelection.js +32 -16
- package/dist/native/AppleObjcCategories.js +6 -2
- package/dist/native/AppleObjcDispatchFacets.js +5 -2
- package/dist/native/AppleObjcProperties.js +2 -1
- package/dist/native/AppleSwiftDispatchFacets.js +27 -21
- package/dist/native/AppleSwiftVtables.js +21 -15
- package/dist/native/CommandRunner.js +81 -84
- package/dist/native/LldbCallTracer.js +365 -102
- package/dist/native/LldbRetainedObservations.js +178 -0
- package/dist/native/NativeCallObservation.js +62 -3
- package/dist/native/NativeMacOSProvider.js +98 -17
- package/dist/native/SignatureTargetBinding.js +155 -0
- package/dist/native/parsers/lipo.js +71 -1
- package/dist/native/pwndbg/PwndbgCoreContext.js +25 -5
- package/dist/native/pwntools/RecordedCrashStructureBindings.js +2 -0
- package/dist/process/DarwinProcessRunTokenReader.js +8 -9
- package/dist/process/ExecFileOutput.js +20 -0
- package/dist/process/OwnedCommand.js +7 -2
- package/dist/process/ProcessOwnership.js +203 -53
- package/dist/process/ProcessOwnershipObservation.js +49 -4
- package/dist/process/ProviderProcess.js +38 -7
- package/dist/process/capture/ProcessCaptureError.js +2 -2
- package/dist/process/capture/ProcessCaptureLifecycle.js +100 -34
- package/dist/server/EvidenceMcpServer.js +146 -0
- package/dist/server/createServer.js +15 -8
- package/dist/server/registerAndroidTools.js +2 -4
- package/dist/server/registerApplicationTools/helpers.js +2 -4
- package/dist/server/registerArtifactComparisonTool.js +11 -7
- package/dist/server/registerBinaryDiagnosticsTools.js +2 -4
- package/dist/server/registerBrowserScenarioTool.js +2 -4
- package/dist/server/registerBrowserTools.js +2 -4
- package/dist/server/registerBundleComparisonTool.js +2 -3
- package/dist/server/registerCloseLifecycleTool.js +8 -18
- package/dist/server/registerElectronTools.js +2 -4
- package/dist/server/registerEnhancedTools.js +18 -59
- package/dist/server/registerEvidenceTools.js +2 -4
- package/dist/server/registerEvmTools.js +2 -4
- package/dist/server/registerFirmwareTools.js +2 -4
- package/dist/server/registerFunctionComparisonTool.js +10 -6
- package/dist/server/registerInvestigationTools.js +7 -7
- package/dist/server/registerJavaScriptRecoveryTool.js +2 -4
- package/dist/server/registerJavaScriptRuntimeObservationTools.js +2 -4
- package/dist/server/registerManagedWorkflowTools/compareManagedMembers.js +2 -4
- package/dist/server/registerManagedWorkflowTools/importManagedReconstruction.js +2 -4
- package/dist/server/registerManagedWorkflowTools/projectManagedApplicationGraph.js +2 -4
- package/dist/server/registerManagedWorkflowTools/verifyManagedNativeBoundaries.js +2 -4
- package/dist/server/registerOfficialTools.js +2 -4
- package/dist/server/registerProcessComparisonTool.js +71 -45
- package/dist/server/registerRecordedCrashTools.js +2 -4
- package/dist/server/registerSessionRecordTools.js +5 -6
- package/dist/server/registerSessionStatusTool.js +17 -3
- package/dist/server/registerSessionTools.js +11 -3
- package/dist/server/registerWebModuleTool.js +2 -4
- package/dist/server/registerWebNetworkCaptureTool.js +2 -4
- package/dist/server/registerWebRuntimeTools.js +3 -7
- package/dist/server/registerWebScriptTool.js +2 -4
- package/dist/server/registerWebSourceLocationTool.js +2 -4
- package/dist/server/runDerivedOperation.js +15 -3
- package/dist/server/sessionAvailabilityPolicy.js +0 -1
- package/dist/server/toolRegistrationOptions.js +2 -2
- package/dist/server/toolResult.js +62 -10
- package/dist/server/toolResultEncoding.js +33 -0
- package/install.sh +1 -1
- package/native/windows/build/manifest.json +1 -1
- package/package.json +8 -3
- package/scripts/electron-active-hook-boundaries.cjs +18 -20
- package/scripts/electron-active-hook.cjs +241 -37
- package/skills/reverse-engineer-anything/SKILL.md +11 -3
- package/skills/reverse-engineer-anything/references/javascript-applications.md +8 -1
- package/dist/ghidra/GhidraDefaults.js +0 -2
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { hopperOperationStage } from "./HopperOperationStage.js";
|
|
2
|
+
import { HopperCancelledError, HopperProcessError, HopperProtocolError, HopperRemoteError, HopperTimeoutError, } from "../domain/hopperErrors.js";
|
|
2
3
|
import { err } from "../domain/result.js";
|
|
4
|
+
/** Node timer ceiling; larger deadlines would be clamped instead of honored. */
|
|
5
|
+
const MAX_TIMEOUT_MS = 2_147_483_647;
|
|
3
6
|
/** FIFO that keeps the wire serialized until Hopper actually replies. */
|
|
4
7
|
export class HopperRequestQueue {
|
|
5
8
|
send;
|
|
@@ -8,10 +11,18 @@ export class HopperRequestQueue {
|
|
|
8
11
|
constructor(send) {
|
|
9
12
|
this.send = send;
|
|
10
13
|
}
|
|
11
|
-
/**
|
|
14
|
+
/** Settle the caller on reply, cancellation, or deadline; retain active wire work. */
|
|
12
15
|
run(id, method, params, options) {
|
|
13
16
|
if (options.signal?.aborted === true)
|
|
14
17
|
return Promise.resolve(err(new HopperCancelledError()));
|
|
18
|
+
const timeoutMs = options.timeoutMs;
|
|
19
|
+
if (timeoutMs !== undefined &&
|
|
20
|
+
(!Number.isInteger(timeoutMs) ||
|
|
21
|
+
timeoutMs < 0 ||
|
|
22
|
+
timeoutMs > MAX_TIMEOUT_MS))
|
|
23
|
+
return Promise.resolve(err(new HopperProtocolError(`Hopper timeoutMs must be an integer between 0 and ${String(MAX_TIMEOUT_MS)}`)));
|
|
24
|
+
if (timeoutMs === 0)
|
|
25
|
+
return Promise.resolve(err(new HopperTimeoutError(0, method, id, "not_started", hopperOperationStage(method))));
|
|
15
26
|
return new Promise((resolve) => {
|
|
16
27
|
let entry;
|
|
17
28
|
const onAbort = options.signal === undefined ? undefined : () => this.#cancel(entry);
|
|
@@ -25,12 +36,18 @@ export class HopperRequestQueue {
|
|
|
25
36
|
onAbort,
|
|
26
37
|
callerState: "waiting",
|
|
27
38
|
callerSettled: false,
|
|
39
|
+
transmitted: false,
|
|
28
40
|
startedAt: undefined,
|
|
29
41
|
heartbeat: undefined,
|
|
42
|
+
deadline: undefined,
|
|
43
|
+
deadlineAt: timeoutMs === undefined ? undefined : performance.now() + timeoutMs,
|
|
44
|
+
timeoutMs,
|
|
30
45
|
};
|
|
31
46
|
this.#queue.push(entry);
|
|
32
47
|
if (onAbort !== undefined)
|
|
33
48
|
options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
49
|
+
if (timeoutMs !== undefined)
|
|
50
|
+
entry.deadline = setTimeout(() => this.#cancel(entry, this.#timeoutError(entry, timeoutMs)), timeoutMs);
|
|
34
51
|
if (this.#active !== undefined || this.#queue.length > 1)
|
|
35
52
|
this.#report(entry, `${method} queued behind ${String(this.#size() - 1)} Hopper request(s)`);
|
|
36
53
|
this.#drain();
|
|
@@ -41,17 +58,10 @@ export class HopperRequestQueue {
|
|
|
41
58
|
const entry = this.#active;
|
|
42
59
|
if (entry === undefined || entry.id !== id)
|
|
43
60
|
return false;
|
|
61
|
+
this.#expireIfElapsed(entry);
|
|
44
62
|
this.#releaseWire(entry);
|
|
45
|
-
if (!entry.callerSettled)
|
|
46
|
-
|
|
47
|
-
? err(new HopperRemoteError(result.error.code, result.error.safeMessage, {
|
|
48
|
-
diagnosticType: result.error.diagnosticType,
|
|
49
|
-
operation: entry.method,
|
|
50
|
-
requestId: entry.id,
|
|
51
|
-
}))
|
|
52
|
-
: result;
|
|
53
|
-
this.#settleCaller(entry, contextual, "waiting");
|
|
54
|
-
}
|
|
63
|
+
if (!entry.callerSettled)
|
|
64
|
+
this.#settleCaller(entry, result.ok ? result : err(withRequestContext(result.error, entry)), "waiting");
|
|
55
65
|
this.#active = undefined;
|
|
56
66
|
this.#drain();
|
|
57
67
|
return true;
|
|
@@ -120,6 +130,10 @@ export class HopperRequestQueue {
|
|
|
120
130
|
const entry = this.#queue.shift();
|
|
121
131
|
if (entry === undefined)
|
|
122
132
|
return;
|
|
133
|
+
if (this.#expireIfElapsed(entry)) {
|
|
134
|
+
this.#drain();
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
123
137
|
if (entry.signal?.aborted === true) {
|
|
124
138
|
this.#settleCaller(entry, err(new HopperCancelledError()), "cancelled");
|
|
125
139
|
this.#drain();
|
|
@@ -128,6 +142,17 @@ export class HopperRequestQueue {
|
|
|
128
142
|
this.#active = entry;
|
|
129
143
|
entry.startedAt = performance.now();
|
|
130
144
|
this.#report(entry, `${entry.method} started on Hopper's serial bridge`);
|
|
145
|
+
this.#expireIfElapsed(entry);
|
|
146
|
+
// A progress observer may cancel or tear down synchronously before send.
|
|
147
|
+
// No native work exists yet, so it is safe to release this reserved slot.
|
|
148
|
+
if (this.#active !== entry || entry.callerSettled) {
|
|
149
|
+
if (this.#active === entry) {
|
|
150
|
+
this.#active = undefined;
|
|
151
|
+
this.#releaseWire(entry);
|
|
152
|
+
this.#drain();
|
|
153
|
+
}
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
131
156
|
entry.heartbeat = setInterval(() => {
|
|
132
157
|
if (entry.callerSettled || entry.startedAt === undefined)
|
|
133
158
|
return;
|
|
@@ -135,30 +160,44 @@ export class HopperRequestQueue {
|
|
|
135
160
|
this.#report(entry, `${entry.method} is still running in Hopper (${String(elapsed)} ms elapsed)`);
|
|
136
161
|
}, 1_000);
|
|
137
162
|
try {
|
|
138
|
-
|
|
163
|
+
entry.transmitted = true;
|
|
164
|
+
this.send({ id: entry.id, method: entry.method, params: entry.params }, () => this.accept(entry.id, err(new HopperProcessError(null, undefined, entry.method, entry.id, "unreachable", hopperOperationStage(entry.method)))));
|
|
139
165
|
}
|
|
140
166
|
catch (cause) {
|
|
141
167
|
this.accept(entry.id, err(new HopperProtocolError("Hopper socket write failed", { cause })));
|
|
142
168
|
}
|
|
143
169
|
}
|
|
144
|
-
#
|
|
170
|
+
#timeoutError(entry, timeoutMs) {
|
|
171
|
+
return new HopperTimeoutError(timeoutMs, entry.method, entry.id, entry.transmitted ? "busy" : "not_started", hopperOperationStage(entry.method));
|
|
172
|
+
}
|
|
173
|
+
#expireIfElapsed(entry) {
|
|
174
|
+
if (entry.deadlineAt !== undefined &&
|
|
175
|
+
entry.timeoutMs !== undefined &&
|
|
176
|
+
performance.now() >= entry.deadlineAt)
|
|
177
|
+
this.#settleCaller(entry, err(this.#timeoutError(entry, entry.timeoutMs)), "cancelled");
|
|
178
|
+
return entry.callerSettled;
|
|
179
|
+
}
|
|
180
|
+
#cancel(entry, error = new HopperCancelledError()) {
|
|
145
181
|
if (entry.callerSettled)
|
|
146
182
|
return;
|
|
147
183
|
if (this.#active === entry) {
|
|
148
|
-
this.#settleCaller(entry, err(
|
|
184
|
+
this.#settleCaller(entry, err(error), "cancelled");
|
|
149
185
|
return;
|
|
150
186
|
}
|
|
151
187
|
const index = this.#queue.indexOf(entry);
|
|
152
188
|
if (index < 0)
|
|
153
189
|
return;
|
|
154
190
|
this.#queue.splice(index, 1);
|
|
155
|
-
this.#settleCaller(entry, err(
|
|
191
|
+
this.#settleCaller(entry, err(error), "cancelled");
|
|
156
192
|
}
|
|
157
193
|
#settleCaller(entry, result, callerState) {
|
|
158
194
|
if (entry.callerSettled)
|
|
159
195
|
return;
|
|
160
196
|
entry.callerSettled = true;
|
|
161
197
|
entry.callerState = callerState;
|
|
198
|
+
if (entry.deadline !== undefined)
|
|
199
|
+
clearTimeout(entry.deadline);
|
|
200
|
+
entry.deadline = undefined;
|
|
162
201
|
if (this.#active === entry && entry.heartbeat !== undefined) {
|
|
163
202
|
clearInterval(entry.heartbeat);
|
|
164
203
|
entry.heartbeat = undefined;
|
|
@@ -195,6 +234,16 @@ export class HopperRequestQueue {
|
|
|
195
234
|
}
|
|
196
235
|
}
|
|
197
236
|
}
|
|
198
|
-
const withRequestContext = (error, entry) =>
|
|
199
|
-
|
|
200
|
-
|
|
237
|
+
const withRequestContext = (error, entry) => {
|
|
238
|
+
if (error instanceof HopperProcessError)
|
|
239
|
+
return new HopperProcessError(error.exitCode, error.diagnostic, error.operation ?? entry.method, error.requestId ?? entry.id, error.providerState, error.operation === undefined && error.stage === "connection"
|
|
240
|
+
? hopperOperationStage(entry.method)
|
|
241
|
+
: error.stage);
|
|
242
|
+
if (error instanceof HopperRemoteError)
|
|
243
|
+
return new HopperRemoteError(error.code, error.safeMessage, {
|
|
244
|
+
diagnosticType: error.diagnosticType,
|
|
245
|
+
operation: error.operation ?? entry.method,
|
|
246
|
+
requestId: error.requestId ?? entry.id,
|
|
247
|
+
});
|
|
248
|
+
return error;
|
|
249
|
+
};
|
|
@@ -4,10 +4,11 @@ import { createConnection, createServer, } from "node:net";
|
|
|
4
4
|
import { join, resolve } from "node:path";
|
|
5
5
|
import { HopperStartError } from "../domain/hopperErrors.js";
|
|
6
6
|
import { safeParseJson } from "../domain/safeJson.js";
|
|
7
|
-
|
|
7
|
+
/** Shared private parent of per-target lease sockets; it outlives individual sessions. */
|
|
8
|
+
export const HOPPER_TARGET_LEASE_DIRECTORY = join("/tmp", `rea-hopper-${process.getuid?.() ?? 0}`);
|
|
8
9
|
/** Keep two REA processes from opening duplicate Hopper documents for one target/profile. */
|
|
9
10
|
export const acquireHopperTargetLease = async (input) => {
|
|
10
|
-
const directory = input.directory ??
|
|
11
|
+
const directory = input.directory ?? HOPPER_TARGET_LEASE_DIRECTORY;
|
|
11
12
|
await ensureLeaseDirectory(directory);
|
|
12
13
|
// The canonical path only derives the lease key; it never authorizes access.
|
|
13
14
|
// Common expected `realpath` failures include a missing target or path
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { pathToFileURL } from "node:url";
|
|
2
2
|
import { browserOriginSchema, sanitizeBrowserUrl, } from "../domain/browserObservation.js";
|
|
3
|
+
import { AnalysisCancelledError } from "../domain/analysisErrorCore.js";
|
|
3
4
|
import { authorizedElectronFile } from "../browser/ElectronFileScope.js";
|
|
5
|
+
/** Throw the operation's typed cancellation error whenever its signal is aborted. */
|
|
6
|
+
export const throwIfRuntimeObservationCancelled = (signal) => {
|
|
7
|
+
if (signal?.aborted === true)
|
|
8
|
+
throw new AnalysisCancelledError("observe_javascript_runtime");
|
|
9
|
+
};
|
|
4
10
|
/**
|
|
5
11
|
* Wire bucket for inspector script exclusions. The V8 observation schemas
|
|
6
12
|
* fix the `unsupported_location` key, so every canonical denial reason maps
|
|
@@ -13,7 +19,8 @@ export const inspectorExclusionKey = (reason) => {
|
|
|
13
19
|
return "unsupported_location";
|
|
14
20
|
};
|
|
15
21
|
/** Resolve a protocol location exposed by the explicitly selected Inspector endpoint. */
|
|
16
|
-
export const authorizeRuntimeLocation = async (value) => {
|
|
22
|
+
export const authorizeRuntimeLocation = async (value, signal) => {
|
|
23
|
+
throwIfRuntimeObservationCancelled(signal);
|
|
17
24
|
if (value.startsWith("node:") && value.length > 0)
|
|
18
25
|
return {
|
|
19
26
|
allowed: true,
|
|
@@ -21,6 +28,7 @@ export const authorizeRuntimeLocation = async (value) => {
|
|
|
21
28
|
};
|
|
22
29
|
if (value.startsWith("file:")) {
|
|
23
30
|
const filePath = await authorizedElectronFile(value);
|
|
31
|
+
throwIfRuntimeObservationCancelled(signal);
|
|
24
32
|
return filePath === undefined
|
|
25
33
|
? { allowed: false, reason: "not_approved" }
|
|
26
34
|
: { allowed: true, location: { kind: "file", file_path: filePath } };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { authorizeRuntimeLocation, inspectorExclusionKey, } from "./JavaScriptRuntimeScope.js";
|
|
2
|
+
import { authorizeRuntimeLocation, inspectorExclusionKey, throwIfRuntimeObservationCancelled, } from "./JavaScriptRuntimeScope.js";
|
|
3
3
|
/** Empty durable-location exclusion counters. */
|
|
4
4
|
export const createInspectorExclusionCounts = () => ({
|
|
5
5
|
unsupported_location: 0,
|
|
@@ -15,7 +15,8 @@ export const describeInspectorTargetLimitations = () => [
|
|
|
15
15
|
];
|
|
16
16
|
const LOCATION_AUTHORIZATION_WORKERS = 8;
|
|
17
17
|
/** Canonically authorize, deduplicate, and sort one bounded raw capture. */
|
|
18
|
-
export const finalizeInspectorCapture = async ({ input, runtime, target, state, authorizeLocation = authorizeRuntimeLocation, }) => {
|
|
18
|
+
export const finalizeInspectorCapture = async ({ input, runtime, target, state, authorizeLocation = authorizeRuntimeLocation, signal, }) => {
|
|
19
|
+
throwIfRuntimeObservationCancelled(signal);
|
|
19
20
|
const exclusions = createInspectorExclusionCounts();
|
|
20
21
|
const scripts = new Map();
|
|
21
22
|
const draftsByUrl = new Map();
|
|
@@ -31,6 +32,7 @@ export const finalizeInspectorCapture = async ({ input, runtime, target, state,
|
|
|
31
32
|
const worker = async () => {
|
|
32
33
|
try {
|
|
33
34
|
for (;;) {
|
|
35
|
+
throwIfRuntimeObservationCancelled(signal);
|
|
34
36
|
if (stopScheduling)
|
|
35
37
|
return;
|
|
36
38
|
const index = nextGroup;
|
|
@@ -41,8 +43,9 @@ export const finalizeInspectorCapture = async ({ input, runtime, target, state,
|
|
|
41
43
|
const [rawUrl, drafts] = group;
|
|
42
44
|
authorizedGroups[index] = {
|
|
43
45
|
drafts,
|
|
44
|
-
decision: await authorizeLocation(rawUrl),
|
|
46
|
+
decision: await authorizeLocation(rawUrl, signal),
|
|
45
47
|
};
|
|
48
|
+
throwIfRuntimeObservationCancelled(signal);
|
|
46
49
|
}
|
|
47
50
|
}
|
|
48
51
|
catch (cause) {
|
|
@@ -52,6 +55,7 @@ export const finalizeInspectorCapture = async ({ input, runtime, target, state,
|
|
|
52
55
|
};
|
|
53
56
|
const workerResults = await Promise.allSettled(Array.from({ length: Math.min(LOCATION_AUTHORIZATION_WORKERS, groups.length) }, worker));
|
|
54
57
|
const failedWorker = workerResults.find((result) => result.status === "rejected");
|
|
58
|
+
throwIfRuntimeObservationCancelled(signal);
|
|
55
59
|
if (failedWorker?.status === "rejected")
|
|
56
60
|
throw failedWorker.reason;
|
|
57
61
|
for (const { drafts, decision } of authorizedGroups) {
|
|
@@ -81,6 +85,7 @@ export const finalizeInspectorCapture = async ({ input, runtime, target, state,
|
|
|
81
85
|
: left.context_key > right.context_key
|
|
82
86
|
? 1
|
|
83
87
|
: 0);
|
|
88
|
+
throwIfRuntimeObservationCancelled(signal);
|
|
84
89
|
return {
|
|
85
90
|
runtime,
|
|
86
91
|
target: {
|
|
@@ -12,6 +12,11 @@ import { discoverV8Inspector, } from "./V8InspectorEndpoint.js";
|
|
|
12
12
|
/** Public identity committed by passive V8 Inspector observations. */
|
|
13
13
|
export { V8_INSPECTOR_PROVIDER_IDENTITY } from "./providerIdentity.js";
|
|
14
14
|
import { V8_INSPECTOR_PROVIDER_IDENTITY } from "./providerIdentity.js";
|
|
15
|
+
/** Maximum decoded CDP message accepted while observing one Inspector target. */
|
|
16
|
+
export const INSPECTOR_MAX_PAYLOAD_BYTES = 16 * 1024 * 1024;
|
|
17
|
+
/** Aggregate retained metadata budget, including an estimate of JS object overhead. */
|
|
18
|
+
export const INSPECTOR_MAX_RETAINED_METADATA_BYTES = 8 * 1024 * 1024;
|
|
19
|
+
const RETAINED_OBJECT_OVERHEAD_BYTES = 512;
|
|
15
20
|
/** Attach-only provider; sends only Runtime.enable and Debugger.enable. */
|
|
16
21
|
export class V8InspectorProvider {
|
|
17
22
|
identity() {
|
|
@@ -61,7 +66,7 @@ export class V8InspectorProvider {
|
|
|
61
66
|
const target = await authorizedTarget(discovery.targets, input, discovery.runtime.product);
|
|
62
67
|
if (input.runtime_kind !== undefined)
|
|
63
68
|
assertRuntimeKind(target, input.runtime_kind);
|
|
64
|
-
connection = await CdpConnection.connect(target.webSocketUrl, "observe_javascript_runtime", options.signal);
|
|
69
|
+
connection = await CdpConnection.connect(target.webSocketUrl, "observe_javascript_runtime", options.signal, { maxPayloadBytes: INSPECTOR_MAX_PAYLOAD_BYTES });
|
|
65
70
|
const state = emptyCaptureState();
|
|
66
71
|
const removeListener = connection.onEvent((event) => ingestEvent(event, input, state));
|
|
67
72
|
try {
|
|
@@ -77,6 +82,7 @@ export class V8InspectorProvider {
|
|
|
77
82
|
runtime: discovery.runtime,
|
|
78
83
|
target,
|
|
79
84
|
state,
|
|
85
|
+
...(options.signal === undefined ? {} : { signal: options.signal }),
|
|
80
86
|
});
|
|
81
87
|
outcome = ok(javascriptRuntimeObservationSchema.parse(result));
|
|
82
88
|
}
|
|
@@ -206,15 +212,24 @@ const ingestScript = (event, input, state) => {
|
|
|
206
212
|
length: nonnegativeInteger(value?.length),
|
|
207
213
|
isModule: value?.isModule === true,
|
|
208
214
|
};
|
|
209
|
-
const bytes =
|
|
210
|
-
retainEvent(state, bytes)
|
|
215
|
+
const bytes = retainedMetadataBytes(draft);
|
|
216
|
+
if (!retainEvent(state, bytes))
|
|
217
|
+
return;
|
|
211
218
|
state.scripts.push(draft);
|
|
212
219
|
};
|
|
213
220
|
const ingestContext = (event, input, state) => {
|
|
214
221
|
if (event.method === "Runtime.executionContextsCleared") {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
222
|
+
let byteDelta = 0;
|
|
223
|
+
for (const context of state.contexts.values()) {
|
|
224
|
+
byteDelta +=
|
|
225
|
+
retainedMetadataBytes({ ...context, state: "cleared" }) -
|
|
226
|
+
retainedMetadataBytes(context);
|
|
227
|
+
}
|
|
228
|
+
if (replaceRetainedMetadata(state, byteDelta)) {
|
|
229
|
+
for (const context of state.contexts.values())
|
|
230
|
+
context.state = "cleared";
|
|
231
|
+
retainEvent(state, 0);
|
|
232
|
+
}
|
|
218
233
|
return;
|
|
219
234
|
}
|
|
220
235
|
const parameters = recordValue(event.params);
|
|
@@ -238,18 +253,33 @@ const ingestContext = (event, input, state) => {
|
|
|
238
253
|
: "destroyed",
|
|
239
254
|
origin: origin === "" ? null : origin,
|
|
240
255
|
};
|
|
241
|
-
const
|
|
242
|
-
|
|
256
|
+
const previous = state.contexts.get(key);
|
|
257
|
+
const bytes = retainedMetadataBytes(draft) -
|
|
258
|
+
(previous === undefined ? 0 : retainedMetadataBytes(previous));
|
|
259
|
+
if (!retainEvent(state, bytes))
|
|
260
|
+
return;
|
|
243
261
|
state.contexts.set(key, draft);
|
|
244
262
|
};
|
|
245
263
|
const retainEvent = (state, bytes) => {
|
|
246
|
-
state
|
|
264
|
+
if (!replaceRetainedMetadata(state, bytes))
|
|
265
|
+
return false;
|
|
247
266
|
state.eventsRetained += 1;
|
|
267
|
+
return true;
|
|
268
|
+
};
|
|
269
|
+
const replaceRetainedMetadata = (state, byteDelta) => {
|
|
270
|
+
if (state.metadataBytes + byteDelta > INSPECTOR_MAX_RETAINED_METADATA_BYTES) {
|
|
271
|
+
state.eventsDropped += 1;
|
|
272
|
+
state.truncated = true;
|
|
273
|
+
state.truncationReasons.add("retained_metadata_budget_exceeded");
|
|
274
|
+
return false;
|
|
275
|
+
}
|
|
276
|
+
state.metadataBytes += byteDelta;
|
|
277
|
+
return true;
|
|
248
278
|
};
|
|
249
279
|
const waitForCapture = async (connection, observationMs, signal) => {
|
|
250
280
|
let removeDisconnect = () => undefined;
|
|
251
281
|
const disconnected = new Promise((_resolve, reject) => {
|
|
252
|
-
removeDisconnect = connection.onDisconnect(() => reject(
|
|
282
|
+
removeDisconnect = connection.onDisconnect((error) => reject(error));
|
|
253
283
|
});
|
|
254
284
|
try {
|
|
255
285
|
await Promise.race([
|
|
@@ -261,7 +291,13 @@ const waitForCapture = async (connection, observationMs, signal) => {
|
|
|
261
291
|
removeDisconnect();
|
|
262
292
|
}
|
|
263
293
|
};
|
|
264
|
-
const
|
|
294
|
+
const retainedMetadataBytes = (value) => {
|
|
295
|
+
const strings = "rawUrl" in value
|
|
296
|
+
? [value.rawUrl, value.rawUrl, value.executionContextKey, value.cdpHash]
|
|
297
|
+
: [value.contextKey, value.origin];
|
|
298
|
+
return (RETAINED_OBJECT_OVERHEAD_BYTES +
|
|
299
|
+
strings.reduce((total, text) => total + (text?.length ?? 0) * 2, 0));
|
|
300
|
+
};
|
|
265
301
|
const nonnegativeInteger = (value) => {
|
|
266
302
|
const parsed = numberValue(value);
|
|
267
303
|
return parsed === undefined ? 0 : Math.max(0, Math.trunc(parsed));
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { TraceMap, decodedMappings } from "@jridgewell/trace-mapping";
|
|
2
|
+
import { SourceMapFormatFailure, } from "./SourceMapFormat.js";
|
|
3
|
+
/** Determine whether a leaf-local mapping falls before its enclosing boundary. */
|
|
4
|
+
export const isBeforeSourceMapLeafStop = (leaf, line, column) => {
|
|
5
|
+
if (leaf.stop === null)
|
|
6
|
+
return true;
|
|
7
|
+
const generatedLine = leaf.offset.line + line;
|
|
8
|
+
const generatedColumn = column + (line === 0 ? leaf.offset.column : 0);
|
|
9
|
+
return (generatedLine < leaf.stop.line ||
|
|
10
|
+
(generatedLine === leaf.stop.line && generatedColumn < leaf.stop.column));
|
|
11
|
+
};
|
|
12
|
+
/** Resolve one declaration with the same pinned upstream implementation used by full maps. */
|
|
13
|
+
export const resolveSourceMapSource = (source, sourceRoot, mapUrl) => {
|
|
14
|
+
const map = new TraceMap({
|
|
15
|
+
version: 3,
|
|
16
|
+
names: [],
|
|
17
|
+
sources: [source ?? ""],
|
|
18
|
+
...(sourceRoot == null ? {} : { sourceRoot }),
|
|
19
|
+
mappings: "",
|
|
20
|
+
}, mapUrl);
|
|
21
|
+
const resolved = map.resolvedSources[0];
|
|
22
|
+
if (resolved === undefined)
|
|
23
|
+
throw new SourceMapFormatFailure("format", "Source-map resolver did not return the requested source identity.");
|
|
24
|
+
return resolved;
|
|
25
|
+
};
|
|
26
|
+
/** Decode leaf mappings and validate producer indices before any consumer publishes them. */
|
|
27
|
+
export const decodeValidatedSourceMapLeaves = (leaves, mapUrl, check) => leaves.map((leaf) => {
|
|
28
|
+
check?.();
|
|
29
|
+
const map = new TraceMap(JSON.stringify(leaf.map), mapUrl);
|
|
30
|
+
const rows = decodedMappings(map);
|
|
31
|
+
for (const [line, row] of rows.entries()) {
|
|
32
|
+
if ((line & 255) === 0)
|
|
33
|
+
check?.();
|
|
34
|
+
for (const segment of row) {
|
|
35
|
+
const generatedLine = leaf.offset.line + line;
|
|
36
|
+
const generatedColumn = segment[0] + (line === 0 ? leaf.offset.column : 0);
|
|
37
|
+
if (!Number.isSafeInteger(generatedLine) ||
|
|
38
|
+
!Number.isSafeInteger(generatedColumn) ||
|
|
39
|
+
![1, 4, 5].includes(segment.length) ||
|
|
40
|
+
segment.some((value) => !Number.isSafeInteger(value) || value < 0) ||
|
|
41
|
+
(segment.length !== 1 && segment[1] >= leaf.map.sources.length) ||
|
|
42
|
+
(segment.length === 5 && segment[4] >= leaf.map.names.length))
|
|
43
|
+
throw new SourceMapFormatFailure("format", "Decoded local or absolute position, source index or name index is invalid.");
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return { leaf, rows, resolvedSources: map.resolvedSources };
|
|
47
|
+
});
|
|
@@ -12,10 +12,26 @@ import { PrivateRuntimeRoot, PrivateRuntimeRootUnavailableError, } from "../../p
|
|
|
12
12
|
import { ProviderStartupDeadline } from "../../process/ProviderDeadline.js";
|
|
13
13
|
import { cleanupOwnedProcessGroup } from "../../process/ProcessOwnership.js";
|
|
14
14
|
import { ProviderProcessSupervisor, spawnOwnedProviderProcess, } from "../../process/ProviderProcess.js";
|
|
15
|
+
import { prepareProcessOwnershipInspection } from "../../process/ProcessOwnershipObservation.js";
|
|
15
16
|
const OPERATION = "trace_web_source_location";
|
|
16
17
|
const PROVIDER = "source-map-decoder";
|
|
17
18
|
/** Run a trusted codec command and release its process/root before returning output. */
|
|
18
19
|
export const runSourceMapCommand = async (request, path, options, dependencies) => {
|
|
20
|
+
try {
|
|
21
|
+
await (dependencies.prepareOwnership ?? prepareProcessOwnershipInspection)(options?.signal);
|
|
22
|
+
}
|
|
23
|
+
catch (cause) {
|
|
24
|
+
return err(options?.signal?.aborted === true
|
|
25
|
+
? new AnalysisCancelledError(OPERATION)
|
|
26
|
+
: new ProviderAdapterError(PROVIDER, OPERATION, {
|
|
27
|
+
cause,
|
|
28
|
+
diagnostics: {
|
|
29
|
+
map_path: path,
|
|
30
|
+
error_message: message(cause),
|
|
31
|
+
phase: "process_ownership_preparation",
|
|
32
|
+
},
|
|
33
|
+
}));
|
|
34
|
+
}
|
|
19
35
|
const deadline = new ProviderStartupDeadline(WEB_SOURCE_MAP_LIMITS.decodeTimeoutMs, options?.signal);
|
|
20
36
|
const command = new OwnedCodec(path, dependencies, deadline);
|
|
21
37
|
let result;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { WEB_SOURCE_MAP_LIMITS } from "../../domain/webSourceLocation.js";
|
|
3
|
+
import { hasValidSourceMapContents } from "../../domain/sourceMapContents.js";
|
|
3
4
|
const leafSchema = z.object({
|
|
4
5
|
version: z.literal(3),
|
|
5
6
|
file: z.string().nullable().optional(),
|
|
@@ -48,9 +49,21 @@ const addOffset = (base, offset) => {
|
|
|
48
49
|
throw new SourceMapFormatFailure("limit", "Indexed offset exceeds the 262144 generated-row decoder budget.");
|
|
49
50
|
return result;
|
|
50
51
|
};
|
|
51
|
-
const
|
|
52
|
+
const consumeRecords = (budget, count) => {
|
|
53
|
+
if (count > WEB_SOURCE_MAP_LIMITS.decodedRecords - budget.records)
|
|
54
|
+
throw new SourceMapFormatFailure("limit", `Source-map structure exceeds the ${WEB_SOURCE_MAP_LIMITS.decodedRecords}-record decoder budget.`);
|
|
55
|
+
budget.records += count;
|
|
56
|
+
};
|
|
57
|
+
const addSections = (node, pending, budget, options) => {
|
|
58
|
+
options.check?.();
|
|
59
|
+
const header = mapHeaderSchema.parse(node.value);
|
|
60
|
+
if (!Array.isArray(header.sections))
|
|
61
|
+
throw new SourceMapFormatFailure("format", "Indexed source-map sections must be an array.");
|
|
62
|
+
consumeRecords(budget, header.sections.length);
|
|
52
63
|
const sections = indexedSchema.parse(node.value).sections;
|
|
53
64
|
for (const [index, section] of sections.entries()) {
|
|
65
|
+
if ((index & 255) === 0)
|
|
66
|
+
options.check?.();
|
|
54
67
|
const previous = sections[index - 1];
|
|
55
68
|
if (previous !== undefined && compare(section.offset, previous.offset) <= 0)
|
|
56
69
|
throw new SourceMapFormatFailure("format", "Indexed section offsets must be strictly increasing.");
|
|
@@ -81,7 +94,7 @@ const addSections = (node, pending) => {
|
|
|
81
94
|
});
|
|
82
95
|
}
|
|
83
96
|
};
|
|
84
|
-
const inspectMappingLayout = (mappings) => {
|
|
97
|
+
const inspectMappingLayout = (mappings, check) => {
|
|
85
98
|
let lines = 1;
|
|
86
99
|
let segments = 0;
|
|
87
100
|
let components = 0;
|
|
@@ -94,7 +107,10 @@ const inspectMappingLayout = (mappings) => {
|
|
|
94
107
|
if (inSegment && ![1, 4, 5].includes(components))
|
|
95
108
|
throw new SourceMapFormatFailure("format", "Source-map segment requires one, four or five VLQ components.");
|
|
96
109
|
};
|
|
110
|
+
let index = 0;
|
|
97
111
|
for (const character of mappings) {
|
|
112
|
+
if ((index++ & 0x3fff) === 0)
|
|
113
|
+
check?.();
|
|
98
114
|
if (character === "," || character === ";") {
|
|
99
115
|
finish();
|
|
100
116
|
inSegment = false;
|
|
@@ -123,6 +139,35 @@ const inspectMappingLayout = (mappings) => {
|
|
|
123
139
|
finish();
|
|
124
140
|
return { lines, segments };
|
|
125
141
|
};
|
|
142
|
+
const inspectLeaf = (node, budget, options) => {
|
|
143
|
+
options.check?.();
|
|
144
|
+
consumeRecords(budget, 1);
|
|
145
|
+
if (typeof node.value !== "object" ||
|
|
146
|
+
node.value === null ||
|
|
147
|
+
Array.isArray(node.value))
|
|
148
|
+
return leafSchema.parse(node.value);
|
|
149
|
+
const rawMap = node.value;
|
|
150
|
+
const rawSources = rawMap.sources;
|
|
151
|
+
const rawNames = rawMap.names;
|
|
152
|
+
const rawContents = rawMap.sourcesContent;
|
|
153
|
+
consumeRecords(budget, Array.isArray(rawSources) ? rawSources.length : 0);
|
|
154
|
+
consumeRecords(budget, Array.isArray(rawNames) ? rawNames.length : 0);
|
|
155
|
+
consumeRecords(budget, Array.isArray(rawContents) ? rawContents.length : 0);
|
|
156
|
+
options.check?.();
|
|
157
|
+
if (options.profile === "browser-collection" && !Array.isArray(rawNames))
|
|
158
|
+
throw new SourceMapFormatFailure("format", "Source-map leaf requires a names array.");
|
|
159
|
+
if (options.profile === "browser-collection" &&
|
|
160
|
+
!hasValidSourceMapContents(Array.isArray(rawSources) ? rawSources.length : 0, rawContents))
|
|
161
|
+
throw new SourceMapFormatFailure("format", "Source-map sourcesContent must align with its source declarations.");
|
|
162
|
+
options.check?.();
|
|
163
|
+
const leaf = leafSchema.parse(node.value);
|
|
164
|
+
const layout = inspectMappingLayout(leaf.mappings, options.check);
|
|
165
|
+
consumeRecords(budget, layout.segments + layout.lines);
|
|
166
|
+
if (layout.segments > WEB_SOURCE_MAP_LIMITS.decodedSegments ||
|
|
167
|
+
node.offset.line + layout.lines > WEB_SOURCE_MAP_LIMITS.decodedRows)
|
|
168
|
+
throw new SourceMapFormatFailure("limit", "Source-map layout exceeds the 262144 segment/generated-row decoder budget.");
|
|
169
|
+
return leaf;
|
|
170
|
+
};
|
|
126
171
|
/** Validate structure before allocation, retaining raw declarations and section boundaries. */
|
|
127
172
|
export const inspectSourceMap = (text) => {
|
|
128
173
|
const withoutBom = text.replace(/^\uFEFF/u, "");
|
|
@@ -130,53 +175,56 @@ export const inspectSourceMap = (text) => {
|
|
|
130
175
|
? withoutBom.slice(withoutBom.indexOf("\n") + 1)
|
|
131
176
|
: withoutBom;
|
|
132
177
|
const value = JSON.parse(jsonText);
|
|
178
|
+
const inspected = inspectSourceMapValue(value);
|
|
179
|
+
const normalized = inspected.format === "regular"
|
|
180
|
+
? inspected.leaves[0]?.map
|
|
181
|
+
: {
|
|
182
|
+
version: 3,
|
|
183
|
+
sections: inspected.leaves.map((leaf) => ({
|
|
184
|
+
offset: leaf.offset,
|
|
185
|
+
map: leaf.map,
|
|
186
|
+
})),
|
|
187
|
+
};
|
|
188
|
+
return {
|
|
189
|
+
jsonText: JSON.stringify(normalized),
|
|
190
|
+
...inspected,
|
|
191
|
+
};
|
|
192
|
+
};
|
|
193
|
+
/** Return validated leaves when a consumer needs decoded rows after preflight. */
|
|
194
|
+
export const inspectSourceMapValue = (value, options = {}) => {
|
|
195
|
+
options.check?.();
|
|
133
196
|
const root = mapHeaderSchema.parse(value);
|
|
134
197
|
const declarations = [];
|
|
135
198
|
const leaves = [];
|
|
136
199
|
const pending = [
|
|
137
200
|
{ value, path: [], offset: { line: 0, column: 0 }, stop: null },
|
|
138
201
|
];
|
|
139
|
-
|
|
202
|
+
const budget = options.budget ?? { records: 0 };
|
|
140
203
|
while (pending.length > 0) {
|
|
204
|
+
options.check?.();
|
|
141
205
|
const node = pending.pop();
|
|
142
206
|
if (node === undefined)
|
|
143
207
|
break;
|
|
144
208
|
if (node.path.length > WEB_SOURCE_MAP_LIMITS.sectionDepth)
|
|
145
209
|
throw new SourceMapFormatFailure("limit", "Indexed source-map nesting exceeds the 64-level decoder stack budget.");
|
|
146
210
|
if (mapHeaderSchema.parse(node.value).sections !== undefined) {
|
|
147
|
-
addSections(node, pending);
|
|
211
|
+
addSections(node, pending, budget, options);
|
|
148
212
|
continue;
|
|
149
213
|
}
|
|
150
|
-
const leaf =
|
|
151
|
-
const layout = inspectMappingLayout(leaf.mappings);
|
|
152
|
-
segmentCount += layout.segments;
|
|
153
|
-
if (segmentCount > WEB_SOURCE_MAP_LIMITS.decodedSegments ||
|
|
154
|
-
node.offset.line + layout.lines > WEB_SOURCE_MAP_LIMITS.decodedRows)
|
|
155
|
-
throw new SourceMapFormatFailure("limit", "Source-map layout exceeds the 262144 segment/generated-row decoder budget.");
|
|
214
|
+
const leaf = inspectLeaf(node, budget, options);
|
|
156
215
|
leaves.push({ map: leaf, offset: node.offset, stop: node.stop });
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
216
|
+
if (options.profile !== "browser-collection")
|
|
217
|
+
for (const [sourceIndex, source] of leaf.sources.entries())
|
|
218
|
+
declarations.push({
|
|
219
|
+
sectionPath: node.path,
|
|
220
|
+
sourceIndex,
|
|
221
|
+
source,
|
|
222
|
+
root: leaf.sourceRoot ?? null,
|
|
223
|
+
content: leaf.sourcesContent?.[sourceIndex] ?? null,
|
|
224
|
+
ignored: (leaf.ignoreList ?? leaf.x_google_ignoreList ?? []).includes(sourceIndex),
|
|
225
|
+
});
|
|
166
226
|
}
|
|
167
|
-
// Absolute leaves adapt nested first-line column offsets before upstream AnyMap.
|
|
168
|
-
// Raw map bytes and the original section/source identities are retained separately.
|
|
169
|
-
const normalized = root.sections === undefined
|
|
170
|
-
? leaves[0]?.map
|
|
171
|
-
: {
|
|
172
|
-
version: 3,
|
|
173
|
-
sections: leaves.map((leaf) => ({
|
|
174
|
-
offset: leaf.offset,
|
|
175
|
-
map: leaf.map,
|
|
176
|
-
})),
|
|
177
|
-
};
|
|
178
227
|
return {
|
|
179
|
-
jsonText: JSON.stringify(normalized),
|
|
180
228
|
format: root.sections === undefined ? "regular" : "indexed",
|
|
181
229
|
reportedFile: root.file ?? null,
|
|
182
230
|
declarations,
|