refactor(native): consolidate provider-neutral analysis contracts
Move shared function, dossier, inventory, reference and output semantics from the legacy Hopper module to native domain owners. Migrate provider, adapter, test and verifier consumers directly.
This commit is contained in:
1 parent
a6488a96a9
commit
82fd223638
94 files changed
+1050
-985
No files matched your search
@@ -1,4 +1,4 @@
|
||||
import { functionDossierSchema } from "../../../dist/domain/hopperValues.js";
|
||||
import { functionDossierSchema } from "../../../dist/domain/native/functionDossier.js";
|
||||
|
||||
/** Validated synthetic native observations shared by managed verification lanes. */
|
||||
export const functionDossier = (name) =>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { z } from "zod";
|
||||
|
||||
import type { analysisStringSchema } from "../../src/domain/hopperValues.js";
|
||||
import type { analysisStringSchema } from "../../src/domain/native/nativeString.js";
|
||||
|
||||
type VerifierCall = (
|
||||
name: string,
|
||||
|
||||
@@ -14,7 +14,7 @@ import { promisify } from "node:util";
|
||||
import { Client } from "@modelcontextprotocol/client";
|
||||
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
|
||||
import { parseEvidence } from "../dist/domain/evidence.js";
|
||||
import { functionDossierSchema } from "../dist/domain/hopperValues.js";
|
||||
import { functionDossierSchema } from "../dist/domain/native/functionDossier.js";
|
||||
import { inspectVerifierGhidraInstallation } from "./lib/ghidra-installation-inspection.mjs";
|
||||
import { buildDosComFixture } from "../tests/conformance/ghidra/dos-com-fixture.mjs";
|
||||
import { createVerifierRun, completeVerifierRun } from "./lib/verifier-run.mjs";
|
||||
|
||||
@@ -14,7 +14,7 @@ import { ArtifactResourceScope } from "../dist/artifacts/ArtifactResourceScope.j
|
||||
import { readGhidraLanguageCatalog } from "../dist/ghidra/GhidraLanguageCatalog.js";
|
||||
import { parseEvidence } from "../dist/domain/evidence.js";
|
||||
import { parseAnalysisSnapshot } from "../dist/domain/analysisSnapshot.js";
|
||||
import { functionDossierSchema } from "../dist/domain/hopperValues.js";
|
||||
import { functionDossierSchema } from "../dist/domain/native/functionDossier.js";
|
||||
import { inspectVerifierGhidraInstallation } from "./lib/ghidra-installation-inspection.mjs";
|
||||
import { buildGameCubeDolFixture } from "../tests/conformance/ghidra/dol-fixture.mjs";
|
||||
import {
|
||||
|
||||
@@ -21,10 +21,8 @@ import { promisify } from "node:util";
|
||||
import { Client } from "@modelcontextprotocol/client";
|
||||
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
|
||||
import { parseEvidence } from "../dist/domain/evidence.js";
|
||||
import {
|
||||
functionBodySchema,
|
||||
functionDossierSchema,
|
||||
} from "../dist/domain/hopperValues.js";
|
||||
import { functionDossierSchema } from "../dist/domain/native/functionDossier.js";
|
||||
import { functionBodySchema } from "../dist/domain/native/functionBody.js";
|
||||
import { inspectVerifierGhidraInstallation } from "./lib/ghidra-installation-inspection.mjs";
|
||||
import { buildDosMzFixture } from "../tests/conformance/ghidra/dos-mz-fixture.mjs";
|
||||
import {
|
||||
|
||||
@@ -12,7 +12,7 @@ import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
|
||||
import { parseBinaryTarget } from "../dist/application/BinaryTargetResolver.js";
|
||||
import { parseEvidence } from "../dist/domain/evidence.js";
|
||||
import { parseAnalysisSnapshot } from "../dist/domain/analysisSnapshot.js";
|
||||
import { functionDossierSchema } from "../dist/domain/hopperValues.js";
|
||||
import { functionDossierSchema } from "../dist/domain/native/functionDossier.js";
|
||||
import { inspectVerifierGhidraInstallation } from "./lib/ghidra-installation-inspection.mjs";
|
||||
import { buildMipsRelocatableFixture } from "../tests/conformance/ghidra/mips-fixture.mjs";
|
||||
import {
|
||||
|
||||
@@ -21,7 +21,7 @@ import {
|
||||
} from "../tests/conformance/ghidra/mips-checks.mjs";
|
||||
import { parseEvidence } from "../dist/domain/evidence.js";
|
||||
import { parseAnalysisSnapshot } from "../dist/domain/analysisSnapshot.js";
|
||||
import { functionDossierSchema } from "../dist/domain/hopperValues.js";
|
||||
import { functionDossierSchema } from "../dist/domain/native/functionDossier.js";
|
||||
import { inspectVerifierGhidraInstallation } from "./lib/ghidra-installation-inspection.mjs";
|
||||
import { buildMipsFixture } from "../tests/conformance/ghidra/mips-fixture.mjs";
|
||||
import {
|
||||
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
} from "../tests/conformance/ghidra/ppc-checks.mjs";
|
||||
import { parseEvidence } from "../dist/domain/evidence.js";
|
||||
import { parseAnalysisSnapshot } from "../dist/domain/analysisSnapshot.js";
|
||||
import { functionDossierSchema } from "../dist/domain/hopperValues.js";
|
||||
import { functionDossierSchema } from "../dist/domain/native/functionDossier.js";
|
||||
import { inspectVerifierGhidraInstallation } from "./lib/ghidra-installation-inspection.mjs";
|
||||
import { buildPowerPcFixture } from "../tests/conformance/ghidra/ppc-fixture.mjs";
|
||||
import { requireMcpOperationResult } from "./lib/mcp-verifier-results.mjs";
|
||||
|
||||
@@ -21,7 +21,7 @@ import addFormats from "ajv-formats";
|
||||
import { parseBinaryTarget } from "../dist/application/BinaryTargetResolver.js";
|
||||
import { parseAnalysisSnapshot } from "../dist/domain/analysisSnapshot.js";
|
||||
import { parseEvidence } from "../dist/domain/evidence.js";
|
||||
import { functionDossierSchema } from "../dist/domain/hopperValues.js";
|
||||
import { functionDossierSchema } from "../dist/domain/native/functionDossier.js";
|
||||
import { inspectVerifierGhidraInstallation } from "./lib/ghidra-installation-inspection.mjs";
|
||||
import { buildRiscvFixture } from "../tests/conformance/ghidra/riscv-fixture.mjs";
|
||||
import {
|
||||
|
||||
@@ -60,7 +60,7 @@ const [
|
||||
] = await Promise.all([
|
||||
importBuilt("contracts/toolContracts.js"),
|
||||
importBuilt("domain/evidence.js"),
|
||||
importBuilt("domain/hopperValues.js"),
|
||||
importBuilt("domain/native/functionDossier.js"),
|
||||
importBuilt("ida/IdaConfiguration.js"),
|
||||
importBuilt("ida/IdaMcpConnection.js"),
|
||||
importBuilt("ida/IdaProtocolValues.js"),
|
||||
|
||||
@@ -6,10 +6,8 @@ import type {
|
||||
import type { AnalysisError } from "../domain/analysisErrorBase.js";
|
||||
import { AnalysisOutputError } from "../domain/analysisErrorCore.js";
|
||||
import type { JsonValue } from "../domain/jsonValue.js";
|
||||
import {
|
||||
analysisBookmarkSchema,
|
||||
parseContainingProcedureResolution,
|
||||
} from "../domain/hopperValues.js";
|
||||
import { analysisBookmarkSchema } from "../domain/native/nativeInventory.js";
|
||||
import { parseContainingProcedureResolution } from "../domain/native/nativeObservation.js";
|
||||
import { resolveAnalysisDocument } from "./AnalysisDocument.js";
|
||||
import { err, ok, type Result } from "../domain/result.js";
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
AnalysisOutputError,
|
||||
} from "../domain/analysisErrorCore.js";
|
||||
import type { AnalysisError } from "../domain/analysisErrorBase.js";
|
||||
import { parseDocuments } from "../domain/hopperValues.js";
|
||||
import { parseDocuments } from "../domain/native/nativeDocument.js";
|
||||
import { err, ok, type Result } from "../domain/result.js";
|
||||
|
||||
const executionOptions = (signal: AbortSignal | undefined) =>
|
||||
|
||||
@@ -8,7 +8,7 @@ import type {
|
||||
import { projectAnalysisError } from "../domain/analysisErrorProjection.js";
|
||||
import { AnalysisCancelledError } from "../domain/analysisErrorCore.js";
|
||||
import type { AnalysisError } from "../domain/analysisErrorBase.js";
|
||||
import { parseRelatedAddresses } from "../domain/hopperValues.js";
|
||||
import { parseRelatedAddresses } from "../domain/native/nativeObservation.js";
|
||||
import type { JsonValue } from "../domain/jsonValue.js";
|
||||
import { err, ok, type Result } from "../domain/result.js";
|
||||
import type { EnhancedResult } from "./EnhancedToolTypes.js";
|
||||
|
||||
@@ -10,7 +10,7 @@ import type { JsonValue } from "../domain/jsonValue.js";
|
||||
import {
|
||||
parseContainingProcedureResolution,
|
||||
type ContainingProcedureResolution,
|
||||
} from "../domain/hopperValues.js";
|
||||
} from "../domain/native/nativeObservation.js";
|
||||
import { err, ok, type Result } from "../domain/result.js";
|
||||
import type { EnhancedResult, TraceReference } from "./EnhancedToolTypes.js";
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { z } from "zod";
|
||||
import { AnalysisInputError } from "../domain/analysisErrorCore.js";
|
||||
import type { AnalysisError } from "../domain/analysisErrorBase.js";
|
||||
import type { JsonValue } from "../domain/jsonValue.js";
|
||||
import type { ContainingProcedureResolution } from "../domain/hopperValues.js";
|
||||
import type { ContainingProcedureResolution } from "../domain/native/nativeObservation.js";
|
||||
import { err, type Result } from "../domain/result.js";
|
||||
import {
|
||||
enhancedInputSchemas,
|
||||
|
||||
@@ -21,13 +21,16 @@ import {
|
||||
} from "../domain/analysisErrorCore.js";
|
||||
import { projectAnalysisError } from "../domain/analysisErrorProjection.js";
|
||||
import type { AnalysisError } from "../domain/analysisErrorBase.js";
|
||||
import { parseFunctionDossier } from "../domain/native/functionDossier.js";
|
||||
import {
|
||||
parseFunctionDossier,
|
||||
parseListCount,
|
||||
parseRelatedAddresses,
|
||||
addressedValueSchema,
|
||||
} from "../domain/native/nativeInventory.js";
|
||||
import { parseRelatedAddresses } from "../domain/native/nativeObservation.js";
|
||||
import {
|
||||
parseSegments,
|
||||
projectBinaryOverview,
|
||||
} from "../domain/hopperValues.js";
|
||||
} from "../domain/native/nativeSegments.js";
|
||||
import { err, ok, type Result } from "../domain/result.js";
|
||||
import {
|
||||
categorizeSwiftTypes,
|
||||
@@ -40,7 +43,6 @@ import { traceNativeValues } from "./native/NativeValueTrace.js";
|
||||
import { traceNativeUiAction } from "./native/NativeUiActionTrace.js";
|
||||
import { decompileAll } from "./decompileDirectory/decompileAll.js";
|
||||
import { jsonValueSchema, type JsonValue } from "../domain/jsonValue.js";
|
||||
import { addressedValueSchema } from "../domain/native/nativeInventory.js";
|
||||
import {
|
||||
nativeDispatchMetadataResultSchema,
|
||||
MAX_DISPATCH_METADATA_RECORDS,
|
||||
|
||||
@@ -6,7 +6,7 @@ import type {
|
||||
import { nativeProcedureInputSchema } from "../domain/native/nativeProcedureInput.js";
|
||||
import type { BinaryTarget } from "../domain/binaryTargetTypes.js";
|
||||
import { createAnalysisExecutionEvidence } from "../domain/analysisObservation.js";
|
||||
import { parseFunctionDossier } from "../domain/hopperValues.js";
|
||||
import { parseFunctionDossier } from "../domain/native/functionDossier.js";
|
||||
import type { JsonValue } from "../domain/jsonValue.js";
|
||||
import { completeProviderEvidence } from "./ProviderOperationExecution.js";
|
||||
import type { Evidence } from "../domain/evidence.js";
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
import { AnalysisCancelledError } from "../domain/analysisErrorCore.js";
|
||||
import { nativeApiBoundarySchema } from "../domain/native/nativeApiBoundary.js";
|
||||
import { projectNativeApiInspection } from "./native/NativeApiInspection.js";
|
||||
import { functionDossierSchema } from "../domain/hopperValues.js";
|
||||
import { functionDossierSchema } from "../domain/native/functionDossier.js";
|
||||
import { ghidraFunctionDossier } from "../domain/ghidraValues.fixture.js";
|
||||
import { ANALYSIS_SNAPSHOT_PROFILE } from "../domain/analysisSnapshot.fixture.js";
|
||||
import { createEvidence } from "../domain/evidence.js";
|
||||
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
rebindEvidenceToTargetPath,
|
||||
} from "../../domain/evidence.js";
|
||||
import { inspectAnalysisViewInputSchema } from "../../domain/analysisView/analysisView.js";
|
||||
import { functionDossierSchema } from "../../domain/hopperValues.js";
|
||||
import { functionDossierSchema } from "../../domain/native/functionDossier.js";
|
||||
import { ghidraFunctionDossier } from "../../domain/ghidraValues.fixture.js";
|
||||
import {
|
||||
inspectAnalysisView,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { functionDossierSchema } from "../../domain/hopperValues.js";
|
||||
import { functionDossierSchema } from "../../domain/native/functionDossier.js";
|
||||
import { ghidraFunctionDossier } from "../../domain/ghidraValues.fixture.js";
|
||||
import { nativeApiBoundarySchema } from "../../domain/native/nativeApiBoundary.js";
|
||||
import { projectNativeApiInspection } from "./NativeApiInspection.js";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { FunctionDossier } from "../../domain/hopperValues.js";
|
||||
import type { FunctionDossier } from "../../domain/native/functionDossier.js";
|
||||
import {
|
||||
nativeApiInspectionResultSchema,
|
||||
type NativeApiBoundary,
|
||||
|
||||
@@ -4,10 +4,8 @@ import type {
|
||||
ExecutionOptions,
|
||||
ProviderIdentity,
|
||||
} from "../AnalysisProvider.js";
|
||||
import {
|
||||
parseRelatedAddresses,
|
||||
referenceKindSchema,
|
||||
} from "../../domain/hopperValues.js";
|
||||
import { parseRelatedAddresses } from "../../domain/native/nativeObservation.js";
|
||||
import { referenceKindSchema } from "../../domain/native/nativeReference.js";
|
||||
import { AnalysisOutputError } from "../../domain/analysisErrorCore.js";
|
||||
import type { AnalysisError } from "../../domain/analysisErrorBase.js";
|
||||
import { err, ok, type Result } from "../../domain/result.js";
|
||||
|
||||
@@ -5,7 +5,7 @@ import { nativeFunctionMatchingSchema } from "../../domain/native/functionMatchi
|
||||
import { createEvidence, immutableEvidence } from "../../domain/evidence.js";
|
||||
import { createAnalysisProfile } from "../../domain/analysisProfile.js";
|
||||
import { AnalysisResourceConstraintError } from "../../domain/analysisErrorCore.js";
|
||||
import { functionDossierSchema } from "../../domain/hopperValues.js";
|
||||
import { functionDossierSchema } from "../../domain/native/functionDossier.js";
|
||||
import { procedureInfoSchema } from "../../domain/native/procedureInfo.js";
|
||||
import {
|
||||
nativeMatchingNamedInventory as nativeMatchingInventory,
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
type AnalysisOperationPort,
|
||||
} from "../AnalysisProvider.js";
|
||||
import { ghidraFunctionDossier } from "../../domain/ghidraValues.fixture.js";
|
||||
import { functionDossierSchema } from "../../domain/hopperValues.js";
|
||||
import { functionDossierSchema } from "../../domain/native/functionDossier.js";
|
||||
import { jsonValueSchema } from "../../domain/jsonValue.js";
|
||||
import { nativeValueTraceSchema } from "../../domain/native/nativeValueTrace.js";
|
||||
import {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { functionDossierSchema } from "../../domain/hopperValues.js";
|
||||
import { functionDossierSchema } from "../../domain/native/functionDossier.js";
|
||||
import { nativeCallTargetsSchema } from "../../domain/native/nativeInstruction.js";
|
||||
import {
|
||||
nativeValueTraceInputSchema,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
nativeAddressListSchema,
|
||||
nativeObservationSourceSchemas,
|
||||
containingProcedureResolutionSchema,
|
||||
} from "../domain/native/nativeObservation.js";
|
||||
import {
|
||||
nativeObservationSummaryViewResultSchemas,
|
||||
@@ -22,13 +23,14 @@ import { nativeByteReadSchema } from "../domain/native/nativeByteRead.js";
|
||||
import { analysisDetailOutputSchemaOf } from "./analysisDetail.js";
|
||||
import {
|
||||
functionDossierSchema,
|
||||
analysisBookmarkSchema,
|
||||
functionInstructionWindowSchema,
|
||||
} from "../domain/native/functionDossier.js";
|
||||
import { analysisBookmarkSchema } from "../domain/native/nativeInventory.js";
|
||||
import {
|
||||
referenceEdgeSchema,
|
||||
unresolvedCallSchema,
|
||||
procedureIdentitySchema,
|
||||
containingProcedureResolutionSchema,
|
||||
} from "../domain/hopperValues.js";
|
||||
} from "../domain/native/nativeReference.js";
|
||||
import { procedureIdentitySchema } from "../domain/native/procedureInfo.js";
|
||||
import { nativeInventoryRowsSchemas } from "../domain/native/nativeInventory.js";
|
||||
import { z } from "zod";
|
||||
import { jsonRecordSchema } from "../domain/jsonRecord.js";
|
||||
|
||||
@@ -2,7 +2,7 @@ import { procedureInfoSchema } from "../domain/native/procedureInfo.js";
|
||||
import { z } from "zod";
|
||||
import { utcDateTimeSchema } from "../domain/offsetDateTime.js";
|
||||
|
||||
import { functionDossierSchema } from "../domain/hopperValues.js";
|
||||
import { functionDossierSchema } from "../domain/native/functionDossier.js";
|
||||
import { analysisProfileSchema } from "../domain/analysisProfile.js";
|
||||
import {
|
||||
evidenceSchema,
|
||||
|
||||
@@ -4,7 +4,7 @@ import { describe, expect, it } from "vitest";
|
||||
import { z } from "zod";
|
||||
|
||||
import { packetFieldsSchema } from "../domain/captures/packetCapture.js";
|
||||
import { swiftStringLiteralSchema } from "../domain/hopperValues.js";
|
||||
import { swiftStringLiteralSchema } from "../domain/native/swiftStringLiteral.js";
|
||||
import { applicationVersionComparisonResultSchema } from "../domain/javascript/javascriptApplicationVersionComparisonSchemas.js";
|
||||
import { kernelModuleSchema } from "../domain/native/kernelModule.js";
|
||||
import { pinvokeVerificationSchema } from "../domain/managed/managedNativeVerificationSchemas.js";
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
} from "./analysisViewCoverage.js";
|
||||
import { expect, it } from "vitest";
|
||||
|
||||
import { functionDossierSchema } from "../hopperValues.js";
|
||||
import { functionDossierSchema } from "../native/functionDossier.js";
|
||||
import { ghidraFunctionDossier } from "../ghidraValues.fixture.js";
|
||||
import { JAVASCRIPT_APPLICATION_EVIDENCE_EXAMPLE } from "../../contracts/javascript/javascriptRuntimeReconciliationExample.js";
|
||||
import { ARTIFACT_COMPARISON_EXAMPLE } from "../../contracts/artifactComparisonExample.js";
|
||||
|
||||
@@ -195,7 +195,7 @@ import { bundleResourceRootSchema } from "../apple/bundleResourceRoot.js";
|
||||
import {
|
||||
functionDossierSchema,
|
||||
functionFacetAvailabilitySchema,
|
||||
} from "../hopperValues.js";
|
||||
} from "../native/functionDossier.js";
|
||||
import {
|
||||
artifactGraphManifestSchema,
|
||||
artifactInventoryCoverageSchema,
|
||||
|
||||
@@ -2,7 +2,7 @@ import { analysisViewInputError } from "./analysisViewErrors.js";
|
||||
import { analysisTextPage } from "./analysisViewText.js";
|
||||
import type { AnalysisError } from "../analysisErrorBase.js";
|
||||
import { uniqueSorted } from "../canonicalOrdering.js";
|
||||
import type { FunctionDossier } from "../hopperValues.js";
|
||||
import type { FunctionDossier } from "../native/functionDossier.js";
|
||||
import { jsonValueSchema } from "../jsonValue.js";
|
||||
import { err, ok, type Result } from "../result.js";
|
||||
import type {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { z } from "zod";
|
||||
import { analysisStringSchema } from "../hopperValues.js";
|
||||
import { analysisStringSchema } from "../native/nativeString.js";
|
||||
import { jsonValueSchema } from "../jsonValue.js";
|
||||
import {
|
||||
addressedValueSchema,
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { NATIVE_OBSERVATION_OPERATIONS } from "../native/nativeObservation.js";
|
||||
import {
|
||||
NATIVE_OBSERVATION_OPERATIONS,
|
||||
containingProcedureResolutionSchema,
|
||||
} from "../native/nativeObservation.js";
|
||||
import { z } from "zod";
|
||||
import { digestSchema } from "../digests.js";
|
||||
import { evidenceSchema } from "../evidence.js";
|
||||
import { containingProcedureResolutionSchema } from "../hopperValues.js";
|
||||
import {
|
||||
completeRelationshipCoverageSchema,
|
||||
partialRelationshipCoverageSchema,
|
||||
|
||||
@@ -2,7 +2,7 @@ import {
|
||||
relationshipCoverageLimitations,
|
||||
type RelationshipCoverage,
|
||||
} from "./relationshipCoverage.js";
|
||||
import type { FunctionDossier } from "./hopperValues.js";
|
||||
import type { FunctionDossier } from "./native/functionDossier.js";
|
||||
import { uniqueSorted } from "./canonicalOrdering.js";
|
||||
|
||||
const callerCoveragePrefix = "Callers: ";
|
||||
|
||||
@@ -7,7 +7,7 @@ import { functionComparisonResultSchema } from "./functionComparisonSchemas.js";
|
||||
import { createFunctionCoordinateContext } from "./native/functionCoordinateContext.js";
|
||||
import { createAnalysisProfile } from "./analysisProfile.js";
|
||||
import { createEvidence, type Evidence } from "./evidence.js";
|
||||
import { functionDossierSchema } from "./hopperValues.js";
|
||||
import { functionDossierSchema } from "./native/functionDossier.js";
|
||||
import { canonicalJson } from "./comparisonSemantics.js";
|
||||
import { digestCanonicalValue } from "./canonicalDigest.js";
|
||||
import { compareUnicodeCodePoints } from "./unicodeCodePointOrder.js";
|
||||
|
||||
@@ -5,7 +5,10 @@ import { readEvidence, readEvidencePayload } from "./evidenceValidation.js";
|
||||
import type { JsonValue } from "./jsonValue.js";
|
||||
import { err, ok, type Result } from "./result.js";
|
||||
import { compareUnicodeCodePoints } from "./unicodeCodePointOrder.js";
|
||||
import { functionDossierSchema, type FunctionDossier } from "./hopperValues.js";
|
||||
import {
|
||||
functionDossierSchema,
|
||||
type FunctionDossier,
|
||||
} from "./native/functionDossier.js";
|
||||
import { assessFunctionCallCoverage } from "./functionCallCoverage.js";
|
||||
|
||||
/** Complete function observation parsed from one analyze_function Evidence record. */
|
||||
|
||||
@@ -1,264 +0,0 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import {
|
||||
functionBodySchema,
|
||||
addressDistance,
|
||||
procedureIdentitySchema,
|
||||
parseDocuments,
|
||||
parseListCount,
|
||||
parseRelatedAddresses,
|
||||
parseSegments,
|
||||
} from "./hopperValues.js";
|
||||
|
||||
describe("Hopper boundary values", () => {
|
||||
it("distinguishes measured zero, malformed, incomparable, reversed, and oversized extents", () => {
|
||||
expect(addressDistance("not-an-address", "0x2000")).toBeNull();
|
||||
expect(addressDistance("ram:0x1000", "rom:0x2000")).toBeNull();
|
||||
expect(addressDistance("0x2000", "0x1000")).toBeNull();
|
||||
expect(addressDistance("0x1000", "0x1000")).toBe(0);
|
||||
expect(addressDistance("0x1000", "0x2000")).toBe(4096);
|
||||
expect(addressDistance("0x0", "0x20000000000000")).toBeNull();
|
||||
});
|
||||
|
||||
it("counts Hopper's address-keyed string maps", () => {
|
||||
expect(
|
||||
parseListCount({ "0x1000": "hello", "0x2000": "world" }, "strings"),
|
||||
).toEqual({ ok: true, value: 2 });
|
||||
});
|
||||
|
||||
it("parses direct and wrapped protocol shapes", () => {
|
||||
expect(parseRelatedAddresses({ callers: ["0x3"] }, "callers")).toEqual({
|
||||
ok: true,
|
||||
value: ["0x3"],
|
||||
});
|
||||
expect(
|
||||
parseSegments([
|
||||
{
|
||||
name: "__TEXT",
|
||||
start: "0x1",
|
||||
end: "0x2",
|
||||
readable: true,
|
||||
writable: false,
|
||||
executable: null,
|
||||
},
|
||||
]),
|
||||
).toEqual({
|
||||
ok: true,
|
||||
value: [
|
||||
{
|
||||
name: "__TEXT",
|
||||
start: "0x1",
|
||||
end: "0x2",
|
||||
readable: true,
|
||||
writable: false,
|
||||
executable: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(parseDocuments(["fixture"])).toEqual({
|
||||
ok: true,
|
||||
value: ["fixture"],
|
||||
});
|
||||
});
|
||||
|
||||
it.each([
|
||||
["relations", () => parseRelatedAddresses([1], "callees")],
|
||||
["segments", () => parseSegments([{ name: 1 }])],
|
||||
["documents", () => parseDocuments([1])],
|
||||
])(
|
||||
"rejects malformed %s instead of manufacturing empty output",
|
||||
(_label, parse) => {
|
||||
const result = parse();
|
||||
expect(result.ok).toBe(false);
|
||||
if (!result.ok) expect(result.error._tag).toBe("AnalysisOutputError");
|
||||
},
|
||||
);
|
||||
|
||||
it("uses provider-neutral wording for malformed shared values", () => {
|
||||
const result = parseSegments([{ name: 1 }]);
|
||||
expect(result.ok).toBe(false);
|
||||
if (!result.ok) {
|
||||
expect(result.error._tag).toBe("AnalysisOutputError");
|
||||
expect(result.error.operation).toBe("list_segments");
|
||||
expect(result.error.reason).toBe(
|
||||
"Provider returned an invalid segment list",
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("provider-neutral inclusive function body evidence", () => {
|
||||
const observed = () => ({
|
||||
available: true,
|
||||
provenance: "test-address-set",
|
||||
ranges: [
|
||||
{ start: "0x1000", end: "0x1002" },
|
||||
{ start: "0x1010", end: "0x1011" },
|
||||
],
|
||||
total_bytes: 5,
|
||||
span_bytes: 18,
|
||||
non_contiguous: true,
|
||||
contains_entry: true,
|
||||
});
|
||||
it("rejects missing provider body metadata", () => {
|
||||
expect(
|
||||
procedureIdentitySchema.safeParse({ address: "0x1000", name: "main" })
|
||||
.success,
|
||||
).toBe(false);
|
||||
});
|
||||
it("rejects segments when required coordinates or unknown permissions are omitted", () => {
|
||||
expect(
|
||||
parseSegments([
|
||||
{
|
||||
name: "__TEXT",
|
||||
start: "0x1",
|
||||
end: "0x2",
|
||||
readable: null,
|
||||
writable: null,
|
||||
},
|
||||
]).ok,
|
||||
).toBe(false);
|
||||
expect(
|
||||
parseSegments([{ name: "__TEXT", start: "0x1", end: "0x2" }]).ok,
|
||||
).toBe(false);
|
||||
});
|
||||
it("counts inclusive one-byte ranges without assuming an enclosing continuous body", () => {
|
||||
expect(
|
||||
functionBodySchema.safeParse({
|
||||
...observed(),
|
||||
ranges: [{ start: "0x1000", end: "0x1000" }],
|
||||
total_bytes: 1,
|
||||
span_bytes: 1,
|
||||
non_contiguous: false,
|
||||
}).success,
|
||||
).toBe(true);
|
||||
expect(functionBodySchema.parse(observed())).toMatchObject({
|
||||
total_bytes: 5,
|
||||
span_bytes: 18,
|
||||
});
|
||||
});
|
||||
it("preserves address-space identity and leaves a multi-space span unknown", () => {
|
||||
expect(
|
||||
functionBodySchema.safeParse({
|
||||
...observed(),
|
||||
ranges: [
|
||||
{ start: "a:0x1000", end: "a:0x1000" },
|
||||
{ start: "b:0x1000", end: "b:0x1001" },
|
||||
],
|
||||
total_bytes: 3,
|
||||
span_bytes: null,
|
||||
}).success,
|
||||
).toBe(true);
|
||||
expect(
|
||||
functionBodySchema.safeParse({
|
||||
...observed(),
|
||||
ranges: [{ start: "a:0x1000", end: "b:0x1000" }],
|
||||
}).success,
|
||||
).toBe(false);
|
||||
});
|
||||
it("allows an observed empty set without manufacturing entry membership", () => {
|
||||
expect(
|
||||
procedureIdentitySchema.safeParse({
|
||||
address: "0x1000",
|
||||
name: "external",
|
||||
classification: null,
|
||||
body: {
|
||||
...observed(),
|
||||
ranges: [],
|
||||
total_bytes: 0,
|
||||
span_bytes: 0,
|
||||
non_contiguous: false,
|
||||
contains_entry: false,
|
||||
},
|
||||
}).success,
|
||||
).toBe(true);
|
||||
});
|
||||
it("checks entry membership using address space and inclusive endpoints", () => {
|
||||
expect(
|
||||
procedureIdentitySchema.safeParse({
|
||||
address: "0x1002",
|
||||
name: "last",
|
||||
classification: null,
|
||||
body: observed(),
|
||||
}).success,
|
||||
).toBe(true);
|
||||
expect(
|
||||
procedureIdentitySchema.safeParse({
|
||||
address: "0x1003",
|
||||
name: "gap",
|
||||
classification: null,
|
||||
body: observed(),
|
||||
}).success,
|
||||
).toBe(false);
|
||||
expect(
|
||||
procedureIdentitySchema.safeParse({
|
||||
address: "other:0x1000",
|
||||
name: "other",
|
||||
classification: null,
|
||||
body: observed(),
|
||||
}).success,
|
||||
).toBe(false);
|
||||
});
|
||||
it.each([
|
||||
{ total_bytes: 18 },
|
||||
{ span_bytes: 5 },
|
||||
{ non_contiguous: false },
|
||||
{
|
||||
ranges: [
|
||||
{ start: "0x1000", end: "0x1002" },
|
||||
{ start: "0x1003", end: "0x1004" },
|
||||
],
|
||||
},
|
||||
{
|
||||
ranges: [
|
||||
{ start: "0x1000", end: "0x1002" },
|
||||
{ start: "0x1002", end: "0x1004" },
|
||||
],
|
||||
},
|
||||
{
|
||||
ranges: [
|
||||
{ start: "0x1010", end: "0x1011" },
|
||||
{ start: "0x1000", end: "0x1002" },
|
||||
],
|
||||
},
|
||||
{
|
||||
ranges: [{ start: "0x0", end: "0x20000000000000" }],
|
||||
total_bytes: Number.MAX_SAFE_INTEGER,
|
||||
span_bytes: Number.MAX_SAFE_INTEGER,
|
||||
non_contiguous: false,
|
||||
},
|
||||
])("rejects contradictory or unsafe range facts %j", (change) => {
|
||||
expect(
|
||||
functionBodySchema.safeParse({ ...observed(), ...change }).success,
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
it.each(["procedures", "strings"])(
|
||||
"counts every own entry of direct and wrapped %s maps",
|
||||
(property) => {
|
||||
for (const text of [
|
||||
'{"__proto__":"observed"}',
|
||||
'{"__proto__":"observed","0x1000":"one"}',
|
||||
]) {
|
||||
const source = JSON.parse(text);
|
||||
const count = Object.keys(source).length;
|
||||
expect(parseListCount(source, property)).toEqual({
|
||||
ok: true,
|
||||
value: count,
|
||||
});
|
||||
expect(parseListCount({ [property]: source }, property)).toEqual({
|
||||
ok: true,
|
||||
value: count,
|
||||
});
|
||||
expect(JSON.stringify(source)).toBe(text);
|
||||
}
|
||||
expect(parseListCount({ [property]: [] }, property)).toEqual({
|
||||
ok: true,
|
||||
value: 0,
|
||||
});
|
||||
expect(parseListCount({}, property)).toEqual({ ok: true, value: 0 });
|
||||
for (const value of [null, "not a list", 3, false])
|
||||
expect(parseListCount(value, property).ok).toBe(false);
|
||||
},
|
||||
);
|
||||
@@ -1,616 +0,0 @@
|
||||
import { nativeSymbolMetadataSchema } from "./native/nativeSymbol.js";
|
||||
import { z } from "zod";
|
||||
import { fixedTupleSchema } from "./fixedTupleSchema.js";
|
||||
import { addressCoordinate } from "./native/addressCoordinate.js";
|
||||
|
||||
import { jsonObjectSchema, type JsonValue } from "./jsonValue.js";
|
||||
|
||||
import { err, ok, type Result } from "./result.js";
|
||||
import { nativeApiBoundarySchema } from "./native/nativeApiBoundary.js";
|
||||
import { nativeValueFlowSchema } from "./native/nativeValueFlow.js";
|
||||
import { nativeCallClassificationSchema } from "./native/nativeCallReference.js";
|
||||
import { AnalysisOutputError } from "./analysisErrorCore.js";
|
||||
|
||||
export interface AddressedName {
|
||||
readonly address: string;
|
||||
readonly name: string;
|
||||
readonly objc_class_label?: "hopper_legacy_class_object";
|
||||
}
|
||||
|
||||
export interface SegmentSummary {
|
||||
readonly name: string;
|
||||
readonly start: string;
|
||||
readonly end: string;
|
||||
readonly readable: boolean | null;
|
||||
readonly writable: boolean | null;
|
||||
readonly executable: boolean | null;
|
||||
readonly image_base?: string;
|
||||
readonly address_space?: string;
|
||||
}
|
||||
|
||||
/** Return a bounded byte distance between canonical addresses in one space. */
|
||||
export const addressDistance = (start: string, end: string): number | null => {
|
||||
const left = addressCoordinate(start);
|
||||
const right = addressCoordinate(end);
|
||||
if (left === null || right === null || left.space !== right.space)
|
||||
return null;
|
||||
const distance = right.offset - left.offset;
|
||||
if (distance < 0n || distance > BigInt(Number.MAX_SAFE_INTEGER)) return null;
|
||||
return Number(distance);
|
||||
};
|
||||
|
||||
const segmentSchema = z.object({
|
||||
name: z.string(),
|
||||
start: z.string(),
|
||||
end: z.string(),
|
||||
readable: z.boolean().nullable(),
|
||||
writable: z.boolean().nullable(),
|
||||
executable: z.boolean().nullable(),
|
||||
image_base: z.string().min(1).exactOptional(),
|
||||
address_space: z.string().min(1).exactOptional(),
|
||||
});
|
||||
const unavailableAnalysisFactSchema = z
|
||||
.object({ available: z.literal(false), reason: z.string() })
|
||||
.strict();
|
||||
/** Typed string evidence shared by inventories and referenced function literals. */
|
||||
export const analysisStringSchema = z.object({
|
||||
address: z.string(),
|
||||
value: z.string(),
|
||||
provider_value: z
|
||||
.string()
|
||||
.exactOptional()
|
||||
.describe(
|
||||
"Original provider display text, which may shorten the typed object.",
|
||||
),
|
||||
decoding: unavailableAnalysisFactSchema
|
||||
.exactOptional()
|
||||
.describe(
|
||||
"When present, value retains native display text because typed bytes could not be decoded.",
|
||||
),
|
||||
string: z
|
||||
.object({
|
||||
encoding: z.string().min(1),
|
||||
encoding_status: z
|
||||
.enum(["observed", "inferred"])
|
||||
.exactOptional()
|
||||
.describe(
|
||||
"Whether the provider declares the encoding or REA infers it from the typed bytes and native display. This does not establish the original source's intended encoding.",
|
||||
),
|
||||
termination: z.enum(["missing", "present_or_not_required"]),
|
||||
byte_length: z.int().min(0),
|
||||
})
|
||||
.exactOptional(),
|
||||
});
|
||||
|
||||
/** An analyzed bookmark retains a missing provider label as null. */
|
||||
export const analysisBookmarkSchema = z.strictObject({
|
||||
address: z.string(),
|
||||
name: z.string().nullable(),
|
||||
});
|
||||
/** Complete observed function-body ranges; every range endpoint is inclusive. */
|
||||
export const availableFunctionBodySchema = z
|
||||
.strictObject({
|
||||
available: z.literal(true),
|
||||
provenance: z.string().min(1),
|
||||
ranges: z.array(
|
||||
z.strictObject({
|
||||
start: z.string(),
|
||||
end: z
|
||||
.string()
|
||||
.describe("Inclusive last address of this observed body range."),
|
||||
}),
|
||||
),
|
||||
total_bytes: z.int().min(0).max(Number.MAX_SAFE_INTEGER),
|
||||
span_bytes: z.int().min(0).max(Number.MAX_SAFE_INTEGER).nullable(),
|
||||
non_contiguous: z.boolean(),
|
||||
contains_entry: z.boolean(),
|
||||
})
|
||||
.superRefine((body, context) => {
|
||||
let total = 0n;
|
||||
const bounds = new Map<string | null, { start: bigint; end: bigint }>();
|
||||
const closedSpaces = new Set<string | null>();
|
||||
let previousSpace: string | null | undefined;
|
||||
for (const range of body.ranges) {
|
||||
const start = addressCoordinate(range.start);
|
||||
const end = addressCoordinate(range.end);
|
||||
if (
|
||||
start === null ||
|
||||
end === null ||
|
||||
start.space !== end.space ||
|
||||
end.offset < start.offset
|
||||
) {
|
||||
context.addIssue({
|
||||
code: "custom",
|
||||
message: "Function body range has invalid inclusive endpoints",
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (previousSpace !== undefined && previousSpace !== start.space)
|
||||
closedSpaces.add(previousSpace);
|
||||
const previous = bounds.get(start.space);
|
||||
if (
|
||||
closedSpaces.has(start.space) ||
|
||||
(previous !== undefined && start.offset <= previous.end + 1n)
|
||||
) {
|
||||
context.addIssue({
|
||||
code: "custom",
|
||||
message:
|
||||
"Function body ranges must be ordered, disjoint and maximal within each address space",
|
||||
});
|
||||
return;
|
||||
}
|
||||
bounds.set(start.space, {
|
||||
start: previous?.start ?? start.offset,
|
||||
end: end.offset,
|
||||
});
|
||||
previousSpace = start.space;
|
||||
total += end.offset - start.offset + 1n;
|
||||
}
|
||||
const onlyBounds = bounds.size === 1 ? [...bounds.values()][0] : undefined;
|
||||
const span =
|
||||
bounds.size === 0
|
||||
? 0n
|
||||
: onlyBounds === undefined
|
||||
? null
|
||||
: onlyBounds.end - onlyBounds.start + 1n;
|
||||
const reportedSpan =
|
||||
body.span_bytes === null ? null : BigInt(body.span_bytes);
|
||||
if (
|
||||
total !== BigInt(body.total_bytes) ||
|
||||
span !== reportedSpan ||
|
||||
body.non_contiguous !== body.ranges.length > 1
|
||||
)
|
||||
context.addIssue({
|
||||
code: "custom",
|
||||
message:
|
||||
"Function body counts, enclosing span or contiguity disagree with inclusive ranges",
|
||||
});
|
||||
});
|
||||
|
||||
export const functionBodySchema = z.discriminatedUnion("available", [
|
||||
unavailableAnalysisFactSchema,
|
||||
availableFunctionBodySchema,
|
||||
]);
|
||||
|
||||
/** Check the declared entry membership against complete, inclusive body ranges. */
|
||||
export const functionBodyEntryAgrees = (
|
||||
body: z.infer<typeof functionBodySchema>,
|
||||
address: string,
|
||||
): boolean => {
|
||||
if (!body.available) return true;
|
||||
const entry = addressCoordinate(address);
|
||||
if (entry === null) return false;
|
||||
const contains = body.ranges.some((range) => {
|
||||
const start = addressCoordinate(range.start);
|
||||
const end = addressCoordinate(range.end);
|
||||
return (
|
||||
start !== null &&
|
||||
end !== null &&
|
||||
start.space === entry.space &&
|
||||
entry.offset >= start.offset &&
|
||||
entry.offset <= end.offset
|
||||
);
|
||||
});
|
||||
return contains === body.contains_entry;
|
||||
};
|
||||
|
||||
export const procedureClassificationSchema = z
|
||||
.object({
|
||||
external: z.boolean(),
|
||||
thunk: z.boolean(),
|
||||
thunk_target: z.string().nullable(),
|
||||
provenance: z.string().min(1),
|
||||
})
|
||||
.strict();
|
||||
export const procedureIdentitySchema = z
|
||||
.object({
|
||||
address: z.string(),
|
||||
name: z.string(),
|
||||
symbol: nativeSymbolMetadataSchema.nullable().exactOptional(),
|
||||
classification: procedureClassificationSchema.nullable(),
|
||||
body: functionBodySchema,
|
||||
})
|
||||
.strict()
|
||||
.superRefine((identity, context) => {
|
||||
if (!functionBodyEntryAgrees(identity.body, identity.address))
|
||||
context.addIssue({
|
||||
code: "custom",
|
||||
message:
|
||||
"Function body entry membership disagrees with procedure identity",
|
||||
});
|
||||
});
|
||||
/** Exact containing-procedure outcome; valid absence is distinct from unreadable output. */
|
||||
export const containingProcedureResolutionSchema = z.discriminatedUnion(
|
||||
"found",
|
||||
[
|
||||
z.object({
|
||||
query_address: z.string(),
|
||||
found: z.literal(true),
|
||||
procedure: procedureIdentitySchema,
|
||||
}),
|
||||
z.object({
|
||||
query_address: z.string(),
|
||||
found: z.literal(false),
|
||||
procedure: z.null(),
|
||||
reason: z.enum(["outside_segments", "not_in_procedure"]),
|
||||
}),
|
||||
],
|
||||
);
|
||||
export type ContainingProcedureResolution = z.infer<
|
||||
typeof containingProcedureResolutionSchema
|
||||
>;
|
||||
|
||||
/** Validate provider procedure facts before a workflow interprets absence. */
|
||||
export const parseContainingProcedureResolution = (
|
||||
value: JsonValue,
|
||||
): Result<ContainingProcedureResolution, AnalysisOutputError> => {
|
||||
const parsed = containingProcedureResolutionSchema.safeParse(value);
|
||||
return parsed.success
|
||||
? ok(parsed.data)
|
||||
: err(
|
||||
new AnalysisOutputError(
|
||||
"resolve_containing_procedure",
|
||||
"expected a containing-procedure resolution",
|
||||
{ cause: parsed.error },
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
export const localVariableSchema = z
|
||||
.object({
|
||||
description: z.string(),
|
||||
provenance: z.string().min(1),
|
||||
name: z.string().nullable().exactOptional(),
|
||||
stack_displacement: z
|
||||
.string()
|
||||
.regex(/^-?\d+$/u)
|
||||
.nullable()
|
||||
.exactOptional()
|
||||
.describe(
|
||||
"Observed signed stack byte displacement, encoded as decimal to preserve precision.",
|
||||
),
|
||||
})
|
||||
.strict();
|
||||
/** Provider-neutral complete raw-instruction list for one analyzed function. */
|
||||
export const functionInstructionWindowSchema = z
|
||||
.object({
|
||||
procedure: procedureIdentitySchema,
|
||||
instructions: z.array(z.string()),
|
||||
limitations: z.array(z.string()),
|
||||
})
|
||||
.strict();
|
||||
|
||||
export const availableReferenceKindSchema = z
|
||||
.object({
|
||||
available: z.literal(true),
|
||||
provenance: z.string().min(1),
|
||||
type: z.string(),
|
||||
flow: z.boolean(),
|
||||
call: z.boolean(),
|
||||
jump: z.boolean(),
|
||||
data: z.boolean(),
|
||||
read: z.boolean(),
|
||||
write: z.boolean(),
|
||||
indirect: z.boolean(),
|
||||
computed: z.boolean(),
|
||||
conditional: z.boolean(),
|
||||
terminal: z.boolean(),
|
||||
primary: z.boolean(),
|
||||
operand_index: z.int(),
|
||||
external: z.boolean(),
|
||||
})
|
||||
.strict();
|
||||
export const referenceKindSchema = z.discriminatedUnion("available", [
|
||||
unavailableAnalysisFactSchema,
|
||||
availableReferenceKindSchema,
|
||||
]);
|
||||
/** Reported call sites without resolved targets; unreported dispatch stays unknown. */
|
||||
export const unresolvedCallSchema = z.strictObject({
|
||||
address: z.string(),
|
||||
reason: z.string(),
|
||||
});
|
||||
|
||||
const swiftStringLiteralFields = {
|
||||
source_address: z
|
||||
.string()
|
||||
.describe(
|
||||
"Instruction that consumes both words: a call, tail call, return, or paired store.",
|
||||
),
|
||||
value: z
|
||||
.string()
|
||||
.describe("Decoded UTF-8 text, including the canonical empty string."),
|
||||
registers: fixedTupleSchema([z.string(), z.string()]),
|
||||
words: fixedTupleSchema([z.string(), z.string()]).describe(
|
||||
"Observed constant register values, in register order.",
|
||||
),
|
||||
};
|
||||
|
||||
/** A Swift String literal decoded from the two words of its ABI value, not a data reference. */
|
||||
export const swiftSmallStringLiteralSchema = z.strictObject({
|
||||
...swiftStringLiteralFields,
|
||||
representation: z
|
||||
.literal("small")
|
||||
.describe("The UTF-8 bytes are inside the two words."),
|
||||
address: z.null().describe("A small literal has no storage of its own."),
|
||||
});
|
||||
export const swiftLargeStringLiteralSchema = z.strictObject({
|
||||
...swiftStringLiteralFields,
|
||||
representation: z
|
||||
.literal("large")
|
||||
.describe(
|
||||
"An immortal literal whose words hold its count and a pointer to its bytes.",
|
||||
),
|
||||
address: z.string().describe("Address of a large literal's bytes."),
|
||||
});
|
||||
export const swiftStringLiteralSchema = z.discriminatedUnion("representation", [
|
||||
swiftSmallStringLiteralSchema,
|
||||
swiftLargeStringLiteralSchema,
|
||||
]);
|
||||
|
||||
const swiftStringLiteralOmissionFields = {
|
||||
source_address: swiftStringLiteralFields.source_address,
|
||||
byte_length: z.int().min(0).max(Number.MAX_SAFE_INTEGER),
|
||||
reason: z
|
||||
.enum(["capacity", "unresolved_jump"])
|
||||
.describe(
|
||||
"Why this candidate is omitted: capacity stopped decoding, or an unresolved jump left its use unknown.",
|
||||
),
|
||||
registers: swiftStringLiteralFields.registers,
|
||||
words: swiftStringLiteralFields.words,
|
||||
};
|
||||
|
||||
/** A Swift literal candidate omitted because decoding capacity or control flow left it unknown. */
|
||||
export const swiftSmallStringLiteralOmissionSchema = z.strictObject({
|
||||
...swiftStringLiteralOmissionFields,
|
||||
representation: z.literal("small"),
|
||||
address: z.null(),
|
||||
});
|
||||
export const swiftLargeStringLiteralOmissionSchema = z.strictObject({
|
||||
...swiftStringLiteralOmissionFields,
|
||||
representation: z.literal("large"),
|
||||
address: z.string().describe("Address of the literal's bytes."),
|
||||
});
|
||||
export const swiftStringLiteralOmissionSchema = z.discriminatedUnion(
|
||||
"representation",
|
||||
[
|
||||
swiftSmallStringLiteralOmissionSchema,
|
||||
swiftLargeStringLiteralOmissionSchema,
|
||||
],
|
||||
);
|
||||
|
||||
/** Observed endpoints with complete or partial reference classification evidence. */
|
||||
export const referenceEdgeSchema = z
|
||||
.object({
|
||||
source_address: z.string(),
|
||||
target_address: z.string(),
|
||||
source_procedure: procedureIdentitySchema.nullable(),
|
||||
target_procedure: procedureIdentitySchema.nullable(),
|
||||
kind: referenceKindSchema,
|
||||
call: nativeCallClassificationSchema.exactOptional(),
|
||||
})
|
||||
.strict();
|
||||
const facetAvailabilitySchema = z.discriminatedUnion("available", [
|
||||
z.object({ available: z.literal(true) }).strict(),
|
||||
z.object({ available: z.literal(false), reason: z.string().min(1) }).strict(),
|
||||
]);
|
||||
|
||||
/** Provider-declared availability per dossier facet; an omitted facet declares nothing. */
|
||||
export const functionFacetAvailabilitySchema = z
|
||||
.object({
|
||||
pseudocode: facetAvailabilitySchema.exactOptional(),
|
||||
assembly: facetAvailabilitySchema.exactOptional(),
|
||||
comments: facetAvailabilitySchema.exactOptional(),
|
||||
callers: facetAvailabilitySchema.exactOptional(),
|
||||
callees: facetAvailabilitySchema.exactOptional(),
|
||||
references: facetAvailabilitySchema.exactOptional(),
|
||||
strings_names: facetAvailabilitySchema.exactOptional(),
|
||||
cfg: facetAvailabilitySchema.exactOptional(),
|
||||
})
|
||||
.strict();
|
||||
|
||||
export const functionDossierSchema = z
|
||||
.object({
|
||||
procedure: procedureIdentitySchema.safeExtend({
|
||||
signature: z.string().nullable(),
|
||||
locals: z.array(localVariableSchema),
|
||||
}),
|
||||
pseudocode: z.string(),
|
||||
assembly: z.array(z.string()),
|
||||
comments: z.array(
|
||||
z
|
||||
.object({
|
||||
address: z.string(),
|
||||
kind: z.enum(["comment", "inline"]),
|
||||
text: z.string(),
|
||||
})
|
||||
.strict(),
|
||||
),
|
||||
callers: z.array(procedureIdentitySchema),
|
||||
callees: z.array(procedureIdentitySchema),
|
||||
incoming_references: z.array(referenceEdgeSchema),
|
||||
outgoing_references: z.array(referenceEdgeSchema),
|
||||
unresolved_calls: z.array(unresolvedCallSchema).exactOptional(),
|
||||
referenced_strings: z.array(
|
||||
analysisStringSchema.extend({ source_address: z.string() }),
|
||||
),
|
||||
referenced_names: z.array(
|
||||
z
|
||||
.object({
|
||||
address: z.string(),
|
||||
value: z.string(),
|
||||
source_address: z.string(),
|
||||
})
|
||||
.strict(),
|
||||
),
|
||||
swift_string_literals: z
|
||||
.array(swiftStringLiteralSchema)
|
||||
.exactOptional()
|
||||
.describe(
|
||||
"Swift String literals decoded from constant register values. Absent when the provider does not decode them for this target.",
|
||||
),
|
||||
swift_string_literal_omissions: z
|
||||
.array(swiftStringLiteralOmissionSchema)
|
||||
.exactOptional()
|
||||
.describe(
|
||||
"Literal candidates omitted for capacity or unresolved control flow. Present exactly when swift_string_literals is present; empty when all examined use sites were classified.",
|
||||
),
|
||||
basic_blocks: z.array(
|
||||
z
|
||||
.object({
|
||||
start: z.string(),
|
||||
end: z.string(),
|
||||
successors: z.array(z.string()),
|
||||
})
|
||||
.strict(),
|
||||
),
|
||||
native_api: nativeApiBoundarySchema.nullable(),
|
||||
native_value_flow: nativeValueFlowSchema.nullable(),
|
||||
facet_availability: functionFacetAvailabilitySchema.exactOptional(),
|
||||
limitations: z.array(z.string()),
|
||||
})
|
||||
.strict();
|
||||
|
||||
/** Strict analyzed-function dossier shared by provider and comparison boundaries. */
|
||||
export type FunctionDossier = z.infer<typeof functionDossierSchema>;
|
||||
|
||||
/** Strictly parse a complete provider-neutral function dossier. */
|
||||
export const parseFunctionDossier = (
|
||||
value: JsonValue,
|
||||
): Result<FunctionDossier, AnalysisOutputError> => {
|
||||
const parsed = functionDossierSchema.safeParse(value);
|
||||
return parsed.success
|
||||
? ok(parsed.data)
|
||||
: err(
|
||||
new AnalysisOutputError(
|
||||
"analyze_function",
|
||||
"provider output did not match the dossier contract",
|
||||
{ cause: parsed.error },
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
/** Parse callee/caller strings from direct or wrapped provider results. */
|
||||
export const parseRelatedAddresses = (
|
||||
value: JsonValue,
|
||||
relation: "callees" | "callers",
|
||||
operation = relation === "callees"
|
||||
? "procedure_callees"
|
||||
: "procedure_callers",
|
||||
): Result<readonly string[], AnalysisOutputError> => {
|
||||
const parsed = z.array(z.string()).safeParse(unwrapProperty(value, relation));
|
||||
return parsed.success
|
||||
? ok(parsed.data)
|
||||
: invalid(operation, `${relation} list`, parsed.error);
|
||||
};
|
||||
|
||||
/** Preserve reported address context in direct or wrapped provider segments. */
|
||||
export const parseSegments = (
|
||||
value: JsonValue,
|
||||
operation = "list_segments",
|
||||
): Result<readonly SegmentSummary[], AnalysisOutputError> => {
|
||||
const parsed = z
|
||||
.array(segmentSchema)
|
||||
.safeParse(unwrapProperty(value, "segments"));
|
||||
return parsed.success
|
||||
? ok(parsed.data)
|
||||
: invalid(operation, "segment list", parsed.error);
|
||||
};
|
||||
|
||||
/** Return a reported overview base only when every parsed segment agrees. */
|
||||
export const parseSegmentImageBase = (
|
||||
segments: readonly SegmentSummary[],
|
||||
): string | undefined => {
|
||||
const base = segments[0]?.image_base;
|
||||
return base !== undefined &&
|
||||
segments.every((segment) => segment.image_base === base)
|
||||
? base
|
||||
: undefined;
|
||||
};
|
||||
|
||||
/** Project the same overview from complete lists or provider-owned aggregates. */
|
||||
export const projectBinaryOverview = (input: {
|
||||
readonly document: string;
|
||||
readonly procedure_count: number;
|
||||
readonly string_count: number;
|
||||
readonly segments?: readonly SegmentSummary[];
|
||||
}): JsonValue => {
|
||||
const { document, procedure_count, string_count, segments } = input;
|
||||
const imageBase =
|
||||
segments === undefined ? undefined : parseSegmentImageBase(segments);
|
||||
return {
|
||||
document,
|
||||
...(imageBase === undefined ? {} : { image_base: imageBase }),
|
||||
...(segments === undefined
|
||||
? {}
|
||||
: {
|
||||
segments: segments.map(
|
||||
({ name, start, end, image_base, address_space }) => ({
|
||||
name,
|
||||
start,
|
||||
end,
|
||||
length: addressDistance(start, end),
|
||||
...(image_base === undefined ? {} : { image_base }),
|
||||
...(address_space === undefined ? {} : { address_space }),
|
||||
}),
|
||||
),
|
||||
segment_count: segments.length,
|
||||
}),
|
||||
procedure_count,
|
||||
string_count,
|
||||
};
|
||||
};
|
||||
|
||||
/** Parse direct or wrapped provider document names. */
|
||||
export const parseDocuments = (
|
||||
value: JsonValue,
|
||||
operation = "list_documents",
|
||||
): Result<readonly string[], AnalysisOutputError> => {
|
||||
const parsed = z
|
||||
.array(z.string())
|
||||
.safeParse(unwrapProperty(value, "documents"));
|
||||
return parsed.success
|
||||
? ok(parsed.data)
|
||||
: invalid(operation, "document list", parsed.error);
|
||||
};
|
||||
|
||||
/** Parse a direct or wrapped list when only its cardinality is required. */
|
||||
export const parseListCount = (
|
||||
value: JsonValue,
|
||||
property: string,
|
||||
operation = property === "procedures" ? "list_procedures" : "list_strings",
|
||||
): Result<number, AnalysisOutputError> => {
|
||||
const unwrapped = unwrapProperty(value, property);
|
||||
const list = z.array(z.unknown()).safeParse(unwrapped);
|
||||
if (list.success) return ok(list.data.length);
|
||||
const map = jsonObjectSchema.safeParse(unwrapped);
|
||||
return map.success
|
||||
? ok(Object.keys(map.data).length)
|
||||
: invalid(operation, `${property} list`, map.error);
|
||||
};
|
||||
|
||||
const unwrapProperty = (value: JsonValue, property: string): JsonValue => {
|
||||
if (
|
||||
typeof value === "object" &&
|
||||
value !== null &&
|
||||
!Array.isArray(value) &&
|
||||
property in value
|
||||
) {
|
||||
return value[property] ?? null;
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
const invalid = <T>(
|
||||
operation: string,
|
||||
expected: string,
|
||||
cause: z.ZodError,
|
||||
): Result<T, AnalysisOutputError> =>
|
||||
err(
|
||||
new AnalysisOutputError(
|
||||
operation,
|
||||
`Provider returned an invalid ${expected}`,
|
||||
{
|
||||
cause,
|
||||
},
|
||||
),
|
||||
);
|
||||
@@ -7,7 +7,7 @@ import { EvidenceIntegrityError } from "../evidenceErrors.js";
|
||||
import { analysisInputErrorFromIssues } from "../inputIssueProjection.js";
|
||||
import { readEvidence, readEvidencePayload } from "../evidenceValidation.js";
|
||||
import { err, ok, type Result } from "../result.js";
|
||||
import { functionDossierSchema } from "../hopperValues.js";
|
||||
import { functionDossierSchema } from "../native/functionDossier.js";
|
||||
import { inspectMachoSchema } from "../native/nativeInspection.js";
|
||||
import {
|
||||
managedNativeBoundaryInspectionSchema,
|
||||
|
||||
@@ -9,3 +9,14 @@ export const addressCoordinate = (
|
||||
if (!/^0x[0-9a-f]+$/u.test(offset)) return null;
|
||||
return { space, offset: BigInt(offset) };
|
||||
};
|
||||
|
||||
/** Return a bounded byte distance between canonical addresses in one space. */
|
||||
export const addressDistance = (start: string, end: string): number | null => {
|
||||
const left = addressCoordinate(start);
|
||||
const right = addressCoordinate(end);
|
||||
if (left === null || right === null || left.space !== right.space)
|
||||
return null;
|
||||
const distance = right.offset - left.offset;
|
||||
if (distance < 0n || distance > BigInt(Number.MAX_SAFE_INTEGER)) return null;
|
||||
return Number(distance);
|
||||
};
|
||||
@@ -0,0 +1,134 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { functionBodySchema } from "./functionBody.js";
|
||||
import { procedureIdentitySchema } from "./procedureInfo.js";
|
||||
|
||||
describe("provider-neutral inclusive function body evidence", () => {
|
||||
const observed = () => ({
|
||||
available: true,
|
||||
provenance: "test-address-set",
|
||||
ranges: [
|
||||
{ start: "0x1000", end: "0x1002" },
|
||||
{ start: "0x1010", end: "0x1011" },
|
||||
],
|
||||
total_bytes: 5,
|
||||
span_bytes: 18,
|
||||
non_contiguous: true,
|
||||
contains_entry: true,
|
||||
});
|
||||
it("rejects missing provider body metadata", () => {
|
||||
expect(
|
||||
procedureIdentitySchema.safeParse({ address: "0x1000", name: "main" })
|
||||
.success,
|
||||
).toBe(false);
|
||||
});
|
||||
it("counts inclusive one-byte ranges without assuming an enclosing continuous body", () => {
|
||||
expect(
|
||||
functionBodySchema.safeParse({
|
||||
...observed(),
|
||||
ranges: [{ start: "0x1000", end: "0x1000" }],
|
||||
total_bytes: 1,
|
||||
span_bytes: 1,
|
||||
non_contiguous: false,
|
||||
}).success,
|
||||
).toBe(true);
|
||||
expect(functionBodySchema.parse(observed())).toMatchObject({
|
||||
total_bytes: 5,
|
||||
span_bytes: 18,
|
||||
});
|
||||
});
|
||||
it("preserves address-space identity and leaves a multi-space span unknown", () => {
|
||||
expect(
|
||||
functionBodySchema.safeParse({
|
||||
...observed(),
|
||||
ranges: [
|
||||
{ start: "a:0x1000", end: "a:0x1000" },
|
||||
{ start: "b:0x1000", end: "b:0x1001" },
|
||||
],
|
||||
total_bytes: 3,
|
||||
span_bytes: null,
|
||||
}).success,
|
||||
).toBe(true);
|
||||
expect(
|
||||
functionBodySchema.safeParse({
|
||||
...observed(),
|
||||
ranges: [{ start: "a:0x1000", end: "b:0x1000" }],
|
||||
}).success,
|
||||
).toBe(false);
|
||||
});
|
||||
it("allows an observed empty set without manufacturing entry membership", () => {
|
||||
expect(
|
||||
procedureIdentitySchema.safeParse({
|
||||
address: "0x1000",
|
||||
name: "external",
|
||||
classification: null,
|
||||
body: {
|
||||
...observed(),
|
||||
ranges: [],
|
||||
total_bytes: 0,
|
||||
span_bytes: 0,
|
||||
non_contiguous: false,
|
||||
contains_entry: false,
|
||||
},
|
||||
}).success,
|
||||
).toBe(true);
|
||||
});
|
||||
it("checks entry membership using address space and inclusive endpoints", () => {
|
||||
expect(
|
||||
procedureIdentitySchema.safeParse({
|
||||
address: "0x1002",
|
||||
name: "last",
|
||||
classification: null,
|
||||
body: observed(),
|
||||
}).success,
|
||||
).toBe(true);
|
||||
expect(
|
||||
procedureIdentitySchema.safeParse({
|
||||
address: "0x1003",
|
||||
name: "gap",
|
||||
classification: null,
|
||||
body: observed(),
|
||||
}).success,
|
||||
).toBe(false);
|
||||
expect(
|
||||
procedureIdentitySchema.safeParse({
|
||||
address: "other:0x1000",
|
||||
name: "other",
|
||||
classification: null,
|
||||
body: observed(),
|
||||
}).success,
|
||||
).toBe(false);
|
||||
});
|
||||
it.each([
|
||||
{ total_bytes: 18 },
|
||||
{ span_bytes: 5 },
|
||||
{ non_contiguous: false },
|
||||
{
|
||||
ranges: [
|
||||
{ start: "0x1000", end: "0x1002" },
|
||||
{ start: "0x1003", end: "0x1004" },
|
||||
],
|
||||
},
|
||||
{
|
||||
ranges: [
|
||||
{ start: "0x1000", end: "0x1002" },
|
||||
{ start: "0x1002", end: "0x1004" },
|
||||
],
|
||||
},
|
||||
{
|
||||
ranges: [
|
||||
{ start: "0x1010", end: "0x1011" },
|
||||
{ start: "0x1000", end: "0x1002" },
|
||||
],
|
||||
},
|
||||
{
|
||||
ranges: [{ start: "0x0", end: "0x20000000000000" }],
|
||||
total_bytes: Number.MAX_SAFE_INTEGER,
|
||||
span_bytes: Number.MAX_SAFE_INTEGER,
|
||||
non_contiguous: false,
|
||||
},
|
||||
])("rejects contradictory or unsafe range facts %j", (change) => {
|
||||
expect(
|
||||
functionBodySchema.safeParse({ ...observed(), ...change }).success,
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,110 @@
|
||||
import { z } from "zod";
|
||||
import { addressCoordinate } from "./addressCoordinate.js";
|
||||
import { unavailableNativeFactSchema } from "./nativeFactAvailability.js";
|
||||
|
||||
/** Complete observed function-body ranges; every range endpoint is inclusive. */
|
||||
export const availableFunctionBodySchema = z
|
||||
.strictObject({
|
||||
available: z.literal(true),
|
||||
provenance: z.string().min(1),
|
||||
ranges: z.array(
|
||||
z.strictObject({
|
||||
start: z.string(),
|
||||
end: z
|
||||
.string()
|
||||
.describe("Inclusive last address of this observed body range."),
|
||||
}),
|
||||
),
|
||||
total_bytes: z.int().min(0).max(Number.MAX_SAFE_INTEGER),
|
||||
span_bytes: z.int().min(0).max(Number.MAX_SAFE_INTEGER).nullable(),
|
||||
non_contiguous: z.boolean(),
|
||||
contains_entry: z.boolean(),
|
||||
})
|
||||
.superRefine((body, context) => {
|
||||
let total = 0n;
|
||||
const bounds = new Map<string | null, { start: bigint; end: bigint }>();
|
||||
const closedSpaces = new Set<string | null>();
|
||||
let previousSpace: string | null | undefined;
|
||||
for (const range of body.ranges) {
|
||||
const start = addressCoordinate(range.start);
|
||||
const end = addressCoordinate(range.end);
|
||||
if (
|
||||
start === null ||
|
||||
end === null ||
|
||||
start.space !== end.space ||
|
||||
end.offset < start.offset
|
||||
) {
|
||||
context.addIssue({
|
||||
code: "custom",
|
||||
message: "Function body range has invalid inclusive endpoints",
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (previousSpace !== undefined && previousSpace !== start.space)
|
||||
closedSpaces.add(previousSpace);
|
||||
const previous = bounds.get(start.space);
|
||||
if (
|
||||
closedSpaces.has(start.space) ||
|
||||
(previous !== undefined && start.offset <= previous.end + 1n)
|
||||
) {
|
||||
context.addIssue({
|
||||
code: "custom",
|
||||
message:
|
||||
"Function body ranges must be ordered, disjoint and maximal within each address space",
|
||||
});
|
||||
return;
|
||||
}
|
||||
bounds.set(start.space, {
|
||||
start: previous?.start ?? start.offset,
|
||||
end: end.offset,
|
||||
});
|
||||
previousSpace = start.space;
|
||||
total += end.offset - start.offset + 1n;
|
||||
}
|
||||
const onlyBounds = bounds.size === 1 ? [...bounds.values()][0] : undefined;
|
||||
const span =
|
||||
bounds.size === 0
|
||||
? 0n
|
||||
: onlyBounds === undefined
|
||||
? null
|
||||
: onlyBounds.end - onlyBounds.start + 1n;
|
||||
const reportedSpan =
|
||||
body.span_bytes === null ? null : BigInt(body.span_bytes);
|
||||
if (
|
||||
total !== BigInt(body.total_bytes) ||
|
||||
span !== reportedSpan ||
|
||||
body.non_contiguous !== body.ranges.length > 1
|
||||
)
|
||||
context.addIssue({
|
||||
code: "custom",
|
||||
message:
|
||||
"Function body counts, enclosing span or contiguity disagree with inclusive ranges",
|
||||
});
|
||||
});
|
||||
|
||||
export const functionBodySchema = z.discriminatedUnion("available", [
|
||||
unavailableNativeFactSchema,
|
||||
availableFunctionBodySchema,
|
||||
]);
|
||||
|
||||
/** Check the declared entry membership against complete, inclusive body ranges. */
|
||||
export const functionBodyEntryAgrees = (
|
||||
body: z.infer<typeof functionBodySchema>,
|
||||
address: string,
|
||||
): boolean => {
|
||||
if (!body.available) return true;
|
||||
const entry = addressCoordinate(address);
|
||||
if (entry === null) return false;
|
||||
const contains = body.ranges.some((range) => {
|
||||
const start = addressCoordinate(range.start);
|
||||
const end = addressCoordinate(range.end);
|
||||
return (
|
||||
start !== null &&
|
||||
end !== null &&
|
||||
start.space === entry.space &&
|
||||
entry.offset >= start.offset &&
|
||||
entry.offset <= end.offset
|
||||
);
|
||||
});
|
||||
return contains === body.contains_entry;
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
import { createHash } from "node:crypto";
|
||||
import type { z } from "zod";
|
||||
import type { functionBodySchema } from "../hopperValues.js";
|
||||
import type { functionBodySchema } from "../native/functionBody.js";
|
||||
import type { nativeByteReadSchema } from "./nativeByteRead.js";
|
||||
import { addressCoordinate } from "./addressCoordinate.js";
|
||||
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
import { z } from "zod";
|
||||
import { err, ok, type Result } from "../result.js";
|
||||
import { AnalysisOutputError } from "../analysisErrorCore.js";
|
||||
import type { JsonValue } from "../jsonValue.js";
|
||||
import {
|
||||
procedureIdentitySchema,
|
||||
localVariableSchema,
|
||||
} from "./procedureInfo.js";
|
||||
import { analysisStringSchema } from "./nativeString.js";
|
||||
import {
|
||||
referenceEdgeSchema,
|
||||
unresolvedCallSchema,
|
||||
} from "./nativeReference.js";
|
||||
import {
|
||||
swiftStringLiteralSchema,
|
||||
swiftStringLiteralOmissionSchema,
|
||||
} from "./swiftStringLiteral.js";
|
||||
import { nativeApiBoundarySchema } from "./nativeApiBoundary.js";
|
||||
import { nativeValueFlowSchema } from "./nativeValueFlow.js";
|
||||
|
||||
/** Provider-neutral complete raw-instruction list for one analyzed function. */
|
||||
export const functionInstructionWindowSchema = z
|
||||
.object({
|
||||
procedure: procedureIdentitySchema,
|
||||
instructions: z.array(z.string()),
|
||||
limitations: z.array(z.string()),
|
||||
})
|
||||
.strict();
|
||||
|
||||
const facetAvailabilitySchema = z.discriminatedUnion("available", [
|
||||
z.object({ available: z.literal(true) }).strict(),
|
||||
z.object({ available: z.literal(false), reason: z.string().min(1) }).strict(),
|
||||
]);
|
||||
|
||||
/** Provider-declared availability per dossier facet; an omitted facet declares nothing. */
|
||||
export const functionFacetAvailabilitySchema = z
|
||||
.object({
|
||||
pseudocode: facetAvailabilitySchema.exactOptional(),
|
||||
assembly: facetAvailabilitySchema.exactOptional(),
|
||||
comments: facetAvailabilitySchema.exactOptional(),
|
||||
callers: facetAvailabilitySchema.exactOptional(),
|
||||
callees: facetAvailabilitySchema.exactOptional(),
|
||||
references: facetAvailabilitySchema.exactOptional(),
|
||||
strings_names: facetAvailabilitySchema.exactOptional(),
|
||||
cfg: facetAvailabilitySchema.exactOptional(),
|
||||
})
|
||||
.strict();
|
||||
|
||||
export const functionDossierSchema = z
|
||||
.object({
|
||||
procedure: procedureIdentitySchema.safeExtend({
|
||||
signature: z.string().nullable(),
|
||||
locals: z.array(localVariableSchema),
|
||||
}),
|
||||
pseudocode: z.string(),
|
||||
assembly: z.array(z.string()),
|
||||
comments: z.array(
|
||||
z
|
||||
.object({
|
||||
address: z.string(),
|
||||
kind: z.enum(["comment", "inline"]),
|
||||
text: z.string(),
|
||||
})
|
||||
.strict(),
|
||||
),
|
||||
callers: z.array(procedureIdentitySchema),
|
||||
callees: z.array(procedureIdentitySchema),
|
||||
incoming_references: z.array(referenceEdgeSchema),
|
||||
outgoing_references: z.array(referenceEdgeSchema),
|
||||
unresolved_calls: z.array(unresolvedCallSchema).exactOptional(),
|
||||
referenced_strings: z.array(
|
||||
analysisStringSchema.extend({ source_address: z.string() }),
|
||||
),
|
||||
referenced_names: z.array(
|
||||
z
|
||||
.object({
|
||||
address: z.string(),
|
||||
value: z.string(),
|
||||
source_address: z.string(),
|
||||
})
|
||||
.strict(),
|
||||
),
|
||||
swift_string_literals: z
|
||||
.array(swiftStringLiteralSchema)
|
||||
.exactOptional()
|
||||
.describe(
|
||||
"Swift String literals decoded from constant register values. Absent when the provider does not decode them for this target.",
|
||||
),
|
||||
swift_string_literal_omissions: z
|
||||
.array(swiftStringLiteralOmissionSchema)
|
||||
.exactOptional()
|
||||
.describe(
|
||||
"Literal candidates omitted for capacity or unresolved control flow. Present exactly when swift_string_literals is present; empty when all examined use sites were classified.",
|
||||
),
|
||||
basic_blocks: z.array(
|
||||
z
|
||||
.object({
|
||||
start: z.string(),
|
||||
end: z.string(),
|
||||
successors: z.array(z.string()),
|
||||
})
|
||||
.strict(),
|
||||
),
|
||||
native_api: nativeApiBoundarySchema.nullable(),
|
||||
native_value_flow: nativeValueFlowSchema.nullable(),
|
||||
facet_availability: functionFacetAvailabilitySchema.exactOptional(),
|
||||
limitations: z.array(z.string()),
|
||||
})
|
||||
.strict();
|
||||
|
||||
/** Strict analyzed-function dossier shared by provider and comparison boundaries. */
|
||||
export type FunctionDossier = z.infer<typeof functionDossierSchema>;
|
||||
|
||||
/** Strictly parse a complete provider-neutral function dossier. */
|
||||
export const parseFunctionDossier = (
|
||||
value: JsonValue,
|
||||
): Result<FunctionDossier, AnalysisOutputError> => {
|
||||
const parsed = functionDossierSchema.safeParse(value);
|
||||
return parsed.success
|
||||
? ok(parsed.data)
|
||||
: err(
|
||||
new AnalysisOutputError(
|
||||
"analyze_function",
|
||||
"provider output did not match the dossier contract",
|
||||
{ cause: parsed.error },
|
||||
),
|
||||
);
|
||||
};
|
||||
@@ -4,11 +4,11 @@ import type { Evidence } from "../evidence.js";
|
||||
import { EvidenceIntegrityError } from "../evidenceErrors.js";
|
||||
import { readEvidencePayload } from "../evidenceValidation.js";
|
||||
import { digestCanonicalValue } from "../canonicalDigest.js";
|
||||
import { functionDossierSchema } from "../native/functionDossier.js";
|
||||
import {
|
||||
functionBodySchema,
|
||||
functionBodyEntryAgrees,
|
||||
functionDossierSchema,
|
||||
} from "../hopperValues.js";
|
||||
} from "../native/functionBody.js";
|
||||
import { nativeSymbolOrigin } from "./nativeSymbol.js";
|
||||
import {
|
||||
addressedValueSchema,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { Evidence } from "../evidence.js";
|
||||
import type { FunctionDossier } from "../hopperValues.js";
|
||||
import type { FunctionDossier } from "../native/functionDossier.js";
|
||||
import { digestCanonicalValue } from "../canonicalDigest.js";
|
||||
import { normalizeCfg } from "../functionComparisonNormalization.js";
|
||||
import { hasStableNativeSymbol } from "./nativeSymbol.js";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { z } from "zod";
|
||||
import { analysisStringSchema } from "../hopperValues.js";
|
||||
import { analysisStringSchema } from "../native/nativeString.js";
|
||||
import { addressedValueSchema } from "./nativeInventory.js";
|
||||
|
||||
/** Tracing reuses the search row's qualifications without reinterpreting them. */
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import { z } from "zod";
|
||||
import { ok, type Result } from "../result.js";
|
||||
import { AnalysisOutputError } from "../analysisErrorCore.js";
|
||||
import type { JsonValue } from "../jsonValue.js";
|
||||
import {
|
||||
unwrapNativeProperty,
|
||||
invalidNativeOutput,
|
||||
} from "./nativeProviderOutput.js";
|
||||
|
||||
/** Parse direct or wrapped provider document names. */
|
||||
export const parseDocuments = (
|
||||
value: JsonValue,
|
||||
operation = "list_documents",
|
||||
): Result<readonly string[], AnalysisOutputError> => {
|
||||
const parsed = z
|
||||
.array(z.string())
|
||||
.safeParse(unwrapNativeProperty(value, "documents"));
|
||||
return parsed.success
|
||||
? ok(parsed.data)
|
||||
: invalidNativeOutput(operation, "document list", parsed.error);
|
||||
};
|
||||
@@ -0,0 +1,6 @@
|
||||
import { z } from "zod";
|
||||
|
||||
/** An explicitly unavailable provider fact preserves its reported reason. */
|
||||
export const unavailableNativeFactSchema = z
|
||||
.object({ available: z.literal(false), reason: z.string() })
|
||||
.strict();
|
||||
@@ -1,5 +1,5 @@
|
||||
import { z } from "zod";
|
||||
import { functionDossierSchema } from "../hopperValues.js";
|
||||
import { functionDossierSchema } from "../native/functionDossier.js";
|
||||
|
||||
/** One explicit function's analyst-authored metadata changes. */
|
||||
export const nativeFunctionAnnotationsInputSchema = z
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
import { z } from "zod";
|
||||
import { nativeSymbolMetadataSchema } from "./nativeSymbol.js";
|
||||
import { analysisStringSchema } from "../hopperValues.js";
|
||||
import { analysisStringSchema } from "./nativeString.js";
|
||||
import { ok, type Result } from "../result.js";
|
||||
import { AnalysisOutputError } from "../analysisErrorCore.js";
|
||||
import type { JsonValue } from "../jsonValue.js";
|
||||
import { jsonObjectSchema } from "../jsonValue.js";
|
||||
import {
|
||||
unwrapNativeProperty,
|
||||
invalidNativeOutput,
|
||||
} from "./nativeProviderOutput.js";
|
||||
|
||||
/** Canonical named native observations; same-address alternatives stay separate. */
|
||||
export const addressedValueSchema = z.object({
|
||||
@@ -49,3 +57,29 @@ export const isNativeInventoryOperation = (
|
||||
operation: string,
|
||||
): operation is NativeInventoryOperation =>
|
||||
NATIVE_INVENTORY_OPERATIONS.some((supported) => supported === operation);
|
||||
|
||||
export interface AddressedName {
|
||||
readonly address: string;
|
||||
readonly name: string;
|
||||
readonly objc_class_label?: "hopper_legacy_class_object";
|
||||
}
|
||||
|
||||
/** An analyzed bookmark retains a missing provider label as null. */
|
||||
export const analysisBookmarkSchema = z.strictObject({
|
||||
address: z.string(),
|
||||
name: z.string().nullable(),
|
||||
});
|
||||
/** Parse a direct or wrapped list when only its cardinality is required. */
|
||||
export const parseListCount = (
|
||||
value: JsonValue,
|
||||
property: string,
|
||||
operation = property === "procedures" ? "list_procedures" : "list_strings",
|
||||
): Result<number, AnalysisOutputError> => {
|
||||
const unwrapped = unwrapNativeProperty(value, property);
|
||||
const list = z.array(z.unknown()).safeParse(unwrapped);
|
||||
if (list.success) return ok(list.data.length);
|
||||
const map = jsonObjectSchema.safeParse(unwrapped);
|
||||
return map.success
|
||||
? ok(Object.keys(map.data).length)
|
||||
: invalidNativeOutput(operation, `${property} list`, map.error);
|
||||
};
|
||||
@@ -1,7 +1,51 @@
|
||||
import {
|
||||
unwrapNativeProperty,
|
||||
invalidNativeOutput,
|
||||
} from "./nativeProviderOutput.js";
|
||||
import { z } from "zod";
|
||||
import { containingProcedureResolutionSchema } from "../hopperValues.js";
|
||||
import { procedureIdentitySchema } from "./procedureInfo.js";
|
||||
import { AnalysisOutputError } from "../analysisErrorCore.js";
|
||||
import type { JsonValue } from "../jsonValue.js";
|
||||
import { err, ok, type Result } from "../result.js";
|
||||
import { nativeCallGraphSchema } from "./nativeCallGraph.js";
|
||||
|
||||
/** Exact containing-procedure outcome; valid absence is distinct from unreadable output. */
|
||||
export const containingProcedureResolutionSchema = z.discriminatedUnion(
|
||||
"found",
|
||||
[
|
||||
z.object({
|
||||
query_address: z.string(),
|
||||
found: z.literal(true),
|
||||
procedure: procedureIdentitySchema,
|
||||
}),
|
||||
z.object({
|
||||
query_address: z.string(),
|
||||
found: z.literal(false),
|
||||
procedure: z.null(),
|
||||
reason: z.enum(["outside_segments", "not_in_procedure"]),
|
||||
}),
|
||||
],
|
||||
);
|
||||
export type ContainingProcedureResolution = z.infer<
|
||||
typeof containingProcedureResolutionSchema
|
||||
>;
|
||||
|
||||
/** Validate provider procedure facts before a workflow interprets absence. */
|
||||
export const parseContainingProcedureResolution = (
|
||||
value: JsonValue,
|
||||
): Result<ContainingProcedureResolution, AnalysisOutputError> => {
|
||||
const parsed = containingProcedureResolutionSchema.safeParse(value);
|
||||
return parsed.success
|
||||
? ok(parsed.data)
|
||||
: err(
|
||||
new AnalysisOutputError(
|
||||
"resolve_containing_procedure",
|
||||
"expected a containing-procedure resolution",
|
||||
{ cause: parsed.error },
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
export const NATIVE_PRIMITIVE_OBSERVATION_OPERATIONS = [
|
||||
"procedure_address",
|
||||
"procedure_callees",
|
||||
@@ -34,3 +78,19 @@ export const isNativeObservationOperation = (
|
||||
operation: string,
|
||||
): operation is NativeObservationOperation =>
|
||||
NATIVE_OBSERVATION_OPERATIONS.some((supported) => supported === operation);
|
||||
|
||||
/** Parse callee/caller strings from direct or wrapped provider results. */
|
||||
export const parseRelatedAddresses = (
|
||||
value: JsonValue,
|
||||
relation: "callees" | "callers",
|
||||
operation = relation === "callees"
|
||||
? "procedure_callees"
|
||||
: "procedure_callers",
|
||||
): Result<readonly string[], AnalysisOutputError> => {
|
||||
const parsed = z
|
||||
.array(z.string())
|
||||
.safeParse(unwrapNativeProperty(value, relation));
|
||||
return parsed.success
|
||||
? ok(parsed.data)
|
||||
: invalidNativeOutput(operation, `${relation} list`, parsed.error);
|
||||
};
|
||||
@@ -0,0 +1,129 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { addressDistance } from "./addressCoordinate.js";
|
||||
import { parseDocuments } from "./nativeDocument.js";
|
||||
import { parseListCount } from "./nativeInventory.js";
|
||||
import { parseRelatedAddresses } from "./nativeObservation.js";
|
||||
import { parseSegments } from "./nativeSegments.js";
|
||||
|
||||
describe("native provider boundary values", () => {
|
||||
it("distinguishes measured zero, malformed, incomparable, reversed, and oversized extents", () => {
|
||||
expect(addressDistance("not-an-address", "0x2000")).toBeNull();
|
||||
expect(addressDistance("ram:0x1000", "rom:0x2000")).toBeNull();
|
||||
expect(addressDistance("0x2000", "0x1000")).toBeNull();
|
||||
expect(addressDistance("0x1000", "0x1000")).toBe(0);
|
||||
expect(addressDistance("0x1000", "0x2000")).toBe(4096);
|
||||
expect(addressDistance("0x0", "0x20000000000000")).toBeNull();
|
||||
});
|
||||
|
||||
it("rejects segments when required coordinates or unknown permissions are omitted", () => {
|
||||
expect(
|
||||
parseSegments([
|
||||
{
|
||||
name: "__TEXT",
|
||||
start: "0x1",
|
||||
end: "0x2",
|
||||
readable: null,
|
||||
writable: null,
|
||||
},
|
||||
]).ok,
|
||||
).toBe(false);
|
||||
expect(
|
||||
parseSegments([{ name: "__TEXT", start: "0x1", end: "0x2" }]).ok,
|
||||
).toBe(false);
|
||||
});
|
||||
it("counts Hopper's address-keyed string maps", () => {
|
||||
expect(
|
||||
parseListCount({ "0x1000": "hello", "0x2000": "world" }, "strings"),
|
||||
).toEqual({ ok: true, value: 2 });
|
||||
});
|
||||
|
||||
it("parses direct and wrapped protocol shapes", () => {
|
||||
expect(parseRelatedAddresses({ callers: ["0x3"] }, "callers")).toEqual({
|
||||
ok: true,
|
||||
value: ["0x3"],
|
||||
});
|
||||
expect(
|
||||
parseSegments([
|
||||
{
|
||||
name: "__TEXT",
|
||||
start: "0x1",
|
||||
end: "0x2",
|
||||
readable: true,
|
||||
writable: false,
|
||||
executable: null,
|
||||
},
|
||||
]),
|
||||
).toEqual({
|
||||
ok: true,
|
||||
value: [
|
||||
{
|
||||
name: "__TEXT",
|
||||
start: "0x1",
|
||||
end: "0x2",
|
||||
readable: true,
|
||||
writable: false,
|
||||
executable: null,
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(parseDocuments(["fixture"])).toEqual({
|
||||
ok: true,
|
||||
value: ["fixture"],
|
||||
});
|
||||
});
|
||||
|
||||
it.each([
|
||||
["relations", () => parseRelatedAddresses([1], "callees")],
|
||||
["segments", () => parseSegments([{ name: 1 }])],
|
||||
["documents", () => parseDocuments([1])],
|
||||
])(
|
||||
"rejects malformed %s instead of manufacturing empty output",
|
||||
(_label, parse) => {
|
||||
const result = parse();
|
||||
expect(result.ok).toBe(false);
|
||||
if (!result.ok) expect(result.error._tag).toBe("AnalysisOutputError");
|
||||
},
|
||||
);
|
||||
|
||||
it("uses provider-neutral wording for malformed shared values", () => {
|
||||
const result = parseSegments([{ name: 1 }]);
|
||||
expect(result.ok).toBe(false);
|
||||
if (!result.ok) {
|
||||
expect(result.error._tag).toBe("AnalysisOutputError");
|
||||
expect(result.error.operation).toBe("list_segments");
|
||||
expect(result.error.reason).toBe(
|
||||
"Provider returned an invalid segment list",
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it.each(["procedures", "strings"])(
|
||||
"counts every own entry of direct and wrapped %s maps",
|
||||
(property) => {
|
||||
for (const text of [
|
||||
'{"__proto__":"observed"}',
|
||||
'{"__proto__":"observed","0x1000":"one"}',
|
||||
]) {
|
||||
const source = JSON.parse(text);
|
||||
const count = Object.keys(source).length;
|
||||
expect(parseListCount(source, property)).toEqual({
|
||||
ok: true,
|
||||
value: count,
|
||||
});
|
||||
expect(parseListCount({ [property]: source }, property)).toEqual({
|
||||
ok: true,
|
||||
value: count,
|
||||
});
|
||||
expect(JSON.stringify(source)).toBe(text);
|
||||
}
|
||||
expect(parseListCount({ [property]: [] }, property)).toEqual({
|
||||
ok: true,
|
||||
value: 0,
|
||||
});
|
||||
expect(parseListCount({}, property)).toEqual({ ok: true, value: 0 });
|
||||
for (const value of [null, "not a list", 3, false])
|
||||
expect(parseListCount(value, property).ok).toBe(false);
|
||||
},
|
||||
);
|
||||
@@ -0,0 +1,34 @@
|
||||
import { z } from "zod";
|
||||
import { err, type Result } from "../result.js";
|
||||
import { AnalysisOutputError } from "../analysisErrorCore.js";
|
||||
import type { JsonValue } from "../jsonValue.js";
|
||||
|
||||
export const unwrapNativeProperty = (
|
||||
value: JsonValue,
|
||||
property: string,
|
||||
): JsonValue => {
|
||||
if (
|
||||
typeof value === "object" &&
|
||||
value !== null &&
|
||||
!Array.isArray(value) &&
|
||||
property in value
|
||||
) {
|
||||
return value[property] ?? null;
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
export const invalidNativeOutput = <T>(
|
||||
operation: string,
|
||||
expected: string,
|
||||
cause: z.ZodError,
|
||||
): Result<T, AnalysisOutputError> =>
|
||||
err(
|
||||
new AnalysisOutputError(
|
||||
operation,
|
||||
`Provider returned an invalid ${expected}`,
|
||||
{
|
||||
cause,
|
||||
},
|
||||
),
|
||||
);
|
||||
@@ -0,0 +1,46 @@
|
||||
import { z } from "zod";
|
||||
import { unavailableNativeFactSchema } from "./nativeFactAvailability.js";
|
||||
import { procedureIdentitySchema } from "./procedureInfo.js";
|
||||
import { nativeCallClassificationSchema } from "./nativeCallReference.js";
|
||||
|
||||
export const availableReferenceKindSchema = z
|
||||
.object({
|
||||
available: z.literal(true),
|
||||
provenance: z.string().min(1),
|
||||
type: z.string(),
|
||||
flow: z.boolean(),
|
||||
call: z.boolean(),
|
||||
jump: z.boolean(),
|
||||
data: z.boolean(),
|
||||
read: z.boolean(),
|
||||
write: z.boolean(),
|
||||
indirect: z.boolean(),
|
||||
computed: z.boolean(),
|
||||
conditional: z.boolean(),
|
||||
terminal: z.boolean(),
|
||||
primary: z.boolean(),
|
||||
operand_index: z.int(),
|
||||
external: z.boolean(),
|
||||
})
|
||||
.strict();
|
||||
export const referenceKindSchema = z.discriminatedUnion("available", [
|
||||
unavailableNativeFactSchema,
|
||||
availableReferenceKindSchema,
|
||||
]);
|
||||
/** Reported call sites without resolved targets; unreported dispatch stays unknown. */
|
||||
export const unresolvedCallSchema = z.strictObject({
|
||||
address: z.string(),
|
||||
reason: z.string(),
|
||||
});
|
||||
|
||||
/** Observed endpoints with complete or partial reference classification evidence. */
|
||||
export const referenceEdgeSchema = z
|
||||
.object({
|
||||
source_address: z.string(),
|
||||
target_address: z.string(),
|
||||
source_procedure: procedureIdentitySchema.nullable(),
|
||||
target_procedure: procedureIdentitySchema.nullable(),
|
||||
kind: referenceKindSchema,
|
||||
call: nativeCallClassificationSchema.exactOptional(),
|
||||
})
|
||||
.strict();
|
||||
@@ -0,0 +1,87 @@
|
||||
import { z } from "zod";
|
||||
import { ok, type Result } from "../result.js";
|
||||
import { AnalysisOutputError } from "../analysisErrorCore.js";
|
||||
import type { JsonValue } from "../jsonValue.js";
|
||||
import {
|
||||
unwrapNativeProperty,
|
||||
invalidNativeOutput,
|
||||
} from "./nativeProviderOutput.js";
|
||||
import { addressDistance } from "./addressCoordinate.js";
|
||||
|
||||
export interface SegmentSummary {
|
||||
readonly name: string;
|
||||
readonly start: string;
|
||||
readonly end: string;
|
||||
readonly readable: boolean | null;
|
||||
readonly writable: boolean | null;
|
||||
readonly executable: boolean | null;
|
||||
readonly image_base?: string;
|
||||
readonly address_space?: string;
|
||||
}
|
||||
|
||||
const segmentSchema = z.object({
|
||||
name: z.string(),
|
||||
start: z.string(),
|
||||
end: z.string(),
|
||||
readable: z.boolean().nullable(),
|
||||
writable: z.boolean().nullable(),
|
||||
executable: z.boolean().nullable(),
|
||||
image_base: z.string().min(1).exactOptional(),
|
||||
address_space: z.string().min(1).exactOptional(),
|
||||
});
|
||||
/** Preserve reported address context in direct or wrapped provider segments. */
|
||||
export const parseSegments = (
|
||||
value: JsonValue,
|
||||
operation = "list_segments",
|
||||
): Result<readonly SegmentSummary[], AnalysisOutputError> => {
|
||||
const parsed = z
|
||||
.array(segmentSchema)
|
||||
.safeParse(unwrapNativeProperty(value, "segments"));
|
||||
return parsed.success
|
||||
? ok(parsed.data)
|
||||
: invalidNativeOutput(operation, "segment list", parsed.error);
|
||||
};
|
||||
|
||||
/** Return a reported overview base only when every parsed segment agrees. */
|
||||
export const parseSegmentImageBase = (
|
||||
segments: readonly SegmentSummary[],
|
||||
): string | undefined => {
|
||||
const base = segments[0]?.image_base;
|
||||
return base !== undefined &&
|
||||
segments.every((segment) => segment.image_base === base)
|
||||
? base
|
||||
: undefined;
|
||||
};
|
||||
|
||||
/** Project the same overview from complete lists or provider-owned aggregates. */
|
||||
export const projectBinaryOverview = (input: {
|
||||
readonly document: string;
|
||||
readonly procedure_count: number;
|
||||
readonly string_count: number;
|
||||
readonly segments?: readonly SegmentSummary[];
|
||||
}): JsonValue => {
|
||||
const { document, procedure_count, string_count, segments } = input;
|
||||
const imageBase =
|
||||
segments === undefined ? undefined : parseSegmentImageBase(segments);
|
||||
return {
|
||||
document,
|
||||
...(imageBase === undefined ? {} : { image_base: imageBase }),
|
||||
...(segments === undefined
|
||||
? {}
|
||||
: {
|
||||
segments: segments.map(
|
||||
({ name, start, end, image_base, address_space }) => ({
|
||||
name,
|
||||
start,
|
||||
end,
|
||||
length: addressDistance(start, end),
|
||||
...(image_base === undefined ? {} : { image_base }),
|
||||
...(address_space === undefined ? {} : { address_space }),
|
||||
}),
|
||||
),
|
||||
segment_count: segments.length,
|
||||
}),
|
||||
procedure_count,
|
||||
string_count,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
import { z } from "zod";
|
||||
import { unavailableNativeFactSchema } from "./nativeFactAvailability.js";
|
||||
|
||||
/** Typed string evidence shared by inventories and referenced function literals. */
|
||||
export const analysisStringSchema = z.object({
|
||||
address: z.string(),
|
||||
value: z.string(),
|
||||
provider_value: z
|
||||
.string()
|
||||
.exactOptional()
|
||||
.describe(
|
||||
"Original provider display text, which may shorten the typed object.",
|
||||
),
|
||||
decoding: unavailableNativeFactSchema
|
||||
.exactOptional()
|
||||
.describe(
|
||||
"When present, value retains native display text because typed bytes could not be decoded.",
|
||||
),
|
||||
string: z
|
||||
.object({
|
||||
encoding: z.string().min(1),
|
||||
encoding_status: z
|
||||
.enum(["observed", "inferred"])
|
||||
.exactOptional()
|
||||
.describe(
|
||||
"Whether the provider declares the encoding or REA infers it from the typed bytes and native display. This does not establish the original source's intended encoding.",
|
||||
),
|
||||
termination: z.enum(["missing", "present_or_not_required"]),
|
||||
byte_length: z.int().min(0),
|
||||
})
|
||||
.exactOptional(),
|
||||
});
|
||||
@@ -1,11 +1,47 @@
|
||||
import { nativeSymbolMetadataSchema } from "./nativeSymbol.js";
|
||||
import { z } from "zod";
|
||||
import {
|
||||
functionBodySchema,
|
||||
localVariableSchema,
|
||||
procedureClassificationSchema,
|
||||
} from "../hopperValues.js";
|
||||
import { nativeSymbolMetadataSchema } from "./nativeSymbol.js";
|
||||
import { functionBodySchema, functionBodyEntryAgrees } from "./functionBody.js";
|
||||
|
||||
export const procedureClassificationSchema = z
|
||||
.object({
|
||||
external: z.boolean(),
|
||||
thunk: z.boolean(),
|
||||
thunk_target: z.string().nullable(),
|
||||
provenance: z.string().min(1),
|
||||
})
|
||||
.strict();
|
||||
export const procedureIdentitySchema = z
|
||||
.object({
|
||||
address: z.string(),
|
||||
name: z.string(),
|
||||
symbol: nativeSymbolMetadataSchema.nullable().exactOptional(),
|
||||
classification: procedureClassificationSchema.nullable(),
|
||||
body: functionBodySchema,
|
||||
})
|
||||
.strict()
|
||||
.superRefine((identity, context) => {
|
||||
if (!functionBodyEntryAgrees(identity.body, identity.address))
|
||||
context.addIssue({
|
||||
code: "custom",
|
||||
message:
|
||||
"Function body entry membership disagrees with procedure identity",
|
||||
});
|
||||
});
|
||||
export const localVariableSchema = z
|
||||
.object({
|
||||
description: z.string(),
|
||||
provenance: z.string().min(1),
|
||||
name: z.string().nullable().exactOptional(),
|
||||
stack_displacement: z
|
||||
.string()
|
||||
.regex(/^-?\d+$/u)
|
||||
.nullable()
|
||||
.exactOptional()
|
||||
.describe(
|
||||
"Observed signed stack byte displacement, encoded as decimal to preserve precision.",
|
||||
),
|
||||
})
|
||||
.strict();
|
||||
/** Provider-neutral procedure metadata, including exact body ranges. */
|
||||
export const procedureInfoSchema = z.object({
|
||||
name: z.string(),
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
import { z } from "zod";
|
||||
import { fixedTupleSchema } from "../fixedTupleSchema.js";
|
||||
|
||||
const swiftStringLiteralFields = {
|
||||
source_address: z
|
||||
.string()
|
||||
.describe(
|
||||
"Instruction that consumes both words: a call, tail call, return, or paired store.",
|
||||
),
|
||||
value: z
|
||||
.string()
|
||||
.describe("Decoded UTF-8 text, including the canonical empty string."),
|
||||
registers: fixedTupleSchema([z.string(), z.string()]),
|
||||
words: fixedTupleSchema([z.string(), z.string()]).describe(
|
||||
"Observed constant register values, in register order.",
|
||||
),
|
||||
};
|
||||
|
||||
/** A Swift String literal decoded from the two words of its ABI value, not a data reference. */
|
||||
export const swiftSmallStringLiteralSchema = z.strictObject({
|
||||
...swiftStringLiteralFields,
|
||||
representation: z
|
||||
.literal("small")
|
||||
.describe("The UTF-8 bytes are inside the two words."),
|
||||
address: z.null().describe("A small literal has no storage of its own."),
|
||||
});
|
||||
export const swiftLargeStringLiteralSchema = z.strictObject({
|
||||
...swiftStringLiteralFields,
|
||||
representation: z
|
||||
.literal("large")
|
||||
.describe(
|
||||
"An immortal literal whose words hold its count and a pointer to its bytes.",
|
||||
),
|
||||
address: z.string().describe("Address of a large literal's bytes."),
|
||||
});
|
||||
export const swiftStringLiteralSchema = z.discriminatedUnion("representation", [
|
||||
swiftSmallStringLiteralSchema,
|
||||
swiftLargeStringLiteralSchema,
|
||||
]);
|
||||
|
||||
const swiftStringLiteralOmissionFields = {
|
||||
source_address: swiftStringLiteralFields.source_address,
|
||||
byte_length: z.int().min(0).max(Number.MAX_SAFE_INTEGER),
|
||||
reason: z
|
||||
.enum(["capacity", "unresolved_jump"])
|
||||
.describe(
|
||||
"Why this candidate is omitted: capacity stopped decoding, or an unresolved jump left its use unknown.",
|
||||
),
|
||||
registers: swiftStringLiteralFields.registers,
|
||||
words: swiftStringLiteralFields.words,
|
||||
};
|
||||
|
||||
/** A Swift literal candidate omitted because decoding capacity or control flow left it unknown. */
|
||||
export const swiftSmallStringLiteralOmissionSchema = z.strictObject({
|
||||
...swiftStringLiteralOmissionFields,
|
||||
representation: z.literal("small"),
|
||||
address: z.null(),
|
||||
});
|
||||
export const swiftLargeStringLiteralOmissionSchema = z.strictObject({
|
||||
...swiftStringLiteralOmissionFields,
|
||||
representation: z.literal("large"),
|
||||
address: z.string().describe("Address of the literal's bytes."),
|
||||
});
|
||||
export const swiftStringLiteralOmissionSchema = z.discriminatedUnion(
|
||||
"representation",
|
||||
[
|
||||
swiftSmallStringLiteralOmissionSchema,
|
||||
swiftLargeStringLiteralOmissionSchema,
|
||||
],
|
||||
);
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
type EvidenceSubjectTarget,
|
||||
} from "./evidence.js";
|
||||
import { createEvidenceBundle, parseEvidenceBundle } from "./evidenceBundle.js";
|
||||
import { functionDossierSchema } from "./hopperValues.js";
|
||||
import { functionDossierSchema } from "./native/functionDossier.js";
|
||||
import {
|
||||
jsonObjectSchema,
|
||||
jsonValueSchema,
|
||||
|
||||
@@ -13,7 +13,7 @@ import { AnalysisInputError } from "./analysisErrorCore.js";
|
||||
import { EvidenceIntegrityError } from "./evidenceErrors.js";
|
||||
import { analysisInputErrorFromIssues } from "./inputIssueProjection.js";
|
||||
import { readEvidencePayload } from "./evidenceValidation.js";
|
||||
import { functionDossierSchema } from "./hopperValues.js";
|
||||
import { functionDossierSchema } from "./native/functionDossier.js";
|
||||
import { assessFunctionCallCoverage } from "./functionCallCoverage.js";
|
||||
import { processCaptureSchema } from "./process/processCapture.js";
|
||||
import { digestCanonicalValue } from "./canonicalDigest.js";
|
||||
|
||||
@@ -11,7 +11,7 @@ import { createEvidence, type Evidence } from "./evidence.js";
|
||||
import { functionComparisonResultSchema } from "./functionComparisonSchemas.js";
|
||||
import { jsonValueSchema, type JsonValue } from "./jsonValue.js";
|
||||
import { FUNCTION_COMPARISON_EXAMPLE } from "../contracts/functionComparisonExample.js";
|
||||
import { functionDossierSchema } from "./hopperValues.js";
|
||||
import { functionDossierSchema } from "./native/functionDossier.js";
|
||||
import { compareFunctions } from "./functionComparison.js";
|
||||
import { functionSnapshotFixture } from "./functionDossierEvidence.fixture.js";
|
||||
import {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { objcClassSymbol } from "./objcClassSymbol.js";
|
||||
import type { JsonValue } from "./jsonValue.js";
|
||||
import type { AddressedName } from "./hopperValues.js";
|
||||
import type { AddressedName } from "./native/nativeInventory.js";
|
||||
import type {
|
||||
MachoImportBinding,
|
||||
ObjcClassImportBindingCoverage,
|
||||
|
||||
@@ -3,7 +3,7 @@ import { describe, expect, it } from "vitest";
|
||||
|
||||
import type { JsonValue } from "../domain/jsonValue.js";
|
||||
import { jsonValueSchema } from "../domain/jsonValue.js";
|
||||
import { functionDossierSchema } from "../domain/hopperValues.js";
|
||||
import { functionDossierSchema } from "../domain/native/functionDossier.js";
|
||||
import {
|
||||
compactReference,
|
||||
compactInstruction,
|
||||
|
||||
@@ -20,16 +20,20 @@ import {
|
||||
AnalysisOutputError,
|
||||
} from "../domain/analysisErrorCore.js";
|
||||
import {
|
||||
functionBodyEntryAgrees,
|
||||
functionInstructionWindowSchema,
|
||||
functionDossierSchema,
|
||||
} from "../domain/native/functionDossier.js";
|
||||
import {
|
||||
availableReferenceKindSchema,
|
||||
unresolvedCallSchema,
|
||||
} from "../domain/native/nativeReference.js";
|
||||
import {
|
||||
swiftSmallStringLiteralSchema,
|
||||
swiftLargeStringLiteralSchema,
|
||||
swiftSmallStringLiteralOmissionSchema,
|
||||
swiftLargeStringLiteralOmissionSchema,
|
||||
} from "../domain/hopperValues.js";
|
||||
} from "../domain/native/swiftStringLiteral.js";
|
||||
import { functionBodyEntryAgrees } from "../domain/native/functionBody.js";
|
||||
import {
|
||||
jsonObjectSchema,
|
||||
jsonValueSchema,
|
||||
|
||||
@@ -19,12 +19,12 @@ import {
|
||||
type AnalysisInputError,
|
||||
AnalysisOutputError,
|
||||
} from "../domain/analysisErrorCore.js";
|
||||
import { projectBinaryOverview } from "../domain/native/nativeSegments.js";
|
||||
import {
|
||||
availableFunctionBodySchema,
|
||||
procedureIdentitySchema,
|
||||
functionBodyEntryAgrees,
|
||||
projectBinaryOverview,
|
||||
} from "../domain/hopperValues.js";
|
||||
} from "../domain/native/functionBody.js";
|
||||
import { procedureIdentitySchema } from "../domain/native/procedureInfo.js";
|
||||
import { jsonValueSchema, type JsonValue } from "../domain/jsonValue.js";
|
||||
import { err, ok, type Result } from "../domain/result.js";
|
||||
import { analysisInputErrorFromIssues } from "../domain/inputIssueProjection.js";
|
||||
|
||||
@@ -6,7 +6,7 @@ import { createTestBinarySession } from "../../tests/fixtures/binarySession.js";
|
||||
import { parseConfig } from "../config/parseConfig.js";
|
||||
import type { AnalysisOperation } from "../application/AnalysisProvider.js";
|
||||
import type { BinaryTarget } from "../domain/binaryTargetTypes.js";
|
||||
import { functionDossierSchema } from "../domain/hopperValues.js";
|
||||
import { functionDossierSchema } from "../domain/native/functionDossier.js";
|
||||
import { createAnalysisProfile } from "../domain/analysisProfile.js";
|
||||
import { ghidraFunctionDossier } from "../domain/ghidraValues.fixture.js";
|
||||
import { silentLogger } from "../logger.js";
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
AnalysisCancelledError,
|
||||
AnalysisOutputError,
|
||||
} from "../domain/analysisErrorCore.js";
|
||||
import { analysisStringSchema } from "../domain/hopperValues.js";
|
||||
import { analysisStringSchema } from "../domain/native/nativeString.js";
|
||||
import { HopperRemoteError } from "../domain/hopperErrors.js";
|
||||
import type { JsonValue } from "../domain/jsonValue.js";
|
||||
import { err, ok, type Result } from "../domain/result.js";
|
||||
|
||||
@@ -25,7 +25,7 @@ import {
|
||||
import {
|
||||
functionDossierSchema,
|
||||
functionInstructionWindowSchema,
|
||||
} from "../domain/hopperValues.js";
|
||||
} from "../domain/native/functionDossier.js";
|
||||
import type { JsonValue } from "../domain/jsonValue.js";
|
||||
import type { RelationshipCoverage } from "../domain/relationshipCoverage.js";
|
||||
import { combineFunctionRelationshipCoverage } from "../domain/functionCallCoverage.js";
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
DEFAULT_REGEX_MATCHING_DEADLINE,
|
||||
REGEX_MATCHING_DEADLINE_SETTING,
|
||||
} from "../config/regexMatchingDeadline.js";
|
||||
import { functionDossierSchema } from "../domain/hopperValues.js";
|
||||
import { functionDossierSchema } from "../domain/native/functionDossier.js";
|
||||
import { jsonValueSchema, type JsonValue } from "../domain/jsonValue.js";
|
||||
import {
|
||||
AnalysisProtocolError,
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
AnalysisResourceConstraintError,
|
||||
} from "../domain/analysisErrorCore.js";
|
||||
import type { JsonValue } from "../domain/jsonValue.js";
|
||||
import type { FunctionDossier } from "../domain/hopperValues.js";
|
||||
import type { FunctionDossier } from "../domain/native/functionDossier.js";
|
||||
import type { RelationshipCoverage } from "../domain/relationshipCoverage.js";
|
||||
import { ProviderAdapterError } from "../domain/providerAdapterError.js";
|
||||
import { err, ok, type Result } from "../domain/result.js";
|
||||
|
||||
@@ -34,7 +34,7 @@ import {
|
||||
import type { BinaryTarget } from "../domain/binaryTargetTypes.js";
|
||||
import type { EvidenceLocation } from "../domain/evidence.js";
|
||||
import { combineFunctionRelationshipCoverage } from "../domain/functionCallCoverage.js";
|
||||
import { functionDossierSchema } from "../domain/hopperValues.js";
|
||||
import { functionDossierSchema } from "../domain/native/functionDossier.js";
|
||||
import { analysisInputErrorFromIssues } from "../domain/inputIssueProjection.js";
|
||||
import type { JsonValue } from "../domain/jsonValue.js";
|
||||
import { projectAnalysisError } from "../domain/analysisErrorProjection.js";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import type { AnalysisError } from "../domain/analysisErrorBase.js";
|
||||
import type { analysisStringSchema } from "../domain/hopperValues.js";
|
||||
import type { analysisStringSchema } from "../domain/native/nativeString.js";
|
||||
import { err, ok, type Result } from "../domain/result.js";
|
||||
import { compareKunaAddresses } from "./KunaAddress.js";
|
||||
import { KunaJsonDocumentReader, parseKunaRow } from "./KunaJsonStream.js";
|
||||
|
||||
@@ -9,7 +9,7 @@ import { registerCoreBinaryCommands } from "../../../src/cli/coreBinaryCommands.
|
||||
import type { CliInstance } from "../../../src/cli/types.js";
|
||||
import { CLI_COMMANDS } from "../../../src/cliCommandNames.js";
|
||||
import { createEvidence } from "../../../src/domain/evidence.js";
|
||||
import { functionDossierSchema } from "../../../src/domain/hopperValues.js";
|
||||
import { functionDossierSchema } from "../../../src/domain/native/functionDossier.js";
|
||||
import { ghidraFunctionDossier } from "../../../src/domain/ghidraValues.fixture.js";
|
||||
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ import {
|
||||
jsonObjectSchema,
|
||||
type JsonValue,
|
||||
} from "../../../src/domain/jsonValue.js";
|
||||
import { functionDossierSchema } from "../../../src/domain/hopperValues.js";
|
||||
import { functionDossierSchema } from "../../../src/domain/native/functionDossier.js";
|
||||
import { inspectMachoSchema } from "../../../src/domain/native/nativeInspection.js";
|
||||
import { ghidraFunctionDossier } from "../../../src/domain/ghidraValues.fixture.js";
|
||||
import { ok } from "../../../src/domain/result.js";
|
||||
|
||||
@@ -16,7 +16,7 @@ import { join } from "node:path";
|
||||
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
||||
import type { JsonValue } from "../../../src/domain/jsonValue.js";
|
||||
import { ok } from "../../../src/domain/result.js";
|
||||
import { functionDossierSchema } from "../../../src/domain/hopperValues.js";
|
||||
import { functionDossierSchema } from "../../../src/domain/native/functionDossier.js";
|
||||
import { ghidraFunctionDossier } from "../../../src/domain/ghidraValues.fixture.js";
|
||||
import { parseGhidraFunctionInput } from "../../../src/ghidra/GhidraFunctionValues.js";
|
||||
import { EvidenceLedger } from "../../../src/application/investigation/EvidenceLedger.js";
|
||||
|
||||
@@ -4,7 +4,7 @@ import { expect, it } from "vitest";
|
||||
import { FUNCTION_COMPARISON_EXAMPLE } from "../../../src/contracts/functionComparisonExample.js";
|
||||
import { callPathResultSchema } from "../../../src/domain/callPathSchemas.js";
|
||||
import { createEvidence, parseEvidence } from "../../../src/domain/evidence.js";
|
||||
import { functionDossierSchema } from "../../../src/domain/hopperValues.js";
|
||||
import { functionDossierSchema } from "../../../src/domain/native/functionDossier.js";
|
||||
import { createServer } from "../../../src/server/createServer.js";
|
||||
import { createTestBinarySession } from "../../fixtures/binarySession.js";
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { parseMcpToolError } from "../../fixtures/mcpToolError.js";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
|
||||
import { functionDossierSchema } from "../../../src/domain/hopperValues.js";
|
||||
import { functionDossierSchema } from "../../../src/domain/native/functionDossier.js";
|
||||
import { ghidraFunctionDossier } from "../../../src/domain/ghidraValues.fixture.js";
|
||||
import { observed } from "../../fixtures/analysisExecution.js";
|
||||
import { closeEnhancedToolResources, connect } from "./enhancedToolsHarness.js";
|
||||
|
||||
@@ -4,7 +4,7 @@ import { expect, it } from "vitest";
|
||||
|
||||
import { FUNCTION_COMPARISON_EXAMPLE } from "../../../src/contracts/functionComparisonExample.js";
|
||||
import { createEvidence } from "../../../src/domain/evidence.js";
|
||||
import { functionDossierSchema } from "../../../src/domain/hopperValues.js";
|
||||
import { functionDossierSchema } from "../../../src/domain/native/functionDossier.js";
|
||||
import type { RelationshipCoverage } from "../../../src/domain/relationshipCoverage.js";
|
||||
import { createServer } from "../../../src/server/createServer.js";
|
||||
import { createTestBinarySession } from "../../fixtures/binarySession.js";
|
||||
|
||||
@@ -3,7 +3,7 @@ import { expect, it } from "vitest";
|
||||
|
||||
import { FUNCTION_COMPARISON_EXAMPLE } from "../../../src/contracts/functionComparisonExample.js";
|
||||
import { createEvidence, parseEvidence } from "../../../src/domain/evidence.js";
|
||||
import { functionDossierSchema } from "../../../src/domain/hopperValues.js";
|
||||
import { functionDossierSchema } from "../../../src/domain/native/functionDossier.js";
|
||||
import { jsonValueSchema } from "../../../src/domain/jsonValue.js";
|
||||
import { createServer } from "../../../src/server/createServer.js";
|
||||
import { createTestBinarySession } from "../../fixtures/binarySession.js";
|
||||
|
||||
@@ -16,7 +16,7 @@ import { createEvidence, parseEvidence } from "../../../src/domain/evidence.js";
|
||||
import { createServer } from "../../../src/server/createServer.js";
|
||||
import { observed } from "../../fixtures/analysisExecution.js";
|
||||
import { reconstructionVerificationResultSchema } from "../../../src/domain/reconstructionVerificationSchemas.js";
|
||||
import { functionDossierSchema } from "../../../src/domain/hopperValues.js";
|
||||
import { functionDossierSchema } from "../../../src/domain/native/functionDossier.js";
|
||||
|
||||
it.each([
|
||||
{ leftSpace: "ram", rightSpace: "ram" },
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
} from "../../../src/domain/analysisProfile.js";
|
||||
import { createEvidence, parseEvidence } from "../../../src/domain/evidence.js";
|
||||
import { functionComparisonResultSchema } from "../../../src/domain/functionComparisonSchemas.js";
|
||||
import { functionDossierSchema } from "../../../src/domain/hopperValues.js";
|
||||
import { functionDossierSchema } from "../../../src/domain/native/functionDossier.js";
|
||||
|
||||
import { reconstructionVerificationResultSchema } from "../../../src/domain/reconstructionVerificationSchemas.js";
|
||||
import { createApplicationMcpHarness } from "../../fixtures/applicationMcpHarness.js";
|
||||
|
||||
@@ -3,7 +3,7 @@ import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
||||
|
||||
import { FUNCTION_COMPARISON_EXAMPLE } from "../../../src/contracts/functionComparisonExample.js";
|
||||
import { createEvidence, parseEvidence } from "../../../src/domain/evidence.js";
|
||||
import { functionDossierSchema } from "../../../src/domain/hopperValues.js";
|
||||
import { functionDossierSchema } from "../../../src/domain/native/functionDossier.js";
|
||||
import {
|
||||
functionComparisonResultSchema,
|
||||
type FunctionComparisonResult as Comparison,
|
||||
|
||||
@@ -2,7 +2,7 @@ import { parseMcpToolError } from "../../fixtures/mcpToolError.js";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { EnhancedTools } from "../../../src/application/EnhancedTools.js";
|
||||
import { connectGhidraMcp, sessionEvidence } from "./ghidraMcpHarness.js";
|
||||
import { functionDossierSchema } from "../../../src/domain/hopperValues.js";
|
||||
import { functionDossierSchema } from "../../../src/domain/native/functionDossier.js";
|
||||
import { functionComparisonResultSchema } from "../../../src/domain/functionComparisonSchemas.js";
|
||||
import {
|
||||
ghidraFunctionDossier,
|
||||
|
||||
@@ -10,7 +10,7 @@ import { join } from "node:path";
|
||||
import { jsonValueSchema } from "../../../src/domain/jsonValue.js";
|
||||
import { nativeLoadImageSchema } from "../../../src/domain/native/nativeLoadImage.js";
|
||||
import { nativeApiInspectionResultSchema } from "../../../src/domain/native/nativeApiBoundary.js";
|
||||
import { functionDossierSchema } from "../../../src/domain/hopperValues.js";
|
||||
import { functionDossierSchema } from "../../../src/domain/native/functionDossier.js";
|
||||
import { ok } from "../../../src/domain/result.js";
|
||||
import { ghidraFunctionDossier } from "../../../src/domain/ghidraValues.fixture.js";
|
||||
import { toolContract } from "../../../src/contracts/toolContracts.js";
|
||||
|
||||
@@ -5,7 +5,7 @@ import { join, resolve } from "node:path";
|
||||
import { afterAll, beforeAll, expect } from "vitest";
|
||||
|
||||
import { createEvidence, parseEvidence } from "../../../src/domain/evidence.js";
|
||||
import { functionDossierSchema } from "../../../src/domain/hopperValues.js";
|
||||
import { functionDossierSchema } from "../../../src/domain/native/functionDossier.js";
|
||||
import {
|
||||
MANAGED_APPLICATION_GRAPH_EXAMPLE,
|
||||
MANAGED_NATIVE_VERIFICATION_EXAMPLE,
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
import { observed as ok } from "../../fixtures/analysisExecution.js";
|
||||
import { err } from "../../../src/domain/result.js";
|
||||
import { AnalysisCapabilityUnavailableError } from "../../../src/domain/analysisErrorCore.js";
|
||||
import type { SegmentSummary } from "../../../src/domain/hopperValues.js";
|
||||
import type { SegmentSummary } from "../../../src/domain/native/nativeSegments.js";
|
||||
|
||||
afterEach(closeEnhancedToolResources);
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import { describe, expect, it, onTestFinished } from "vitest";
|
||||
|
||||
import type { AnalysisOperationPort } from "../../../src/application/AnalysisProvider.js";
|
||||
import { runDirectAnalysis } from "../../../src/application/DirectAnalysis.js";
|
||||
import { functionDossierSchema } from "../../../src/domain/hopperValues.js";
|
||||
import { functionDossierSchema } from "../../../src/domain/native/functionDossier.js";
|
||||
import { ghidraFunctionDossier } from "../../../src/domain/ghidraValues.fixture.js";
|
||||
import {
|
||||
jsonValueSchema,
|
||||
|
||||
@@ -3,7 +3,7 @@ import { expect, it } from "vitest";
|
||||
|
||||
import { FUNCTION_COMPARISON_EXAMPLE } from "../../../src/contracts/functionComparisonExample.js";
|
||||
import { createEvidence, parseEvidence } from "../../../src/domain/evidence.js";
|
||||
import { functionDossierSchema } from "../../../src/domain/hopperValues.js";
|
||||
import { functionDossierSchema } from "../../../src/domain/native/functionDossier.js";
|
||||
|
||||
import { createApplicationMcpHarness } from "../../fixtures/applicationMcpHarness.js";
|
||||
import { createTestBinarySession } from "../../fixtures/binarySession.js";
|
||||
|
||||
@@ -6,7 +6,7 @@ import { describe, expect, it } from "vitest";
|
||||
import { z } from "zod";
|
||||
import { OFFICIAL_TOOL_CONTRACTS } from "../../../../src/contracts/officialToolContracts.js";
|
||||
import { nativeSymbolMetadataSchema } from "../../../../src/domain/native/nativeSymbol.js";
|
||||
import { functionDossierSchema } from "../../../../src/domain/hopperValues.js";
|
||||
import { functionDossierSchema } from "../../../../src/domain/native/functionDossier.js";
|
||||
|
||||
const execute = promisify(execFile);
|
||||
const bridgePath = new URL(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { rm } from "node:fs/promises";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { z } from "zod";
|
||||
import { functionDossierSchema } from "../../../../src/domain/hopperValues.js";
|
||||
import { functionDossierSchema } from "../../../../src/domain/native/functionDossier.js";
|
||||
import {
|
||||
jsonValueSchema,
|
||||
type JsonValue,
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
} from "../../../../src/domain/managed/managedNativeVerificationSchemas.js";
|
||||
import { managedNativeBoundaryInspectionSchema } from "../../../../src/domain/managed/managedArtifact.js";
|
||||
import { inspectMachoSchema } from "../../../../src/domain/native/nativeInspection.js";
|
||||
import { functionDossierSchema } from "../../../../src/domain/hopperValues.js";
|
||||
import { functionDossierSchema } from "../../../../src/domain/native/functionDossier.js";
|
||||
|
||||
import { verifyManagedNativeVerificationSource } from "../../../../src/domain/managed/managedNativeVerification.js";
|
||||
import { readManagedNativeVerificationSource } from "../../../../src/domain/managed/managedNativeVerificationSource.js";
|
||||
|
||||
Vendored
+1
-1
@@ -21,7 +21,7 @@ import { createJavaScriptSemanticGraph } from "../../src/domain/javascript/javas
|
||||
import type { BinaryLayout } from "../../src/domain/native/binaryLayout.js";
|
||||
import { binaryLayoutSchema } from "../../src/domain/native/binaryLayout.js";
|
||||
import { ghidraFunctionDossier } from "../../src/domain/ghidraValues.fixture.js";
|
||||
import { functionDossierSchema } from "../../src/domain/hopperValues.js";
|
||||
import { functionDossierSchema } from "../../src/domain/native/functionDossier.js";
|
||||
import type { RelationshipCoverage } from "../../src/domain/relationshipCoverage.js";
|
||||
|
||||
/** Canonical portable Evidence; no native provider is started by this fixture. */
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ import { createEvidence, type Evidence } from "../../src/domain/evidence.js";
|
||||
|
||||
import { FUNCTION_COMPARISON_EXAMPLE } from "../../src/contracts/functionComparisonExample.js";
|
||||
import type { NativeSymbolMetadata } from "../../src/domain/native/nativeSymbol.js";
|
||||
import { functionDossierSchema } from "../../src/domain/hopperValues.js";
|
||||
import { functionDossierSchema } from "../../src/domain/native/functionDossier.js";
|
||||
|
||||
/** Explicit synthetic producer facts; they do not qualify any real provider. */
|
||||
export const nativeMatchingProvider = {
|
||||
|
||||
Reference in new issue
Block a user