mirror of
https://github.com/morluto/rea.git
synced 2026-11-03 13:26:10 +00:00
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.
131 lines
3.9 KiB
TypeScript
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);
|
|
});
|
|
});
|