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