test(native): reproduce unlocated call inputs and named-callee binding loss

Co-authored-by: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com>
This commit is contained in:
N0zoM1z0andgensokyo-dev authored and gensokyo-dev committed 2026-11-11 18:24:58 +08:00
1 parent 930c56d670
commit 47704ab093
2 files changed
+86 -2

No files matched your search

@@ -255,3 +255,24 @@ export const failingValueTraceReads = (
};
return { port: failing, reads };
};
/** Three recovered call arguments, with only the first parameter recovered. */
export const valueTraceUnboundArguments = (options: GraphOptions = {}) =>
staticValueGraph(
{
"0x1000": {
operations: [
{
...pcodeOperation("call", "0x1010", "CALL"),
inputs: Array.from({ length: 4 }, () => pcodeScalar),
},
],
},
"0x2000": {
operations: [pcodeOperation("return", "0x2010", "RETURN")],
parameters: [{ ordinal: 0, name: "fixed", data_type: "/int" }],
},
},
{ "0x1010": [{ destination: "0x2000", status: "direct" }] },
options,
);
@@ -27,6 +27,7 @@ import {
pcodeScalar as scalar,
staticValueGraph,
valueTraceDiamond,
valueTraceUnboundArguments,
valueTraceProvider as provider,
valueTraceSubject as subject,
} from "./NativeValueTrace.fixture.js";
@@ -395,6 +396,7 @@ const callBindingAnalysis = (input: {
readonly caller: ReturnType<typeof jsonValueSchema.parse>;
readonly callee: ReturnType<typeof jsonValueSchema.parse>;
readonly targetLimitations?: readonly string[];
readonly calleeSelector?: string;
}): AnalysisOperationPort => ({
execute: async (operation, parameters) => {
if (operation === "resolve_native_call_targets")
@@ -408,7 +410,7 @@ const callBindingAnalysis = (input: {
targets: [
{
address: input.calleeAddress,
procedure: input.calleeAddress,
procedure: input.calleeSelector ?? input.calleeAddress,
status: "direct",
basis: "provider-reference",
references: [],
@@ -424,7 +426,7 @@ const callBindingAnalysis = (input: {
throw new Error(`Unexpected native trace operation: ${operation}`);
if (
parameters.procedure !== input.callerAddress &&
parameters.procedure !== input.calleeAddress
parameters.procedure !== (input.calleeSelector ?? input.calleeAddress)
)
throw new Error(`Unexpected procedure: ${String(parameters.procedure)}`);
return ok(
@@ -1433,3 +1435,64 @@ describe("native value traversal limits and policies", () => {
expect(limited.analyzed).toEqual(["0x1000"]);
});
});
describe("recovered call input bindings", () => {
it("locates recovered operands without inventing formal or variadic parameters", async () => {
const trace = await traced(valueTraceUnboundArguments().port, {
procedure: "0x1000",
});
const call = trace.nodes.find(({ id }) => id === "0x1000/call")!;
expect(
trace.edges.filter(({ kind }) => kind === "argument-binding"),
).toMatchObject([
{ source: call.id, target: "0x2000/parameter:0", input_index: 1 },
]);
expect(trace.unknowns).toContainEqual({
procedure: "0x1000",
address: "0x1010",
reason: expect.stringContaining("no recovered parameter binding"),
call_inputs: {
call_node_id: call.id,
destination: "0x2000",
evidence_id: call.evidence_id,
input_indices: [2, 3],
},
});
expect(trace.traversal).toEqual({ status: "complete" });
});
it("uses the analyzed callee identity for named call destinations", async () => {
const base = functionDossierSchema.parse(ghidraFunctionDossier());
const caller = nativeFlowDossier(base, {
address: "0x1000",
name: "caller",
operations: [node("call", "0x1010", "CALL")],
parameters: [],
});
const callee = nativeFlowDossier(base, {
address: "0x2000",
name: "callee",
operations: [node("return", "0x2010", "RETURN")],
parameters: [{ ordinal: 0, name: "value", data_type: "/int" }],
});
const trace = await traced(
callBindingAnalysis({
callerAddress: "0x1000",
calleeAddress: "0x2000",
calleeSelector: "callee",
caller,
callee,
}),
{ procedure: "0x1000" },
);
expect(
trace.edges.filter(({ kind }) => kind === "argument-binding"),
).toMatchObject([
{ source: "0x1000/call", target: "0x2000/parameter:0", input_index: 1 },
]);
expect(
trace.edges.filter(({ kind }) => kind === "return-binding"),
).toMatchObject([{ source: "0x2000/return", target: "0x1000/call" }]);
expect(trace.unknowns).toEqual([]);
});
});