feat(kuna): preserve stored pointer references (#8851)

* feat(kuna): verify release 1.990

* feat(kuna): preserve stored pointer references

* fix(kuna): group image-wide reference coverage gaps

* fix(evidence): retain producer-required xref observations

* fix(mcp): clarify xref address page scope

* fix(kuna): preserve reference target aliases

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
This commit is contained in:
morlutoandCursor Agent authored and GitHub committed 2026-11-07 23:33:41 +08:00
1 parent 3e8a5f76d0
commit f7dc5489b3
17 files changed
+692 -29

No files matched your search

+2
View File
@@ -318,6 +318,8 @@ Retained `xrefs` Evidence also supports `native-observation` address pages and
default and does not accept `detail`; when its MCP response exceeds the budget,
follow the returned continuation to page the retained result. These addresses
remain untyped; paging does not add reference kinds or establish completeness.
The page tells callers that any additional provider detail recorded in the parent
requires retrieving or exporting the complete Evidence.
These views reference complete source context through `source_context.digest`
and caveat counts. Read it using the same parent with
+28
View File
@@ -82,6 +82,22 @@ export interface AnalysisExecution extends AnalysisObservation {
* output.
*/
readonly referencesTruncated?: boolean;
/** The producer needs this observation retained as source Evidence for composed workflows. */
readonly requiresSourceEvidence?: true;
/** Known reference-discovery gaps; absent means coverage was not reported. */
readonly referenceCoverageGaps?: readonly {
readonly code: string;
readonly scope: "image" | "observation";
readonly description: string;
}[];
/** Typed source facts for this live xref observation; absent means unreported. */
readonly referenceClassifications?: readonly {
readonly sourceAddress: string;
readonly targetAddress: string;
readonly kind: "stored_pointer";
readonly provenance: string;
readonly width: number;
}[];
/**
* Sibling operations known to be unavailable at the provider level when this
* operation answered from a standalone or fallback path.
@@ -97,6 +113,9 @@ interface CreateAnalysisExecutionOptions {
readonly limitations?: readonly string[];
readonly relationshipCoverage?: RelationshipCoverage;
readonly referencesTruncated?: boolean;
readonly requiresSourceEvidence?: true;
readonly referenceClassifications?: AnalysisExecution["referenceClassifications"];
readonly referenceCoverageGaps?: AnalysisExecution["referenceCoverageGaps"];
readonly siblingUnavailability?: AnalysisCapabilityUnavailableError;
readonly locations?: readonly EvidenceLocation[];
readonly sourceEvidence?: readonly Evidence[];
@@ -156,6 +175,15 @@ export function createAnalysisExecution(
...(options.referencesTruncated === undefined
? {}
: { referencesTruncated: options.referencesTruncated }),
...(options.requiresSourceEvidence === undefined
? {}
: { requiresSourceEvidence: options.requiresSourceEvidence }),
...(options.referenceCoverageGaps === undefined
? {}
: { referenceCoverageGaps: options.referenceCoverageGaps }),
...(options.referenceClassifications === undefined
? {}
: { referenceClassifications: options.referenceClassifications }),
...(options.siblingUnavailability === undefined
? {}
: { siblingUnavailability: options.siblingUnavailability }),
+4 -2
View File
@@ -25,8 +25,10 @@ import {
retainAnalysisResultSources,
type AnalysisResultWithSources,
} from "./investigation/recordEvidenceSources.js";
import { resolveInvestigationEvidenceClosure } from "./investigation/InvestigationEvidence.js";
import { createInvestigationEvidenceClosureChecker } from "./investigation/InvestigationEvidence.js";
import {
resolveInvestigationEvidenceClosure,
createInvestigationEvidenceClosureChecker,
} from "./investigation/InvestigationEvidence.js";
import type { Evidence } from "../domain/evidence.js";
import type { NativeToolName } from "../contracts/native/nativeToolContracts.js";
import type { EnhancedToolName } from "../contracts/enhancedInputs.js";
+29
View File
@@ -137,6 +137,7 @@ const traceReferences = async (
const tracedTargets = new Set<string>();
// A source address is resolved once however many matches reference it.
const containing = new Map<string, TraceContainingProcedure>();
const imageCoverageGaps = new Set<string>();
for (const match of request.matches) {
if (tracedTargets.has(match.address)) continue;
tracedTargets.add(match.address);
@@ -154,6 +155,17 @@ const traceReferences = async (
`References to ${match.address} were truncated by the provider; additional references may exist.`,
);
}
for (const gap of observed.value.referenceCoverageGaps ?? []) {
if (gap.scope === "observation") {
residual.push(`References to ${match.address}: ${gap.description}`);
} else {
const key = JSON.stringify([observed.value.provider.id, gap.code]);
if (!imageCoverageGaps.has(key)) {
imageCoverageGaps.add(key);
residual.push(gap.description);
}
}
}
const sources = observed.value.result;
if (!Array.isArray(sources))
return err(
@@ -162,6 +174,20 @@ const traceReferences = async (
"provider returned a non-array result",
),
);
const classifications = new Map<
string,
{ kind: "stored_pointer"; provenance: string; width: number }[]
>();
for (const fact of observed.value.referenceClassifications ?? []) {
if (fact.targetAddress !== match.address) continue;
const values = classifications.get(fact.sourceAddress) ?? [];
values.push({
kind: fact.kind,
provenance: fact.provenance,
width: fact.width,
});
classifications.set(fact.sourceAddress, values);
}
for (const source of sources) {
if (typeof source !== "string")
return err(
@@ -211,6 +237,9 @@ const traceReferences = async (
target_address: match.address,
source_address: source,
containing_procedure: containingProcedure,
...(classifications.has(source)
? { source_classifications: classifications.get(source)! }
: {}),
});
}
}
+1 -1
View File
@@ -21,7 +21,7 @@ export const workflowAnalysisProfile = (
: operation === "batch_decompile"
? { workflow_revision: 4 }
: operation === "trace_feature"
? { workflow_revision: 4 }
? { workflow_revision: 8 }
: operation === "get_call_graph"
? { workflow_revision: 3 }
: operation === "trace_native_values"
@@ -864,6 +864,73 @@ describe("document-scoped snapshot admission", () => {
});
});
it("retains producer-required xrefs without replaying a filesystem-writing query", () => {
const descriptor: CapabilityDescriptor = {
operation: "xrefs",
provider: ANALYSIS_SNAPSHOT_PROVIDER,
available: true,
reason: null,
effects: {
mutatesArtifact: false,
launchesProcess: true,
mayShowUi: false,
mayAccessNetwork: false,
mayWriteFilesystem: false,
changesPermissions: false,
requiresRoot: false,
},
limitations: [],
};
const xrefs = descriptor;
expect(
isSourceEvidenceRetainable("xrefs", xrefs, { address: "0x1000" }),
).toBe(false);
expect(isSnapshotCacheable("xrefs", xrefs, { address: "0x1000" })).toBe(
false,
);
expect(isSourceEvidenceRetainable("xrefs", xrefs, {})).toBe(false);
const writingXrefs = {
...xrefs,
effects: { ...xrefs.effects, mayWriteFilesystem: true },
};
expect(
isSourceEvidenceRetainable("xrefs", writingXrefs, { address: "0x1000" }),
).toBe(false);
expect(
isSourceEvidenceRetainable(
"xrefs",
writingXrefs,
{ address: "0x1000" },
{ requiresSourceEvidence: true },
),
).toBe(true);
expect(
isSourceEvidenceRetainable(
"xrefs",
writingXrefs,
{},
{ requiresSourceEvidence: true },
),
).toBe(false);
expect(
isSourceEvidenceRetainable(
"xrefs",
{
...writingXrefs,
effects: { ...writingXrefs.effects, mutatesArtifact: true },
},
{ address: "0x1000" },
{ requiresSourceEvidence: true },
),
).toBe(false);
expect(
isSnapshotCacheable("xrefs", writingXrefs, {
address: "0x1000",
document: "fixture",
}),
).toBe(false);
});
describe("analysis snapshot cache partitioning", () => {
it.each<{
operation: Exclude<AnalysisOperation, "health">;
@@ -1490,13 +1557,43 @@ describe("executions that report truncated references", () => {
});
});
it("does not replay references when the snapshot would lose their classifications", () => {
const cache = new AnalysisSnapshotCache();
const parameters = { address: "0x1000", document: "fixture" };
cache.record({
target: ANALYSIS_SNAPSHOT_TARGET,
profile: ANALYSIS_SNAPSHOT_PROFILE,
operation: "xrefs",
parameters,
execution: createAnalysisExecution(["0x2000"], ANALYSIS_SNAPSHOT_PROVIDER, {
referenceClassifications: [
{
sourceAddress: "0x2000",
targetAddress: "0x1000",
kind: "stored_pointer",
provenance: "relocation",
width: 8,
},
],
}),
});
expect(
cache.lookup(
ANALYSIS_SNAPSHOT_TARGET,
ANALYSIS_SNAPSHOT_PROFILE,
"xrefs",
parameters,
),
).toBeUndefined();
});
it("revises the trace_feature workflow profile so earlier results are not replayed", () => {
const profile = ANALYSIS_SNAPSHOT_PROFILE;
expect(
workflowAnalysisProfile(profile, "trace_feature").parameters
.workflow_revision,
).toBe(4);
).toBe(8);
expect(workflowAnalysisProfile(profile, "trace_feature")).not.toEqual(
workflowAnalysisProfile(profile),
@@ -117,25 +117,30 @@ const DOCUMENTLESS_SOURCE_EVIDENCE_OPERATIONS: ReadonlySet<AnalysisOperation> =
/**
* Whether a source observation is safe to retain as Evidence.
* A missing document selector blocks snapshot replay. Only caller and callee
* walks stay in the ledger without one.
* A missing document selector blocks snapshot replay. A producer can require
* its observation as Evidence independently of whether it can be replayed.
*/
export const isSourceEvidenceRetainable = (
operation: AnalysisOperation,
descriptor: CapabilityDescriptor | undefined,
parameters: Readonly<Record<string, JsonValue>>,
context?: ExecutionOptions["snapshotContext"],
options: {
readonly snapshotContext?: ExecutionOptions["snapshotContext"];
readonly requiresSourceEvidence?: boolean;
} = {},
): descriptor is CapabilityDescriptor =>
!STATEFUL_OPERATIONS.has(operation) &&
!RESOURCE_SCOPED_OPERATIONS.has(operation) &&
(!DOCUMENT_SCOPED_OPERATIONS.has(operation) ||
typeof parameters.document === "string" ||
DOCUMENTLESS_SOURCE_EVIDENCE_OPERATIONS.has(operation) ||
isOneShotPseudocode(operation, descriptor, context)) &&
options.requiresSourceEvidence === true ||
isOneShotPseudocode(operation, descriptor, options.snapshotContext)) &&
(!CURSOR_DEFAULT_OPERATIONS.has(operation) ||
typeof parameters.address === "string") &&
descriptor?.effects.mutatesArtifact === false &&
(descriptor.cachePolicy === "snapshot" ||
(options.requiresSourceEvidence === true ||
descriptor.cachePolicy === "snapshot" ||
descriptor.effects.mayWriteFilesystem === false) &&
descriptor.effects.changesPermissions === false;
@@ -146,7 +151,9 @@ export const isSnapshotCacheable = (
parameters: Readonly<Record<string, JsonValue>>,
context?: ExecutionOptions["snapshotContext"],
): descriptor is CapabilityDescriptor =>
isSourceEvidenceRetainable(operation, descriptor, parameters, context) &&
isSourceEvidenceRetainable(operation, descriptor, parameters, {
snapshotContext: context,
}) &&
(replayDocumentSelected(operation, parameters) ||
isOneShotPseudocode(operation, descriptor, context)) &&
descriptor.cachePolicy !== "live";
@@ -589,9 +596,15 @@ export class AnalysisSnapshotCache {
): void {
const { target, profile, operation, parameters, execution } = input;
this.select(target, profile);
// The snapshot format does not carry reference truncation; a replay would
// present a capped read as complete, so it is not recorded.
if (execution.referencesTruncated === true) return;
// The snapshot format does not carry reference truncation or stored-pointer
// classifications. Replaying either would present a capped or untyped read
// as the live observation.
if (
execution.referencesTruncated === true ||
execution.referenceClassifications !== undefined ||
execution.referenceCoverageGaps !== undefined
)
return;
let sourceBinding: SnapshotReplayBinding | undefined;
if ((execution.sourceEvidence?.length ?? 0) > 0) {
if (execution.evidenceId === undefined || lookup === undefined) return;
+9 -7
View File
@@ -1539,12 +1539,10 @@ export class BinarySession
}
if (
execution.ok &&
isSourceEvidenceRetainable(
name,
capability,
arguments_,
options?.snapshotContext,
) &&
isSourceEvidenceRetainable(name, capability, arguments_, {
snapshotContext: options?.snapshotContext,
requiresSourceEvidence: execution.value.requiresSourceEvidence === true,
}) &&
profile !== undefined
) {
const evidence = createAnalysisExecutionEvidence(
@@ -1561,7 +1559,11 @@ export class BinarySession
"observed",
);
const recorded = this.recordEvidence(evidence);
if (!recorded.ok) return recorded;
if (!recorded.ok) {
// The provider finished. A full ledger must not discard that observation.
recorded.error.retainPartialObservation(evidence);
return recorded;
}
const retained = { ...execution.value, evidenceId: evidence.evidence_id };
if (cacheable && execution.value.cachePolicy !== "live")
this.recordSnapshot({
@@ -1,5 +1,10 @@
import type { ElectronBoundarySummary } from "../../domain/javascript/javascriptApplicationAnalysis.js";
import { electronSemanticsState } from "./ElectronBoundaryAnalysis.js";
import {
electronSemanticsState,
createElectronIpcContext,
summarizeElectronBoundaries,
type ElectronIpcContext,
} from "./ElectronBoundaryAnalysis.js";
import { ELECTRON_IDENTITY_LIMITATION } from "../../domain/javascript/javascriptElectronMemberWrites.js";
import type { ArtifactInventorySnapshot } from "../../domain/artifactInventorySnapshot.js";
import {
@@ -47,11 +52,6 @@ import {
} from "./JavaScriptArtifactGraphStructure.js";
import { addJavaScriptBundlerNodes } from "./JavaScriptArtifactGraphBundlers.js";
import { addElectronBoundaries } from "./ElectronBoundaryGraph.js";
import {
createElectronIpcContext,
summarizeElectronBoundaries,
type ElectronIpcContext,
} from "./ElectronBoundaryAnalysis.js";
/**
* Build a validated application graph with cooperative immutable ownership.
@@ -30,6 +30,10 @@ import {
} from "./nativeObservationSchemas.js";
import { projectNativeGraphRows } from "./nativeGraphView.js";
/** What an address page leaves in the complete parent, when recorded. */
export const XREFS_ADDRESS_PAGE_LIMITATION =
"This page contains source addresses only, not reference kinds or provenance. Retrieve or export the complete parent Evidence for any additional provider details it recorded.";
const readAddresses = createAnalysisViewResultReader(nativeAddressListSchema);
const readAddress = createAnalysisViewResultReader(
nativeObservationSourceSchemas.procedure_address,
@@ -347,6 +351,9 @@ export const projectNativeObservationView = (
const { total, ...payload } = page;
const observation = {
...common,
...(source.value.operation === "xrefs"
? { unknowns: [...common.unknowns, XREFS_ADDRESS_PAGE_LIMITATION] }
: {}),
...payload,
summary: { observations: total },
coverage: pageViewCoverage(selection.offset, page.items.rows.length, total),
+12
View File
@@ -7,6 +7,18 @@ export const literalFeatureReferenceSchema = z.object({
target_address: z.string(),
source_address: z.string(),
containing_procedure: traceContainingProcedureSchema,
source_classifications: z
.array(
z.strictObject({
kind: z.literal("stored_pointer"),
provenance: z.string(),
width: z.int().positive(),
}),
)
.exactOptional()
.describe(
"Provider-reported source classifications; omission means none were supplied. The list need not exhaust all kinds.",
),
});
/** Complete result of a native literal feature trace. */
+6 -1
View File
@@ -273,7 +273,12 @@ export class KunaProvider implements AnalysisProviderCandidate {
bodies: false,
edge_kind: "call",
},
xrefs: { command: "xrefs", direction: "to", exact_address: true },
xrefs: {
command: "xrefs",
direction: "to",
exact_address: true,
stored_pointers: true,
},
strings: {
command: "strings",
encoding: "all",
+77 -1
View File
@@ -224,6 +224,32 @@ const xrefRow = z.object({
to_function: xrefFunction.nullable(),
instruction: z.string(),
});
const xrefPointer = z.object({
kind: z.literal("pointer"),
from_address: address,
from_address_hex: address,
from_name: z.string().nullable(),
to_address: address,
to_address_hex: address,
to_name: z.string().nullable(),
width: z.int().positive(),
source: z.string().min(1),
});
const xrefWalkCoverage = z.object({
decoded: z.boolean(),
section: z.string().nullable(),
code_section: z.boolean().nullable(),
executable: z.boolean().nullable(),
unwalked: z.array(
z.object({
name: z.string().nullable(),
address,
address_hex: address,
section: z.string(),
}),
),
notes: z.array(z.string()),
});
const xrefsDocument = z.object({
binary: z.string(),
direction: z.literal("to"),
@@ -235,6 +261,10 @@ const xrefsDocument = z.object({
aliases: z.array(xrefFunction),
}),
xrefs: z.array(z.unknown()),
coverage: xrefWalkCoverage.optional(),
data_refs_coverage: z.string().optional(),
data_refs_count: count.optional(),
data_refs: z.array(xrefPointer).optional(),
});
export interface KunaIncomingReferences {
@@ -243,6 +273,15 @@ export interface KunaIncomingReferences {
readonly bytes: number;
readonly rows: number;
readonly excludedAliasRows: number;
readonly targetAliases: readonly {
readonly name: string;
readonly address: string;
}[];
readonly walkCoverage: z.infer<typeof xrefWalkCoverage> | null;
readonly pointerCoverage: string | null;
readonly pointers: Array<
Omit<z.infer<typeof xrefPointer>, "from_address_hex" | "to_address_hex">
> | null;
}
/** Code from incoming-reference admission, plus the address field when one failed. */
@@ -275,8 +314,34 @@ export const parseKunaIncomingReferences = (
throw new KunaDocumentError("xref_alias_hex");
aliases.add(alias.address);
}
if (
(document.data_refs === undefined) !==
(document.data_refs_count === undefined)
)
throw new KunaDocumentError("xref_count");
if (
document.data_refs !== undefined &&
document.data_refs.length !== document.data_refs_count
)
throw new KunaDocumentError("xref_count");
for (const item of document.coverage?.unwalked ?? [])
if (item.address !== item.address_hex)
throw new KunaDocumentError("xref_endpoints");
for (const pointer of document.data_refs ?? []) {
if (
pointer.from_address !== pointer.from_address_hex ||
pointer.to_address !== pointer.to_address_hex
)
throw new KunaDocumentError("xref_endpoints");
if (!aliases.has(pointer.to_address))
throw new KunaDocumentError("xref_target");
}
const addresses = new Set<string>();
let excludedAliasRows = 0;
for (const pointer of document.data_refs ?? []) {
if (pointer.to_address === target) addresses.add(pointer.from_address);
else excludedAliasRows += 1;
}
for (const raw of document.xrefs) {
const row = xrefRow.parse(raw);
if (
@@ -296,8 +361,19 @@ export const parseKunaIncomingReferences = (
}
return ok({
addresses: [...addresses].sort(compareKunaAddresses),
rows: document.xrefs.length,
rows: document.xrefs.length + (document.data_refs?.length ?? 0),
excludedAliasRows,
targetAliases: document.target.aliases.map(({ name, address }) => ({
name,
address,
})),
walkCoverage: document.coverage ?? null,
pointerCoverage: document.data_refs_coverage ?? null,
pointers:
document.data_refs?.map(
({ from_address_hex: _from, to_address_hex: _to, ...pointer }) =>
pointer,
) ?? null,
bytes: Buffer.byteLength(text),
sha256: createHash("sha256").update(text).digest("hex"),
});
+67 -1
View File
@@ -178,8 +178,11 @@ interface Answer {
readonly locations: readonly EvidenceLocation[];
readonly limitations: readonly string[];
readonly referenceSource?: JsonValue;
readonly requiresSourceEvidence?: true;
readonly stringSource?: JsonValue;
readonly relationshipCoverage?: RelationshipCoverage;
readonly referenceClassifications?: AnalysisExecution["referenceClassifications"];
readonly referenceCoverageGaps?: AnalysisExecution["referenceCoverageGaps"];
readonly siblingUnavailability?: AnalysisCapabilityUnavailableError;
}
@@ -725,6 +728,15 @@ export class KunaSessionClient implements AnalysisClient {
...(answer.relationshipCoverage === undefined
? {}
: { relationshipCoverage: answer.relationshipCoverage }),
...(answer.requiresSourceEvidence === undefined
? {}
: { requiresSourceEvidence: answer.requiresSourceEvidence }),
...(answer.referenceCoverageGaps === undefined
? {}
: { referenceCoverageGaps: answer.referenceCoverageGaps }),
...(answer.referenceClassifications === undefined
? {}
: { referenceClassifications: answer.referenceClassifications }),
...(answer.siblingUnavailability === undefined
? {}
: { siblingUnavailability: answer.siblingUnavailability }),
@@ -1005,11 +1017,59 @@ export class KunaSessionClient implements AnalysisClient {
options.signal,
);
if (!references.ok) return references;
const coverageGaps = [
...(references.value.walkCoverage !== null &&
references.value.walkCoverage.unwalked.length > 0
? [
{
code: "unwalked-code",
scope: "observation" as const,
description:
"Kuna reports unwalked code candidates whose outgoing references are not included. Their presence does not establish a reference to the selected target; the source observation identifies them.",
},
]
: []),
...(references.value.pointerCoverage === "none"
? [
{
code: "pointer-relocations-unavailable",
scope: "image" as const,
description:
"Kuna reports no pointer-relocation coverage for this image; stored pointers may still exist.",
},
]
: []),
];
return ok({
result: [...references.value.addresses],
source: null,
locations,
limitations,
limitations: [
...limitations,
...(references.value.pointerCoverage === null
? []
: [
"Kuna pointer references come from relocation metadata. The reported metadata source does not establish successful or complete enumeration.",
]),
...coverageGaps.map(({ description }) => description),
],
...(coverageGaps.length === 0
? {}
: { referenceCoverageGaps: coverageGaps }),
...(references.value.pointers === null
? {}
: {
referenceClassifications: references.value.pointers
.filter((pointer) => pointer.to_address === address)
.map((pointer) => ({
sourceAddress: pointer.from_address,
targetAddress: pointer.to_address,
kind: "stored_pointer" as const,
provenance: pointer.source,
width: pointer.width,
})),
}),
requiresSourceEvidence: true,
referenceSource: {
kind: "kuna-xrefs",
direction: "to",
@@ -1018,6 +1078,12 @@ export class KunaSessionClient implements AnalysisClient {
bytes: references.value.bytes,
examined_rows: references.value.rows,
excluded_alias_rows: references.value.excludedAliasRows,
target_aliases: references.value.targetAliases.map(
({ name, address }) => ({ name, address }),
),
walk_coverage: references.value.walkCoverage,
pointer_coverage: references.value.pointerCoverage,
pointers: references.value.pointers,
},
});
}
@@ -140,6 +140,106 @@ const trace = async (analysis: AnalysisOperationPort) => {
return result.structuredContent as { normalized_result: unknown };
};
it("reports known reference-discovery gaps without claiming truncation or a missing edge", async () => {
const baseline = provider();
const gap =
"An unwalked candidate may have outgoing references; its presence does not establish a reference to the selected target.";
const output = await trace({
execute: (operation, input, options) =>
operation === "xrefs" && input.address === "0x10"
? Promise.resolve(
ok(
createAnalysisExecution([], PROVIDER, {
referenceCoverageGaps: [
{ code: "unwalked", scope: "observation", description: gap },
],
}),
),
)
: baseline.execute(operation, input, options),
});
expect(output.normalized_result).toMatchObject({
truncated: false,
residual_unknowns: [`References to 0x10: ${gap}`],
references: [{ target_address: "0x11", source_address: "0x21" }],
});
});
it("deduplicates an image-wide gap without merging target-specific gaps", async () => {
const baseline = provider();
const output = await trace({
execute: async (operation, input, options) => {
const result = await baseline.execute(operation, input, options);
if (operation !== "xrefs" || !result.ok) return result;
return ok({
...result.value,
referenceCoverageGaps: [
{
code: "missing-relocations",
scope: "image",
description: "Image relocation coverage unavailable.",
},
{
code: "unwalked",
scope: "observation",
description: "This observation has unwalked sources.",
},
],
});
},
});
expect(output.normalized_result).toMatchObject({
truncated: false,
residual_unknowns: [
"Image relocation coverage unavailable.",
"References to 0x10: This observation has unwalked sources.",
"References to 0x11: This observation has unwalked sources.",
],
});
});
it("carries reported pointer classifications without inferring other reference kinds", async () => {
const baseline = provider();
const output = await trace({
execute: (operation, input, options) =>
operation === "xrefs" && input.address === "0x10"
? Promise.resolve(
ok(
createAnalysisExecution(["0x20"], PROVIDER, {
referenceClassifications: [
{
sourceAddress: "0x20",
targetAddress: "0x10",
kind: "stored_pointer",
provenance: "chained-fixups",
width: 8,
},
{
sourceAddress: "0x20",
targetAddress: "0x99",
kind: "stored_pointer",
provenance: "other",
width: 4,
},
],
}),
),
)
: baseline.execute(operation, input, options),
});
expect(output.normalized_result).toMatchObject({
references: [
{
source_address: "0x20",
source_classifications: [
{ kind: "stored_pointer", provenance: "chained-fixups", width: 8 },
],
},
{ source_address: "0x21" },
],
});
});
it("preserves references and explicitly unknown owners without claiming truncation", async () => {
const baseline = provider();
const output = await trace({
@@ -12,6 +12,7 @@ import {
analysisViewResultSchema,
inspectAnalysisViewInputSchema,
} from "../../../src/domain/analysisView/analysisView.js";
import { XREFS_ADDRESS_PAGE_LIMITATION } from "../../../src/domain/analysisView/nativeObservationView.js";
import { NATIVE_PRIMITIVE_OBSERVATION_OPERATIONS } from "../../../src/domain/native/nativeObservation.js";
import { connectGhidraMcp } from "./ghidraMcpHarness.js";
import { createMcpSchemaValidation } from "../../fixtures/mcpSchemaValidation.js";
@@ -138,6 +139,9 @@ it("delivers native primitive and graph summaries with exact retained parents an
},
coverage: { total: 1, exhausted: true },
});
expect(
analysisViewResultSchema.parse(page.normalized_result).unknowns,
).toContain(XREFS_ADDRESS_PAGE_LIMITATION);
const caveats = await call("inspect_analysis_view", {
source,
view: { kind: "native-context", facet: "limitations" },
@@ -286,6 +286,226 @@ it("keeps exact endpoints, deduplicates sites and counts excluded forwarding ali
});
});
it("preserves pointer and walk coverage separately and rejects inconsistent pointer identity", () => {
const pointer = {
kind: "pointer",
from_address: "@16384",
from_address_hex: "0x4000",
from_name: "slot",
to_address: "@8192",
to_address_hex: "0x2000",
to_name: "callee",
width: 8,
source: "chained-fixups",
};
const source = {
...xrefs(),
coverage: {
decoded: true,
section: "__TEXT,__text",
code_section: true,
executable: true,
unwalked: [
{
name: "middle",
address: "@20480",
address_hex: "0x5000",
section: "__TEXT,__const",
},
],
notes: ["middle was not walked"],
},
data_refs_coverage: "chained-fixups",
data_refs_count: 1,
data_refs: [pointer],
};
expect(
parseKunaIncomingReferences(wire(source), input, "0x2000"),
).toMatchObject({
ok: true,
value: {
addresses: ["0x1004", "0x4000"],
pointerCoverage: "chained-fixups",
pointers: [
{
from_address: "0x4000",
to_address: "0x2000",
source: "chained-fixups",
},
],
walkCoverage: { unwalked: [{ address: "0x5000" }] },
},
});
pointer.to_address_hex = "0x2001";
expect(
parseKunaIncomingReferences(wire(source), input, "0x2000"),
).toMatchObject({
ok: false,
error: { code: "xref_endpoints" },
});
});
it("counts pointer rows excluded because they target a forwarding alias", () => {
const pointer = (from: number, to: number) => ({
kind: "pointer",
from_address: `@${from}`,
from_address_hex: `0x${from.toString(16)}`,
from_name: null,
to_address: `@${to}`,
to_address_hex: `0x${to.toString(16)}`,
to_name: null,
width: 8,
source: "dynamic-relocations",
});
const source = {
...xrefs(),
data_refs_coverage: "dynamic-relocations",
data_refs_count: 2,
data_refs: [pointer(16384, 8192), pointer(16392, 12288)],
};
expect(
parseKunaIncomingReferences(wire(source), input, "0x2000"),
).toMatchObject({
ok: true,
value: {
addresses: ["0x1004", "0x4000"],
rows: 5,
excludedAliasRows: 2,
targetAliases: [{ name: "slot", address: "0x3000" }],
pointers: [
{ from_address: "0x4000", to_address: "0x2000" },
{ from_address: "0x4008", to_address: "0x3000" },
],
},
});
});
it("preserves full-width pointer and unwalked addresses without numeric rounding", () => {
const source = {
binary: input,
direction: "to",
count: 0,
target: {
name: null,
address: "@18446744073709551615",
address_hex: "0xffffffffffffffff",
aliases: [],
},
xrefs: [],
coverage: {
decoded: false,
section: null,
code_section: null,
executable: null,
unwalked: [
{
name: "candidate",
address: "@9007199254740993",
address_hex: "0x20000000000001",
section: ".text",
},
],
notes: [],
},
data_refs_coverage: "dynamic-relocations",
data_refs_count: 1,
data_refs: [
{
kind: "pointer",
from_address: "@9007199254740995",
from_address_hex: "0x20000000000003",
from_name: null,
to_address: "@18446744073709551615",
to_address_hex: "0xffffffffffffffff",
to_name: null,
width: 8,
source: "dynamic-relocations",
},
],
};
expect(
parseKunaIncomingReferences(wire(source), input, "0xffffffffffffffff"),
).toMatchObject({
ok: true,
value: {
addresses: ["0x20000000000003"],
rows: 1,
excludedAliasRows: 0,
pointers: [
{ from_address: "0x20000000000003", to_address: "0xffffffffffffffff" },
],
walkCoverage: { unwalked: [{ address: "0x20000000000001" }] },
},
});
});
it("distinguishes unreported pointer coverage from an explicitly empty pointer list", () => {
const source = xrefs();
expect(
parseKunaIncomingReferences(wire(source), input, "0x2000"),
).toMatchObject({
ok: true,
value: { pointerCoverage: null, pointers: null, walkCoverage: null },
});
expect(
parseKunaIncomingReferences(
wire({
...source,
data_refs_coverage: "none",
data_refs_count: 0,
data_refs: [],
}),
input,
"0x2000",
),
).toMatchObject({
ok: true,
value: { pointerCoverage: "none", pointers: [], walkCoverage: null },
});
});
it("rejects partial or inconsistent pointer inventories rather than reporting them empty", () => {
const source = xrefs();
for (const fields of [
{ data_refs: [] },
{ data_refs_count: 0 },
{ data_refs_count: 1, data_refs: [] },
]) {
expect(
parseKunaIncomingReferences(
wire({ ...source, ...fields }),
input,
"0x2000",
),
).toEqual({
ok: false,
error: { code: "xref_count" },
});
}
});
it("preserves unknown coverage for an address outside known sections", () => {
const source = {
...xrefs(),
coverage: {
decoded: false,
section: null,
code_section: null,
executable: null,
unwalked: [],
notes: [],
},
};
expect(
parseKunaIncomingReferences(wire(source), input, "0x2000"),
).toMatchObject({
ok: true,
value: {
walkCoverage: { section: null, code_section: null, executable: null },
},
});
});
it.each([
[
"count",