mirror of
https://github.com/morluto/rea.git
synced 2026-10-26 17:35:11 +00:00
fix(native): page every retained function dossier collection (#1507)
inspect_analysis_view is the recovery path when an analyze_function dossier exceeds the MCP response budget, but its native facets covered only procedure, pseudocode, assembly, callers/callees, references, and value flow. Basic blocks, comments, unresolved calls, referenced strings and names, and the native API record could not be read from retained Evidence at all; basic blocks grow with the function just like assembly. These are now facets: collections page by rows, and native_api is a singleton. A dossier without an unresolved_calls record reports "not recorded" rather than the value-flow failure reason. Refs #1247 and #1065. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
f63f12d8ff
commit
911323c5dd
6 files changed
+95
-9
No files matched your search
@@ -37,8 +37,9 @@ unknown rather than false.
|
||||
For a completed `analyze_function` dossier, use `inspect_analysis_view` with
|
||||
`view: {kind: "native", facet: "value_flow_operations", offset: 0, limit: 32}`
|
||||
and the exact retained Evidence ID to read selected facts without reanalysis.
|
||||
Native facets also include procedure, pseudocode, assembly, callers/callees,
|
||||
references, and value-flow summaries. Pseudocode offsets and limits count UTF-16
|
||||
Native facets also include procedure, pseudocode, assembly, basic blocks,
|
||||
comments, callers/callees, references, unresolved calls, referenced strings and
|
||||
names, the native API record, and value-flow summaries. Pseudocode offsets and limits count UTF-16
|
||||
code units; use the returned `next_offset` for continuation. Provider limitations
|
||||
and unavailable facts remain explicit; the complete parent Evidence is retained.
|
||||
|
||||
|
||||
@@ -262,8 +262,9 @@ Ordinary responses keep their existing complete result contract.
|
||||
|
||||
`inspect_analysis_view` projects a caller-selected view of already completed
|
||||
`inspect_binary_layout`, `analyze_javascript_application`, or `analyze_function`
|
||||
Evidence. Native views select procedure, pseudocode, assembly, callers/callees,
|
||||
references, and high-pcode facets without starting a provider. Native offset
|
||||
Evidence. Native views select procedure, pseudocode, assembly, basic blocks,
|
||||
comments, callers/callees, references, unresolved calls, referenced strings and
|
||||
names, the native API record, and high-pcode facets without starting a provider. Native offset
|
||||
and limit default to 0 and 64; pseudocode uses UTF-16 code units and never
|
||||
splits surrogate pairs, while other pages count rows. Unavailable facts and
|
||||
provider limitations remain explicit. Source is
|
||||
|
||||
@@ -15,7 +15,7 @@ export const ANALYSIS_VIEW_TOOL_CONTRACTS = [
|
||||
...toolContractMetadata("inspect_analysis_view"),
|
||||
kind: "application",
|
||||
description:
|
||||
"Inspect selected views of completed binary-layout, JavaScript application or native analyze_function Evidence without repeating analysis. Use an exact same-session retained evidence_id or portable inline Evidence. Select a summary, layout mitigations/linkage facet, one section/symbol/module, or a stable page. Module pages include assets, bundled and source modules; use node_id for ambiguous or unavailable paths. Native views select procedure, pseudocode, assembly, callers/callees, references and high-pcode. Use nonnegative offset and a positive caller-selected limit (defaults 0 and 64); pseudocode uses UTF-16 code units, other native pages use rows. Returns artifact identity, parent Evidence ID, a distinct view digest, coverage, limitations and unknowns. Actual serialized size determines MCP transport admission; choose a smaller page or export the complete retained Evidence if needed.",
|
||||
"Inspect selected views of completed binary-layout, JavaScript application or native analyze_function Evidence without repeating analysis. Use an exact same-session retained evidence_id or portable inline Evidence. Select a summary, layout mitigations/linkage facet, one section/symbol/module, or a stable page. Module pages include assets, bundled and source modules; use node_id for ambiguous or unavailable paths. Native views select procedure, pseudocode, assembly, basic blocks, comments, callers/callees, references, unresolved calls, referenced strings/names, native API and high-pcode. Use nonnegative offset and a positive caller-selected limit (defaults 0 and 64); pseudocode uses UTF-16 code units, other native pages use rows. Returns artifact identity, parent Evidence ID, a distinct view digest, coverage, limitations and unknowns. Actual serialized size determines MCP transport admission; choose a smaller page or export the complete retained Evidence if needed.",
|
||||
inputSchema: inspectAnalysisViewInputSchema,
|
||||
outputSchema: evidenceResultOf(analysisViewResultSchema),
|
||||
examples: [
|
||||
|
||||
@@ -161,6 +161,64 @@ it("preserves native API limitations and unavailable residual unknowns", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("pages every retained dossier collection and the native API record", () => {
|
||||
const original = functionDossierSchema.parse(ghidraFunctionDossier());
|
||||
const page = (
|
||||
facet:
|
||||
| "basic_blocks"
|
||||
| "comments"
|
||||
| "unresolved_calls"
|
||||
| "referenced_strings"
|
||||
| "referenced_names",
|
||||
result: unknown = original,
|
||||
) => {
|
||||
const view = projectAnalysisView(nativeParent(result), {
|
||||
kind: "native",
|
||||
facet,
|
||||
offset: 0,
|
||||
limit: 64,
|
||||
});
|
||||
if (!view.ok) throw view.error;
|
||||
return view.value;
|
||||
};
|
||||
expect(page("basic_blocks")).toMatchObject({
|
||||
item: original.basic_blocks,
|
||||
coverage: { total: original.basic_blocks.length, exhausted: true },
|
||||
});
|
||||
expect(page("referenced_strings")).toMatchObject({
|
||||
item: original.referenced_strings,
|
||||
});
|
||||
expect(
|
||||
page("comments", {
|
||||
...original,
|
||||
comments: [{ address: "0x401000", kind: "comment", text: "entry" }],
|
||||
}),
|
||||
).toMatchObject({ item: [{ text: "entry" }] });
|
||||
expect(page("referenced_names")).toMatchObject({ coverage: { total: 0 } });
|
||||
const { unresolved_calls: _omitted, ...withoutUnresolved } = original;
|
||||
expect(
|
||||
page("unresolved_calls", {
|
||||
...withoutUnresolved,
|
||||
native_value_flow: {
|
||||
available: false,
|
||||
reason: "No high p-code",
|
||||
limitations: [],
|
||||
},
|
||||
}),
|
||||
).toMatchObject({ item: { available: false, reason: "not recorded" } });
|
||||
const api = projectAnalysisView(nativeParent(), {
|
||||
kind: "native",
|
||||
facet: "native_api",
|
||||
offset: 0,
|
||||
limit: 64,
|
||||
});
|
||||
if (!api.ok) throw api.error;
|
||||
expect(api.value).toMatchObject({
|
||||
item: original.native_api,
|
||||
coverage: { status: "complete-within-view" },
|
||||
});
|
||||
});
|
||||
|
||||
it("reports the malformed native dossier field", () => {
|
||||
const result = projectAnalysisView(
|
||||
nativeParent({
|
||||
|
||||
@@ -82,10 +82,16 @@ const nativeViewSchema = z.strictObject({
|
||||
"procedure",
|
||||
"pseudocode",
|
||||
"assembly",
|
||||
"basic_blocks",
|
||||
"comments",
|
||||
"callers",
|
||||
"callees",
|
||||
"incoming_references",
|
||||
"outgoing_references",
|
||||
"unresolved_calls",
|
||||
"referenced_strings",
|
||||
"referenced_names",
|
||||
"native_api",
|
||||
"value_flow_summary",
|
||||
"value_flow_operations",
|
||||
"value_flow_def_use",
|
||||
|
||||
@@ -104,17 +104,22 @@ const nativeRows = (
|
||||
dossier: FunctionDossier,
|
||||
facet: Exclude<
|
||||
Extract<AnalysisViewRequest, { readonly kind: "native" }>["facet"],
|
||||
"procedure" | "pseudocode" | "value_flow_summary"
|
||||
"procedure" | "pseudocode" | "native_api" | "value_flow_summary"
|
||||
>,
|
||||
) => {
|
||||
): readonly unknown[] | undefined => {
|
||||
const sourceFlow = dossier.native_value_flow;
|
||||
const flow = sourceFlow?.available ? sourceFlow : undefined;
|
||||
return {
|
||||
assembly: dossier.assembly,
|
||||
basic_blocks: dossier.basic_blocks,
|
||||
comments: dossier.comments,
|
||||
callers: dossier.callers,
|
||||
callees: dossier.callees,
|
||||
incoming_references: dossier.incoming_references,
|
||||
outgoing_references: dossier.outgoing_references,
|
||||
unresolved_calls: dossier.unresolved_calls,
|
||||
referenced_strings: dossier.referenced_strings,
|
||||
referenced_names: dossier.referenced_names,
|
||||
value_flow_operations: flow?.operations,
|
||||
value_flow_def_use: flow?.def_use,
|
||||
value_flow_effects: flow?.effects,
|
||||
@@ -152,10 +157,20 @@ export const projectNativeFunctionView = (
|
||||
...common,
|
||||
});
|
||||
const flow = dossier.native_value_flow;
|
||||
if (view.facet === "procedure" || view.facet === "value_flow_summary") {
|
||||
if (
|
||||
view.facet === "procedure" ||
|
||||
view.facet === "native_api" ||
|
||||
view.facet === "value_flow_summary"
|
||||
) {
|
||||
if (view.offset !== 0)
|
||||
return err(viewError("Singleton native facets require offset 0."));
|
||||
if (view.facet === "procedure") return ok(singleton(dossier.procedure));
|
||||
if (view.facet === "native_api")
|
||||
return ok(
|
||||
singleton(
|
||||
dossier.native_api ?? { available: false, reason: "not recorded" },
|
||||
),
|
||||
);
|
||||
const status =
|
||||
flow === null
|
||||
? { available: false, reason: "not recorded" }
|
||||
@@ -188,7 +203,12 @@ export const projectNativeFunctionView = (
|
||||
return ok(
|
||||
singleton({
|
||||
available: false,
|
||||
reason: flow?.available === false ? flow.reason : "not recorded",
|
||||
// Only value-flow facets inherit the value-flow failure reason; an
|
||||
// older dossier simply has no unresolved_calls record.
|
||||
reason:
|
||||
view.facet.startsWith("value_flow_") && flow?.available === false
|
||||
? flow.reason
|
||||
: "not recorded",
|
||||
}),
|
||||
);
|
||||
const items = rows.slice(view.offset, view.offset + view.limit);
|
||||
|
||||
Reference in new issue
Block a user