import { permitsCapabilityFallback } from "./CapabilityFallback.js"; import { listNamesFact } from "../domain/native/nativeOperationFacts.js"; import { executeFact } from "./AnalysisOperationFacts.js"; import type { AnalysisError } from "../domain/analysisErrorBase.js"; import { AnalysisInputError, AnalysisOutputError, cancelledBy, } from "../domain/analysisErrorCore.js"; import { err, ok, type Result } from "../domain/result.js"; import type { AnalysisOperationPort } from "./AnalysisProvider.js"; import { resolveProcedureAddress } from "./ProcedureAddressResolution.js"; /** Resolve an exact symbol after the provider rejects the caller address format. */ export const resolveXrefsAddress = async ( analysis: AnalysisOperationPort, selector: string, signal: AbortSignal, ): Promise> => { const named = await resolveXrefsName(analysis, selector, signal); if (!named.ok) return named; return named.value !== null ? ok(named.value) : err( new AnalysisInputError( "xrefs", { conformsToInputContract: true, remediationAction: "For a function, use rea search --kind procedures to find an analyzed address. For exports, inspect-macho (Mach-O) or inspect-native-exports (PE/ELF) may help; exports can omit local symbols and use different address coordinates. Verify the address in the selected provider, then retry xrefs.", }, [ { path: ["address"], reason: "invalid_value", message: `Unknown analyzed symbol name ${JSON.stringify(selector)}. Use an exact analyzed name or a hexadecimal address.`, }, ], ), ); }; /** Exact name resolution has one ambiguity owner for enhanced tools and CLI fallback. */ export const resolveXrefsName = async ( analysis: AnalysisOperationPort, selector: string, signal?: AbortSignal, operation: "xrefs" | "find_xrefs_to_name" = "xrefs", ): Promise> => { if (signal?.aborted) return err(cancelledBy(operation, signal)); const inventory = await executeFact( analysis, listNamesFact, {}, signal === undefined ? {} : { signal }, ); if (!inventory.ok) { if ( operation === "xrefs" && permitsCapabilityFallback(inventory.error) && inventory.error.operation === "list_names" ) return resolveProcedureAddress( async (operation, parameters, signal) => { const result = await analysis.execute( operation, parameters, signal === undefined ? {} : { signal }, ); return result.ok ? ok(result.value.result) : result; }, selector, signal, ); return inventory; } if (signal?.aborted) return err(cancelledBy(operation, signal)); const addresses = new Set(); for (const [index, row] of inventory.value.fact.entries()) { // An empty address cannot be passed back to xrefs. if (row.address.length === 0) return err( new AnalysisOutputError( "list_names", `Invalid symbol inventory entry at index ${index}`, ), ); if (row.value === selector) addresses.add(row.address); } const [address] = addresses; if (addresses.size <= 1) return ok(address ?? null); return err( new AnalysisInputError( operation, { conformsToInputContract: true, remediationAction: "Select an exact address from the candidates and call xrefs with that address.", }, [ { path: [operation === "xrefs" ? "address" : "name"], reason: "invalid_value", message: `Analyzed symbol name ${JSON.stringify(selector)} is ambiguous at ${[...addresses].sort().join(", ")}. Select an exact address.`, }, ], ), ); };