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