Files
reverse-engineering-anything/tests/boundary/cli/traceNativeValuesCompletenessCli.test.ts
T
Kaoru0822andClaude Opus 5.5 73ff6ca0c8 fix(native): separate value-trace page omission from traversal truncation (#8309)
trace_native_values set one truncated flag for two different facts: the
returned page omits retained nodes, and a budget stopped or narrowed the
traversal. A last page of a complete trace and a node- or edge-capped trace
both reported truncated: true with next_offset null and no unknown naming the
cap, so a caller could not tell whether to follow pages, raise a budget, or
treat the examined graph as exhausted.

Results now carry page_truncated for page omission and a traversal object
that is complete, or truncated with the observed reasons in schema order:
max_depth, max_functions, max_call_sites, max_nodes, max_edges,
node_payload_bytes, provider_pcode_truncated and ambiguous_call_candidates.
truncated keeps its previous meaning as the union of both.

A new budget owner admits nodes and edges and records every cause together
with a located unknown, only after a budget rejects work; an exactly filled
budget is no longer truncation. Depth and ambiguous-candidate limits are
resolved after traversal, so a destination analyzed or queued through a
shallower path is no longer reported as depth-limited. Once an edge is
rejected, remaining call sites in that procedure are not resolved.

Fixes #8283

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 08:59:58 -07:00

138 lines
4.5 KiB
TypeScript

import { writeFile } from "node:fs/promises";
import { join } from "node:path";
import { Cli } from "incur";
import pino from "pino";
import { expect, it } from "vitest";
import {
createAnalysisExecution,
type AnalysisProvider,
} from "../../../src/application/AnalysisProvider.js";
import { runDirectAnalysis } from "../../../src/application/DirectAnalysis.js";
import type { DirectAnalysisDependencies } from "../../../src/application/DirectAnalysisDependencies.js";
import {
valueTraceDiamond,
valueTraceProvider,
} from "../../../src/application/native/NativeValueTrace.fixture.js";
import { registerCoreNativeCommands } from "../../../src/cli/coreNativeCommands.js";
import { parseConfig } from "../../../src/config/parseConfig.js";
import { createAnalysisProfile } from "../../../src/domain/analysisProfile.js";
import { parseEvidence } from "../../../src/domain/evidence.js";
import { nativeValueTraceSchema } from "../../../src/domain/native/nativeValueTrace.js";
import { ok } from "../../../src/domain/result.js";
import { createTestBinarySession } from "../../fixtures/binarySession.js";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
const effects = {
mutatesArtifact: false,
launchesProcess: false,
mayShowUi: false,
mayAccessNetwork: false,
mayWriteFilesystem: false,
changesPermissions: false,
requiresRoot: false,
};
/** One-shot CLI analysis over the shared trace with a simulated provider. */
const traceCli = async () => {
const graph = valueTraceDiamond({ sessionSubject: true });
const directory = await createTestTempDirectory("rea-value-trace-cli-");
const path = join(directory, "fixture.hop");
await writeFile(path, "synthetic analysis database");
const provider: AnalysisProvider = {
identity: () => valueTraceProvider,
capabilities: () =>
(["analyze_function", "resolve_native_call_targets"] as const).map(
(operation) => ({
provider: valueTraceProvider,
operation,
available: true,
reason: null,
cachePolicy: "live",
limitations: [],
effects,
}),
),
resolveAnalysisProfile: async () =>
ok({
profile: createAnalysisProfile(valueTraceProvider, { fixture: true }),
}),
createClient: () => ({
execute: async (operation, parameters, options) =>
operation === "health"
? ok(createAnalysisExecution(null, valueTraceProvider))
: graph.port.execute(operation, parameters, options),
close: async () => ok(null),
}),
};
const createSession = () => createTestBinarySession(provider);
const dependencies: DirectAnalysisDependencies = {
readConfiguration: () => parseConfig({}),
createBinarySession: createSession,
createNativeDispatchBinarySession: createSession,
createArtifactBinarySession: createSession,
createNativeBinarySession: createSession,
createManagedBinarySession: createSession,
};
const cli = Cli.create("rea-value-trace-cli-test", { sync: false });
registerCoreNativeCommands(
cli,
pino({ enabled: false }),
(target, tool, arguments_, options) =>
runDirectAnalysis(dependencies, target, tool, arguments_, options),
);
return async (options: readonly string[]) => {
let stdout = "";
await cli.serve(
["trace-native-values", path, "0x1000", "--max-depth", "1", ...options],
{
env: {},
stdout: (text) => {
stdout += text;
},
exit: () => undefined,
},
);
return nativeValueTraceSchema.parse(
parseEvidence(JSON.parse(stdout)).normalized_result,
);
};
};
it("reports the CLI last page and its observed node budget separately", async () => {
const trace = await traceCli();
expect(
await trace(["--offset", "10", "--limit", "3", "--json"]),
).toMatchObject({
nodes: [{ id: "0x3000/parameter:0" }],
total_nodes: 11,
next_offset: null,
page_truncated: true,
traversal: { status: "complete" },
truncated: true,
unknowns: [],
});
const capped = await trace([
"--max-nodes",
"9",
"--offset",
"8",
"--limit",
"3",
"--json",
]);
expect(capped).toMatchObject({
nodes: [{ id: "0x3000/op-1" }],
total_nodes: 9,
next_offset: null,
page_truncated: true,
traversal: { status: "truncated", reasons: ["max_nodes"] },
truncated: true,
});
expect(capped.unknowns).toContainEqual({
procedure: "0x3000",
address: null,
reason: "Node budget reached",
});
});