Files
reverse-engineering-anything/tests/boundary/providers/ida/verifierFeatureTrace.test.ts
T
Mykola Dementiev 5f8af4ee5f feat(ida): resolve containing procedures and report the admitted program as the document (#2374)
* test(mcp): expect trace_feature to report references the provider truncated

A provider that reports a capped xrefs read makes the trace truncated with a residual unknown for that target. An explicit complete report and a provider that reports nothing leave the trace as before.

* fix(mcp): report references a provider truncated in trace_feature

Executions gain an optional flag for a capped xrefs read, set by the IDA adapter from the upstream cap. trace_feature marks the trace truncated with a residual unknown for that target only when the provider reports a truncation; a provider that reports nothing keeps its previous result. The trace_feature workflow profile is revised so earlier results are not replayed.

* refactor(ida): report reference truncation whenever the runner observed an xrefs cap

* test(mcp): cover session forwarding, per-target state, native reports and failures of the trace coverage

* test(mcp): format the session forwarding test

* test(ida): expect headless IDA to resolve containing procedures from lookups and segment facts

Cover positive and negative containment, segment boundaries including addresses above 2^53, failures of the lookup and of the segment survey, cancellation, input validation and the admitted program as the only document.

* test(ida): expect feature tracing and address context to work on headless IDA

Run trace_feature, including a capped reference read, and inspect_address_context with and without an explicit document through the IDA session client.

* test(mcp): expect trace_feature availability to follow the search operations it calls

* fix(mcp): derive trace_feature availability from the search operations it calls

* feat(ida): resolve containing procedures and report the admitted program as the document

The modern headless profile resolves an address through the function lookup and, when no function contains it, tells a mapped address from an unmapped one using the database-scoped minimal survey. A survey that is missing, truncated or inconsistent fails the read. list_documents reports the single admitted program. The attached profiles do not declare either operation.

* docs(ida): describe containing procedures, the admitted document and truncated feature traces

* test(ida): verify containing procedures, the admitted document and feature tracing on real IDA

* docs(testing): list the headless IDA workflows the real lane verifies

* test(ida): make each survey failure case isolate a single defect

* test(ida): pin large addresses, segment order, document identity, attached refusals and each trace_feature prerequisite

* test(mcp): expect a repeated reference source to be resolved once in trace_feature

* fix(mcp): resolve the containing procedure of a repeated reference source once

* test(ida): trace the fixture procedure and derive probe addresses from its facts in the real lane

* test(process): format processCaptureLifecycle.test.ts after the merge

* test(ida): declare the alignment each synthetic universal slice offset satisfies

* refactor(ida): move the real lane's feature trace assertions into a helper

* test(ida): expect the real lane's feature trace check to accept references to every matched address

* fix(ida): check each trace reference against its own matched address in the real lane

* test(ida): pin that the new operations reject a document other than the admitted program

* refactor(ida): move the real lane's mapped non-function probe into a helper

* test(ida): expect the real lane's mapped data probe to skip strings inside functions

* fix(ida): try the target's strings in order for the mapped non-function probe

* test(ida): declare the real lane helper types for the verifier tests

* test(ida): expect the real lane to reject a listed string that resolves as unmapped

* fix(ida): fail the real lane when a listed string resolves as unmapped
2026-10-13 20:56:20 +08:00

213 lines
5.6 KiB
TypeScript

import { describe, expect, it } from "vitest";
import {
assertFeatureTrace,
findMappedNonFunction,
} from "../../../../scripts/lib/ida-feature-trace.mjs";
const procedure = { address: "0x1000", name: "main" };
const containing = (found: boolean) =>
found
? {
query_address: "0x1004",
found: true,
procedure: { address: "0x1000", name: "main" },
}
: {
query_address: "0x3010",
found: false,
procedure: null,
reason: "not_in_procedure",
};
const trace = (
matches: readonly { type: string; address: string }[],
references: readonly {
target_address: string;
containing_procedure: unknown;
}[],
) => ({
query: "main",
search_mode: "literal",
matches,
references,
truncated: false,
residual_unknowns: [],
});
const SELECTED = { type: "procedure", address: "0x1000" };
const SUBSTRING = { type: "procedure", address: "0x2000" };
const STRING = { type: "string", address: "0x3000" };
describe("the real IDA lane's feature trace assertions", () => {
it("accepts references to every matched address, whoever contains the source", () => {
expect(() =>
assertFeatureTrace(
trace(
[SELECTED, SUBSTRING, STRING],
[
{
target_address: "0x1000",
containing_procedure: containing(true),
},
// A substring match such as main_helper, referenced from code.
{
target_address: "0x2000",
containing_procedure: containing(true),
},
// A string containing the query, referenced from data outside functions.
{
target_address: "0x3000",
containing_procedure: containing(false),
},
],
),
procedure,
),
).not.toThrow();
});
it("accepts a reference to the selected procedure from data outside functions", () => {
expect(() =>
assertFeatureTrace(
trace(
[SELECTED],
[
{
target_address: "0x1000",
containing_procedure: containing(false),
},
],
),
procedure,
),
).not.toThrow();
});
it("rejects a reference whose target is not one of the matches", () => {
expect(() =>
assertFeatureTrace(
trace(
[SELECTED],
[
{
target_address: "0x9999",
containing_procedure: containing(true),
},
],
),
procedure,
),
).toThrow();
});
it("rejects a trace without the selected procedure's match", () => {
expect(() => assertFeatureTrace(trace([STRING], []), procedure)).toThrow();
});
it("rejects a containment answer that is neither found nor a known reason", () => {
expect(() =>
assertFeatureTrace(
trace(
[SELECTED],
[
{
target_address: "0x1000",
containing_procedure: {
query_address: "0x1004",
found: false,
procedure: null,
reason: "unknown",
},
},
],
),
procedure,
),
).toThrow();
});
it("rejects a trace that does not say whether it was truncated", () => {
expect(() =>
assertFeatureTrace(
{ ...trace([SELECTED], []), truncated: undefined },
procedure,
),
).toThrow();
});
});
describe("the real IDA lane's mapped non-function probe", () => {
const inFunction = {
query_address: "0x1100",
found: true,
procedure: { address: "0x1000", name: "main" },
};
const data = {
query_address: "0x3000",
found: false,
procedure: null,
reason: "not_in_procedure",
};
const resolver =
(answers: Readonly<Record<string, unknown>>, asked: string[] = []) =>
async (address: string) => {
asked.push(address);
return answers[address];
};
it("skips a string inside a function chunk and uses the next one", async () => {
const asked: string[] = [];
await expect(
findMappedNonFunction(
[{ address: "0x1100" }, { address: "0x3000" }],
resolver({ "0x1100": inFunction, "0x3000": data }, asked),
),
).resolves.toBe("0x3000");
expect(asked, "strings are tried in order until one qualifies").toEqual([
"0x1100",
"0x3000",
]);
});
it("stops at the first string that qualifies", async () => {
const asked: string[] = [];
await expect(
findMappedNonFunction(
[{ address: "0x3000" }, { address: "0x3100" }],
resolver({ "0x3000": data, "0x3100": data }, asked),
),
).resolves.toBe("0x3000");
expect(asked).toEqual(["0x3000"]);
});
it("reports no qualifying string when every string is inside a function", async () => {
await expect(
findMappedNonFunction(
[{ address: "0x1100" }, { address: "0x1200" }],
resolver({ "0x1100": inFunction, "0x1200": inFunction }),
),
).resolves.toBeUndefined();
});
it("reports no qualifying string when the target has no strings", async () => {
await expect(
findMappedNonFunction([], resolver({})),
).resolves.toBeUndefined();
});
it("rejects a listed string that resolves as unmapped, since IDA cannot list an unmapped address", async () => {
await expect(
findMappedNonFunction(
[{ address: "0x4000" }, { address: "0x3000" }],
resolver({
"0x4000": { ...data, reason: "outside_segments" },
"0x3000": data,
}),
),
).rejects.toThrow();
});
});