Files
reverse-engineering-anything/src/application/native/NativeCallRoutes.test.ts
T
morluto a4ba8aecd5 fix(contracts)!: require observed provider output metadata
Reject omitted segment bounds, procedure classification and body facts, unresolved-call observations, dossier facets, and Ghidra parameter flow metadata. Providers emit explicit nulls for unsupported facets. Verify those unknowns in the real Hopper workflow.

BREAKING CHANGE: Provider analysis outputs must explicitly supply metadata previously repaired with defaults; unsupported nullable facets must be reported as null.
2026-10-10 03:52:13 +08:00

131 lines
3.9 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { readNativeCallRoutes } from "./NativeCallRoutes.js";
import { createAnalysisExecution } from "../AnalysisProvider.js";
import { ghidraReferenceEdge } from "../../domain/ghidraValues.fixture.js";
import { jsonValueSchema } from "../../domain/jsonValue.js";
import { err, ok } from "../../domain/result.js";
import { AnalysisCancelledError } from "../../domain/analysisErrorCore.js";
const provider = { id: "fixture", name: "Fixture", version: "1" };
describe("typed native call routes", () => {
it("preserves computed targets, ambiguity and targetless call sites", async () => {
const reference = jsonValueSchema.parse(ghidraReferenceEdge());
if (
reference === null ||
typeof reference !== "object" ||
Array.isArray(reference)
)
throw new TypeError("Invalid reference fixture");
const result = await readNativeCallRoutes(
{
execute: async () =>
ok(
createAnalysisExecution(
{
reference_kinds_available: true,
references: ["0x2000", "0x3000"].map((address) => ({
...reference,
source_address: "0x1004",
target_address: address,
target_procedure: { address },
kind: {
available: true,
provenance: "fixture-reference",
type: "COMPUTED_CALL",
flow: true,
call: true,
jump: false,
data: false,
read: false,
write: false,
indirect: true,
computed: true,
external: false,
conditional: false,
terminal: false,
primary: true,
operand_index: 0,
},
})),
unresolved_calls: [
{ address: "0x1008", reason: "No static target" },
],
},
provider,
),
),
},
"0x1000",
);
expect(result).toMatchObject({
ok: true,
value: {
routes: [
{
address: "0x2000",
relation: "indirect_call",
resolution: "ambiguous",
},
{
address: "0x3000",
relation: "indirect_call",
resolution: "ambiguous",
},
],
unknowns: [{ address: "0x1008" }],
},
});
});
it("does not fall back on cancellation or invalid typed output", async () => {
let calls = 0;
const cancelled = await readNativeCallRoutes(
{
execute: async () => {
calls++;
return err(new AnalysisCancelledError("procedure_references"));
},
},
"0x1000",
);
expect(cancelled).toMatchObject({
ok: false,
error: { _tag: "AnalysisCancelledError" },
});
expect(calls).toBe(1);
const malformed = await readNativeCallRoutes(
{ execute: async () => ok(createAnalysisExecution({}, provider)) },
"0x1000",
);
expect(malformed).toMatchObject({
ok: false,
error: { _tag: "AnalysisOutputError" },
});
});
it("rejects omitted unresolved-call observations instead of claiming none", async () => {
let calls = 0;
const result = await readNativeCallRoutes(
{
execute: async () => {
calls++;
return ok(
createAnalysisExecution(
{
reference_kinds_available: false,
references: [],
},
provider,
),
);
},
},
"0x1000",
);
expect(result).toMatchObject({
ok: false,
error: { _tag: "AnalysisOutputError" },
});
expect(calls).toBe(1);
});
});