diff --git a/src/application/BinarySessionComposition.ts b/src/application/BinarySessionComposition.ts new file mode 100644 index 0000000000..327c52f925 --- /dev/null +++ b/src/application/BinarySessionComposition.ts @@ -0,0 +1,62 @@ +import type { + AnalysisClientFactory, + AnalysisProfileResolution, + AnalysisProfileResolutionOptions, + AnalysisProvider, +} from "./AnalysisProvider.js"; +import { AnalysisProviderRegistry } from "./AnalysisProviderRegistry.js"; +import { BinarySession } from "./BinarySession.js"; +import { SessionProviderRouter } from "./SessionProviderRouter.js"; +import type { BinaryTarget } from "../domain/binaryTarget.js"; +import type { AnalysisError } from "../domain/errors.js"; +import type { Result } from "../domain/result.js"; + +type BinarySessionOptions = { + readonly resolveAnalysisProfile?: ( + target: BinaryTarget, + options?: AnalysisProfileResolutionOptions, + ) => Promise>; +}; + +type BinarySessionOptionsOrResolver = + | BinarySessionOptions + | BinarySessionOptions["resolveAnalysisProfile"]; + +const normalizeBinarySessionOptions = ( + optionsOrResolver?: BinarySessionOptionsOrResolver, +): BinarySessionOptions => + typeof optionsOrResolver === "function" + ? { resolveAnalysisProfile: optionsOrResolver } + : (optionsOrResolver ?? {}); + +/** + * Compose one provider-neutral session from deep-provider selection and + * disjoint auxiliary operation families. + */ +export const composeBinarySession = ( + registry: AnalysisProviderRegistry | SessionProviderRouter, + auxiliaryProviders: readonly AnalysisProvider[] = [], +): BinarySession => + new BinarySession( + registry instanceof SessionProviderRouter + ? registry + : SessionProviderRouter.selectable(registry, auxiliaryProviders), + ); + +/** + * Compose a session through the same internal seam for focused adapter tests. + * The constructor remains private to this composition module, so callers test + * provider behavior without coupling to BinarySession construction. + */ +export const composeBinarySessionFromFactory = ( + provider: AnalysisProvider | AnalysisClientFactory | SessionProviderRouter, + optionsOrResolver?: BinarySessionOptionsOrResolver, +): BinarySession => + new BinarySession(provider, normalizeBinarySessionOptions(optionsOrResolver)); + +/** Compose a focused session from any provider-shaped test seam. */ +export const composeBinarySessionFromProvider = ( + provider: AnalysisProvider | AnalysisClientFactory | SessionProviderRouter, + optionsOrResolver?: BinarySessionOptionsOrResolver, +): BinarySession => + new BinarySession(provider, normalizeBinarySessionOptions(optionsOrResolver)); diff --git a/src/application/LoopbackReplay.ts b/src/application/LoopbackReplay.ts index 8450c08533..cd8d4ae30c 100644 --- a/src/application/LoopbackReplay.ts +++ b/src/application/LoopbackReplay.ts @@ -500,12 +500,12 @@ export const startLoopbackReplay = async ( async close() { closePromise ??= (async () => { recorder.stopMachineAdmission(); + await recorder.drainMachine(); for (const client of websocket.clients) client.terminate(); await new Promise((resolveClose) => websocket.close(() => resolveClose()), ); await closeReplayServer(server); - await recorder.drainMachine(); })(); await closePromise; }, diff --git a/src/application/SessionProviderRouter.ts b/src/application/SessionProviderRouter.ts index cd567f52f0..4c7ce74d6b 100644 --- a/src/application/SessionProviderRouter.ts +++ b/src/application/SessionProviderRouter.ts @@ -49,8 +49,8 @@ export interface SessionProviderRoute { ): AnalysisClient; } -interface LegacyProviderRuntime { - readonly kind: "legacy"; +interface SingleProviderRuntime { + readonly kind: "single"; readonly provider: AnalysisProvider | AnalysisClientFactory; readonly identity: ProviderIdentity; readonly capabilities: ReadonlyMap | undefined; @@ -71,7 +71,7 @@ interface SelectableProviderRuntime { /** Resolve either the compatibility single-provider seam or a selectable set. */ export class SessionProviderRouter { private constructor( - private readonly runtime: LegacyProviderRuntime | SelectableProviderRuntime, + private readonly runtime: SingleProviderRuntime | SelectableProviderRuntime, ) {} /** Preserve the existing provider/factory seam used by focused embedders. */ @@ -104,7 +104,7 @@ export class SessionProviderRouter { provider.resolveAnalysisProfile?.(target, resolutionOptions) ?? Promise.resolve(ok({ profile: null, compatibility: {} }))); return new SessionProviderRouter({ - kind: "legacy", + kind: "single", provider, identity, capabilities, @@ -148,7 +148,7 @@ export class SessionProviderRouter { /** Provider identities exposed through the flat 1.x compatibility field. */ providerIdentities(route: SessionProviderRoute): readonly ProviderIdentity[] { - if (this.runtime.kind === "legacy") { + if (this.runtime.kind === "single") { if (route.capabilities === undefined) return [route.identity]; return uniqueProviderIdentities( [...route.capabilities.values()].map(({ provider }) => provider), @@ -177,7 +177,7 @@ export class SessionProviderRouter { binding: null, selection: undefined, createClient: (target, context) => - createLegacyClient(runtime, target, undefined, context), + createSingleProviderClient(runtime, target, undefined, context), }; } @@ -231,7 +231,7 @@ export class SessionProviderRouter { binding: null, selection: undefined, createClient: (openedTarget, context) => - createLegacyClient( + createSingleProviderClient( runtime, openedTarget, normalized.value.profile ?? undefined, @@ -244,7 +244,7 @@ export class SessionProviderRouter { candidateStatuses( route: SessionProviderRoute, ): readonly AnalysisProviderCandidateStatus[] { - if (this.runtime.kind === "legacy") return []; + if (this.runtime.kind === "single") return []; return route.selection?.candidates ?? this.runtime.registry.candidates(); } @@ -292,8 +292,8 @@ const selectableRoute = ( }; }; -const createLegacyClient = ( - runtime: LegacyProviderRuntime, +const createSingleProviderClient = ( + runtime: SingleProviderRuntime, target: BinaryTarget, profile: AnalysisProfileCommitment | undefined, context: AnalysisClientContext, diff --git a/src/application/runtime.ts b/src/application/runtime.ts index 045d6a004e..d32c3e2531 100644 --- a/src/application/runtime.ts +++ b/src/application/runtime.ts @@ -1,12 +1,12 @@ import type { AppConfig } from "../config.js"; -import { BinarySession } from "./BinarySession.js"; +import type { BinarySession } from "./BinarySession.js"; import { HopperProvider } from "../hopper/HopperProvider.js"; import { GhidraProvider } from "../ghidra/GhidraProvider.js"; import { silentLogger, type Logger } from "../logger.js"; import { GENERATED_AUXILIARY_PROVIDERS } from "../generatedMcpToolCatalog.js"; import { AnalysisProviderRegistry } from "./AnalysisProviderRegistry.js"; +import { composeBinarySession } from "./BinarySessionComposition.js"; import { LazyAnalysisProvider } from "./LazyAnalysisProvider.js"; -import { SessionProviderRouter } from "./SessionProviderRouter.js"; /** * Compose the target-switching runtime shared directly by CLI and MCP adapters. @@ -34,32 +34,30 @@ export const createBinarySession = ( throw new TypeError(`Missing generated provider metadata for ${id}`); return new LazyAnalysisProvider({ ...generated, load }); }; - return new BinarySession( - SessionProviderRouter.selectable( - new AnalysisProviderRegistry([hopper, ghidra], config.analysisProvider), - [ - lazyProvider("rea-artifact-graph", async () => { - const { ArtifactProvider } = await import( - "../artifacts/ArtifactProvider.js" - ); - return new ArtifactProvider( - config.artifactNativeMountEnabled, - config.artifactIntegrityContinueEnabled, - ); - }), - lazyProvider("native-macos", async () => { - const { NativeMacOSProvider } = await import( - "../native/NativeMacOSProvider.js" - ); - return new NativeMacOSProvider(); - }), - lazyProvider("rea-dotnet-static", async () => { - const { ManagedStaticProvider } = await import( - "../dotnet/ManagedStaticProvider.js" - ); - return new ManagedStaticProvider(); - }), - ], - ), + return composeBinarySession( + new AnalysisProviderRegistry([hopper, ghidra], config.analysisProvider), + [ + lazyProvider("rea-artifact-graph", async () => { + const { ArtifactProvider } = await import( + "../artifacts/ArtifactProvider.js" + ); + return new ArtifactProvider( + config.artifactNativeMountEnabled, + config.artifactIntegrityContinueEnabled, + ); + }), + lazyProvider("native-macos", async () => { + const { NativeMacOSProvider } = await import( + "../native/NativeMacOSProvider.js" + ); + return new NativeMacOSProvider(); + }), + lazyProvider("rea-dotnet-static", async () => { + const { ManagedStaticProvider } = await import( + "../dotnet/ManagedStaticProvider.js" + ); + return new ManagedStaticProvider(); + }), + ], ); }; diff --git a/src/domain/mobileApplicationInvestigation.ts b/src/domain/mobileApplicationInvestigation.ts index 2ec0813428..95a4b531f1 100644 --- a/src/domain/mobileApplicationInvestigation.ts +++ b/src/domain/mobileApplicationInvestigation.ts @@ -43,8 +43,6 @@ export const iosManifestSchema = z.strictObject({ ui_required_device_capabilities: z.array(z.string()).default([]), app_permissions: z.array(z.string()).default([]), }); -export type IosManifest = z.infer; - /** Signing information. */ export const signingInfoSchema = z.strictObject({ is_signed: z.boolean().default(false), diff --git a/src/domain/objcSwiftMetadata.ts b/src/domain/objcSwiftMetadata.ts index 65181a5fc4..d2bb80bd7c 100644 --- a/src/domain/objcSwiftMetadata.ts +++ b/src/domain/objcSwiftMetadata.ts @@ -85,8 +85,6 @@ export const swiftAccessLevelSchema = z.enum([ "public", "open", ]); -export type SwiftAccessLevel = z.infer; - /** Swift declaration metadata. */ export const swiftDeclSchema = z.strictObject({ kind: swiftDeclKindSchema,