mirror of
https://github.com/morluto/rea
synced 2026-10-27 22:45:19 -04:00
feat(experiment): compose bounded incoming reference spans
Co-authored-by: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com> Co-authored-by: Kaoru0822 <208281322+kaoru0822-kitauji@users.noreply.github.com>
This commit is contained in:
@@ -19,6 +19,12 @@ The [capability map](capability-map.md) distinguishes verified task coverage,
|
||||
experimental exporter requirements and remaining unknowns before interpreting
|
||||
adapter comparisons.
|
||||
|
||||
The [string-span study](string-span-study.md) adds paged incoming occurrence
|
||||
queries for encoded object spans and independently verifies pooled suffix
|
||||
references on real SQLite. The same native sparse range index serves Kuna's
|
||||
existing string aggregates. Equivalent cold occurrence retrieval falls from
|
||||
48.1 to 1.8 seconds; this is shared analysis reuse, not a Code Mode result.
|
||||
|
||||
The [AArch64 reference study](aarch64-xref-study.md) finds and fixes missing
|
||||
ADRP-based string and memory references before the next model comparison.
|
||||
Real SDK and traditional MCP preflights now agree with independent machine
|
||||
|
||||
@@ -5,6 +5,7 @@ import { MAX_GRAPH_BLOCKS } from "./graph-limits.mjs";
|
||||
import { createReference } from "./reference.mjs";
|
||||
import { definition, hex, page, valueLocation } from "./api-contracts.mjs";
|
||||
import { storageMethods } from "./storage-contracts.mjs";
|
||||
import { referencesMethod } from "./reference-contracts.mjs";
|
||||
|
||||
const snapshot = z.string().startsWith("snap_");
|
||||
const byteWindow = z.strictObject({
|
||||
@@ -116,29 +117,7 @@ export const methods = {
|
||||
"strings",
|
||||
'await rea.strings({query:"incorrect header check"})',
|
||||
),
|
||||
references: definition(
|
||||
{
|
||||
summary:
|
||||
"Read incoming exact-address or outgoing address/function xrefs with occurrence identity and ownership coverage.",
|
||||
description:
|
||||
"Batch incoming or outgoing lifted-code references without decompilation. Address scope selects exact target (incoming) or source instruction (outgoing); function scope selects outgoing native owning-function references and requires an entry address. Unknown function applicability stays unknown. Kind filtering is pushed to the native query; source rows, endpoints and Evidence identity remain. Recovered IR calls are a separate representation and may omit machine calls. Interior string references require a separate extent query.",
|
||||
},
|
||||
{
|
||||
addresses: z.array(hex).min(1).max(16),
|
||||
direction: z.enum(["incoming", "outgoing"]).default("incoming"),
|
||||
scope: z.enum(["address", "function"]).default("address"),
|
||||
kinds: z
|
||||
.array(z.enum(["call", "jump", "data", "read", "write"]))
|
||||
.min(1)
|
||||
.optional(),
|
||||
fields: z
|
||||
.array(z.enum(["fromFunction", "toFunction", "instruction"]))
|
||||
.optional(),
|
||||
...page,
|
||||
},
|
||||
"references",
|
||||
'await rea.references({addresses:["0x2000"]})',
|
||||
),
|
||||
references: referencesMethod,
|
||||
snapshot: definition(
|
||||
{
|
||||
summary:
|
||||
@@ -436,15 +415,4 @@ methods.bytes.input = methods.bytes.input.superRefine((value, context) => {
|
||||
message: "Byte range end exceeds the native u64 address format",
|
||||
});
|
||||
});
|
||||
methods.references.input = methods.references.input.superRefine(
|
||||
(value, context) => {
|
||||
if (value.direction === "incoming" && value.scope !== "address")
|
||||
context.addIssue({
|
||||
code: "custom",
|
||||
path: ["scope"],
|
||||
message: "Incoming references require exact address scope",
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
export const reference = createReference(methods);
|
||||
|
||||
@@ -22,6 +22,13 @@ engine profiles and limitations in [the trial record](trials.md).
|
||||
| What machine bytes support the interpretation? | Batched loaded-image bytes and selected linear instruction windows, each with source/coverage | The case models independently derive signed relative targets. These windows do not establish runtime execution/alignment, a complete machine CFG, live memory or arbitrary file-offset mappings. |
|
||||
| Can the answer be audited without returning the report? | Snapshot/source identity, selected fields, batch reads, paging and retained raw/decoded Evidence | Real CLI/MCP checks verify source access and capacity fallbacks. Scope/partial/unknown survive composition; a guest-derived answer keeps its own authority. |
|
||||
|
||||
The [string-span experiment](string-span-study.md) extends exact xrefs with
|
||||
batched incoming target-coordinate spans. It preserves all eleven selected
|
||||
SQLite data occurrences, including nine pooled suffix pointers, with individual
|
||||
endpoints/owners and raw source rows. Kuna already had aggregate string-span
|
||||
counts; the new occurrence query shares its sparse index. Range membership alone
|
||||
does not establish whole-string use, access overlap or runtime behavior.
|
||||
|
||||
The useful grain so far is an identified object plus a relation with its exact
|
||||
qualifiers: an operand needs its slot and PHI binding; a case/out-edge pair needs
|
||||
both identities; an address needs its artifact/address space; an optimized
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,7 +5,7 @@ import { join } from "node:path";
|
||||
import { parseBinaryTarget } from "../../dist/application/BinaryTargetResolver.js";
|
||||
import { inspectNativeInstallation } from "./native-installation.mjs";
|
||||
import { KunaTargetCommands } from "../../dist/kuna/KunaTargetCommands.js";
|
||||
import { referenceReader } from "./reference-reads.mjs";
|
||||
import { NativeReferenceReads } from "./reference-reads.mjs";
|
||||
import { PrivateRuntimeRoot } from "../../dist/process/PrivateRuntimeRoot.js";
|
||||
import { parseNativeIR } from "./native-ir.mjs";
|
||||
import { NativeEvidenceReads } from "./native-evidence-reads.mjs";
|
||||
@@ -44,6 +44,11 @@ export class KunaFactOwner {
|
||||
(id) => this.#sourceText(id),
|
||||
() => this.#requireNativeIR(),
|
||||
);
|
||||
#references = new NativeReferenceReads(
|
||||
(...args) => this.#native(...args),
|
||||
(id) => this.#sourceText(id),
|
||||
() => this.#requireNativeIR(),
|
||||
);
|
||||
#captureDirectory;
|
||||
#evidence = new NativeEvidenceReads(async (id) => ({
|
||||
text: await this.#sourceText(id),
|
||||
@@ -203,22 +208,7 @@ export class KunaFactOwner {
|
||||
return this.#catalogs.registers(input, signal);
|
||||
}
|
||||
async references(input, signal) {
|
||||
return readFactBatch(
|
||||
input.addresses.map((address) => referenceReader.query(input, address)),
|
||||
(query) =>
|
||||
this.#native(
|
||||
referenceReader.key(query),
|
||||
(path) => referenceReader.arguments(path, query),
|
||||
signal,
|
||||
),
|
||||
async (capture, query) =>
|
||||
referenceReader.read(
|
||||
await this.#sourceText(capture.source.evidence_id),
|
||||
capture.input,
|
||||
query,
|
||||
input,
|
||||
),
|
||||
);
|
||||
return this.#references.read(input, signal);
|
||||
}
|
||||
async snapshot(input, signal) {
|
||||
const capture = await this.#native(
|
||||
@@ -468,6 +458,7 @@ export class KunaFactOwner {
|
||||
this.#work.clear();
|
||||
this.#snapshots.clear();
|
||||
this.#catalogs.clear();
|
||||
this.#references.clear();
|
||||
this.#evidence.clear();
|
||||
}
|
||||
cancel() {
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
// One named reference operation; exact-address and image-span selectors stay distinct.
|
||||
import { z } from "zod";
|
||||
import { hex, page } from "./api-contracts.mjs";
|
||||
|
||||
const selection = {
|
||||
kinds: z
|
||||
.array(z.enum(["call", "jump", "data", "read", "write"]))
|
||||
.min(1)
|
||||
.optional(),
|
||||
fields: z
|
||||
.array(z.enum(["fromFunction", "toFunction", "instruction"]))
|
||||
.optional(),
|
||||
...page,
|
||||
};
|
||||
const span = z.strictObject({ start: hex, end_inclusive: hex });
|
||||
const input = z
|
||||
.union([
|
||||
z.strictObject({
|
||||
addresses: z.array(hex).min(1).max(16),
|
||||
direction: z.enum(["incoming", "outgoing"]).default("incoming"),
|
||||
scope: z.literal("address").default("address"),
|
||||
...selection,
|
||||
}),
|
||||
z.strictObject({
|
||||
addresses: z.array(hex).min(1).max(16),
|
||||
direction: z.literal("outgoing"),
|
||||
scope: z.literal("function"),
|
||||
...selection,
|
||||
}),
|
||||
z.strictObject({
|
||||
ranges: z.array(span).min(1).max(16),
|
||||
direction: z.literal("incoming").default("incoming"),
|
||||
scope: z.literal("range").default("range"),
|
||||
...selection,
|
||||
}),
|
||||
])
|
||||
.superRefine((value, context) => {
|
||||
for (const [i, range] of (value.ranges ?? []).entries()) {
|
||||
if (
|
||||
!hex.safeParse(range.start).success ||
|
||||
!hex.safeParse(range.end_inclusive).success
|
||||
)
|
||||
continue;
|
||||
if (BigInt(range.start) > BigInt(range.end_inclusive)) {
|
||||
context.addIssue({
|
||||
code: "custom",
|
||||
path: ["ranges", i, "end_inclusive"],
|
||||
message: "Inclusive end precedes start",
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export const referencesMethod = {
|
||||
summary:
|
||||
"Read exact-address/outgoing xrefs, or page batched incoming target-address spans with one native index.",
|
||||
description:
|
||||
"Select addresses for incoming exact-target or outgoing address/function references; function scope requires direction:outgoing and an entry. Alternatively select incoming native image ranges [{start,end_inclusive}] in caller order, including overlaps/duplicates; offset/limit apply independently to each range. Range membership tests the reference target coordinate, without callable-alias expansion. It does not prove memory-access overlap, whole-string use, or runtime binding. Both forms preserve occurrence/source-row identity, endpoints, native owning functions, paging and Evidence. A range batch builds one index; fields are projected from the same retained page. Completeness is limited to examined native references; discovery stays unknown. Recovered IR calls are a separate representation and may omit machine calls.",
|
||||
input,
|
||||
method: "references",
|
||||
example:
|
||||
'await rea.references({ranges:[{start:"0x2000",end_inclusive:"0x200d"}],kinds:["data"],limit:20})',
|
||||
};
|
||||
@@ -0,0 +1,143 @@
|
||||
import assert from "node:assert/strict";
|
||||
import test from "node:test";
|
||||
import { referencesMethod } from "./reference-contracts.mjs";
|
||||
import { methods } from "./api.mjs";
|
||||
import { NativeReferenceReads } from "./reference-reads.mjs";
|
||||
|
||||
test("range selectors preserve old inputs and reject ambiguous/directional/malformed choices", () => {
|
||||
assert.equal(methods.references, referencesMethod);
|
||||
const schema = methods.references.input;
|
||||
assert.deepEqual(schema.parse({ addresses: ["0x1"] }).addresses, ["0x1"]);
|
||||
assert.equal(
|
||||
schema.parse({
|
||||
addresses: ["0x1"],
|
||||
direction: "outgoing",
|
||||
scope: "function",
|
||||
}).scope,
|
||||
"function",
|
||||
);
|
||||
const ranges = [{ start: "0x0", end_inclusive: "0xffffffffffffffff" }];
|
||||
assert.equal(schema.parse({ ranges }).scope, "range");
|
||||
for (const input of [
|
||||
{ ranges, addresses: ["0x0"] },
|
||||
{ addresses: ["0x1"], scope: "function", direction: "incoming" },
|
||||
{ addresses: ["0x1"], scope: "function" },
|
||||
{ ranges, scope: "function" },
|
||||
{ ranges, direction: "outgoing" },
|
||||
{ ranges: [] },
|
||||
{ ranges: [{ start: "0x2", end_inclusive: "0x1" }] },
|
||||
{ ranges: [{ start: "0xg", end_inclusive: "0x1" }] },
|
||||
{ ranges: [{ start: "0x0", end_inclusive: "0x10000000000000000" }] },
|
||||
{ ranges, limit: 129 },
|
||||
{ ranges, offset: -1 },
|
||||
])
|
||||
assert.equal(schema.safeParse(input).success, false, JSON.stringify(input));
|
||||
});
|
||||
|
||||
const range = { start: "0x100", end_inclusive: "0x10d" };
|
||||
const wire = JSON.stringify({
|
||||
schema_version: 1,
|
||||
binary: "/input",
|
||||
direction: "to",
|
||||
count: 1,
|
||||
ranges: [
|
||||
{
|
||||
...range,
|
||||
index: 0,
|
||||
offset: 0,
|
||||
limit: 20,
|
||||
total: 1,
|
||||
count: 1,
|
||||
next_offset: null,
|
||||
},
|
||||
],
|
||||
xrefs: [
|
||||
{
|
||||
range_index: 0,
|
||||
address: 8,
|
||||
address_hex: "0x8",
|
||||
kind: "data",
|
||||
from_address: 8,
|
||||
from_address_hex: "0x8",
|
||||
to_address: 264,
|
||||
to_address_hex: "0x108",
|
||||
from_function: null,
|
||||
to_function: null,
|
||||
instruction: "LEA pointer",
|
||||
},
|
||||
],
|
||||
});
|
||||
const source = {
|
||||
evidence_id: "ev_page",
|
||||
sha256: "owned-sha",
|
||||
bytes: Buffer.byteLength(wire),
|
||||
elapsed_ms: 1,
|
||||
};
|
||||
test("range pages reuse one immutable parsed source across fields and expire on clear", async () => {
|
||||
let reads = 0;
|
||||
const invocations = [];
|
||||
const reader = new NativeReferenceReads(
|
||||
async (key, args) => {
|
||||
invocations.push({ key, args: args("/input") });
|
||||
return { input: "/input", source };
|
||||
},
|
||||
async () => {
|
||||
reads++;
|
||||
return wire;
|
||||
},
|
||||
() => {},
|
||||
);
|
||||
const input = referencesMethod.input.parse({ ranges: [range] });
|
||||
const first = await reader.read(input);
|
||||
const selected = await reader.read({ ...input, fields: [] });
|
||||
assert.equal(reads, 1);
|
||||
assert.equal(invocations[0].key, invocations[1].key);
|
||||
assert.deepEqual(Object.keys(selected.results[0].items[0]), [
|
||||
"source_row",
|
||||
"from",
|
||||
"to",
|
||||
"kind",
|
||||
]);
|
||||
assert.throws(() => {
|
||||
first.results[0].items[0].instruction = "corrupt";
|
||||
}, TypeError);
|
||||
first.results[0].items.length = 0;
|
||||
assert.equal((await reader.read(input)).results[0].items.length, 1);
|
||||
assert.equal(
|
||||
selected.results[0].coverage.whole_string_use,
|
||||
"not-established",
|
||||
);
|
||||
reader.clear();
|
||||
await reader.read(input);
|
||||
assert.equal(reads, 2);
|
||||
});
|
||||
test("unavailable and malformed ranges cannot masquerade as empty observations", async () => {
|
||||
const input = referencesMethod.input.parse({ ranges: [range] });
|
||||
const unavailable = new NativeReferenceReads(
|
||||
() => {
|
||||
throw Error("must not launch");
|
||||
},
|
||||
() => {},
|
||||
() => {
|
||||
throw Object.assign(Error("requires native"), {
|
||||
code: "capability_unavailable",
|
||||
});
|
||||
},
|
||||
);
|
||||
await assert.rejects(
|
||||
unavailable.read(input),
|
||||
(e) => e.code === "capability_unavailable",
|
||||
);
|
||||
const malformed = new NativeReferenceReads(
|
||||
async () => ({ input: "/input", source }),
|
||||
async () => "{}",
|
||||
() => {},
|
||||
);
|
||||
await assert.rejects(
|
||||
malformed.read(input),
|
||||
(e) =>
|
||||
e.code === "invalid_provider_output" &&
|
||||
e.details.evidence_id === source.evidence_id &&
|
||||
e.details.source_retained === true,
|
||||
);
|
||||
});
|
||||
@@ -1,7 +1,122 @@
|
||||
// Shared native reference selection; adapters never reinterpret wire endpoints.
|
||||
import { parseKunaReferenceFacts } from "../../dist/kuna/KunaReferenceFiles.js";
|
||||
import { pageFacts } from "./fact-reads.mjs";
|
||||
import { requireResult } from "./fact-error.mjs";
|
||||
import { FactFailure, requireResult } from "./fact-error.mjs";
|
||||
import { parseKunaReferenceRanges } from "../../dist/kuna/KunaReferenceRanges.js";
|
||||
import {
|
||||
freezeFacts,
|
||||
projectFact,
|
||||
readFactBatch,
|
||||
sourceSummary,
|
||||
} from "./fact-reads.mjs";
|
||||
import { NATIVE_OUTPUT_BYTES } from "./limits.mjs";
|
||||
|
||||
export class NativeReferenceReads {
|
||||
#lastRange;
|
||||
constructor(capture, readSource, requireRanges) {
|
||||
this.capture = capture;
|
||||
this.readSource = readSource;
|
||||
this.requireRanges = requireRanges;
|
||||
}
|
||||
clear() {
|
||||
this.#lastRange = undefined;
|
||||
}
|
||||
async read(input, signal) {
|
||||
if (input.ranges) return this.#ranges(input, signal);
|
||||
return readFactBatch(
|
||||
input.addresses.map((address) => referenceReader.query(input, address)),
|
||||
(query) =>
|
||||
this.capture(
|
||||
referenceReader.key(query),
|
||||
(path) => referenceReader.arguments(path, query),
|
||||
signal,
|
||||
),
|
||||
async (capture, query) =>
|
||||
referenceReader.read(
|
||||
await this.readSource(capture.source.evidence_id),
|
||||
capture.input,
|
||||
query,
|
||||
input,
|
||||
),
|
||||
);
|
||||
}
|
||||
async #ranges(input, signal) {
|
||||
this.requireRanges();
|
||||
const capture = await this.capture(
|
||||
`xref-ranges:${JSON.stringify([input.ranges, input.kinds, input.offset, input.limit])}`,
|
||||
(path) => [
|
||||
"xrefs",
|
||||
path,
|
||||
...input.ranges.flatMap((r) => [
|
||||
"--to-range",
|
||||
`${r.start}:${r.end_inclusive}`,
|
||||
]),
|
||||
"--offset",
|
||||
String(input.offset),
|
||||
"--limit",
|
||||
String(input.limit),
|
||||
"--output-bytes",
|
||||
String(NATIVE_OUTPUT_BYTES),
|
||||
...(input.kinds ? ["--kind", input.kinds.join(",")] : []),
|
||||
"--json",
|
||||
],
|
||||
signal,
|
||||
);
|
||||
if (this.#lastRange?.source.evidence_id !== capture.source.evidence_id) {
|
||||
try {
|
||||
const facts = requireResult(
|
||||
parseKunaReferenceRanges(
|
||||
await this.readSource(capture.source.evidence_id),
|
||||
capture.input,
|
||||
input,
|
||||
),
|
||||
"invalid_provider_output",
|
||||
);
|
||||
this.#lastRange = freezeFacts({
|
||||
facts,
|
||||
source: sourceSummary(capture.source),
|
||||
});
|
||||
} catch (error) {
|
||||
throw new FactFailure(
|
||||
error.code ?? "invalid_provider_output",
|
||||
error.message,
|
||||
{
|
||||
...error.details,
|
||||
evidence_id: capture.source.evidence_id,
|
||||
source_retained: true,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
const { facts, source } = this.#lastRange;
|
||||
return {
|
||||
results: facts.results.map((page) => ({
|
||||
ok: true,
|
||||
range: { ...page.range },
|
||||
range_index: page.range_index,
|
||||
items: page.facts.map((row) =>
|
||||
projectFact(row, input.fields, ["source_row", "from", "to", "kind"]),
|
||||
),
|
||||
total: page.total,
|
||||
next_offset: page.next_offset,
|
||||
source,
|
||||
coverage: {
|
||||
direction: "incoming",
|
||||
scope: "target-address membership in the inclusive static image span",
|
||||
examined_rows: page.total,
|
||||
returned_rows: page.facts.length,
|
||||
alias_expansion: "not-applied",
|
||||
discovery: "unknown",
|
||||
indirect_targets: "unknown",
|
||||
machine_semantic_equivalence: "unknown",
|
||||
runtime_binding: "unknown",
|
||||
memory_access_overlap: "not-established",
|
||||
whole_string_use: "not-established",
|
||||
},
|
||||
})),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export const referenceReader = {
|
||||
query: (input, address) => ({
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
Analyze {{artifact_path}} statically, starting with the exact encoded strings
|
||||
"no such table" and "sqlite_schema" (not longer messages that contain them).
|
||||
|
||||
Find each retained encoded string's inclusive byte span, including its observed
|
||||
terminator when the inventory includes one. Find every retained incoming data
|
||||
reference whose target coordinate lies in either span, including interior
|
||||
targets. Report the distinct instruction occurrences, target offsets and owning
|
||||
function entries; group counts by target offset without merging repeated sites
|
||||
in the same function. State what the reference discovery actually covers.
|
||||
|
||||
Explain whether each interior reference establishes use of the complete text.
|
||||
For the selected suffix use in sqlite3AlterRenameTable, verify the actual bytes,
|
||||
passed register and next direct call target. For the base-message use in
|
||||
sqlite3LocateTable, identify the local conditional message-pointer selection:
|
||||
the tested mask/storage, zero/nonzero behavior, alternate string, passing
|
||||
register and selected formatting/error call. Corroborate this with focused
|
||||
machine instructions; distinguish a message component from the separate format
|
||||
pointer. Do not infer runtime argument values, executed paths or symbol binding.
|
||||
If using recovered IR, distinguish its identities/model from machine facts.
|
||||
|
||||
Give a focused answer with exact addresses, real Evidence IDs, target digest,
|
||||
coverage and unknowns. Prefer selected facts/windows; keep full reports and
|
||||
inventories out of model context. Static span membership alone proves neither
|
||||
memory-access overlap nor whole-string use. Do not execute the target. Close
|
||||
the target lease.
|
||||
@@ -0,0 +1,18 @@
|
||||
// Input for cli.mjs; the same named operations are used by both MCP adapters.
|
||||
const literals = ["no such table", "sqlite_schema"];
|
||||
const spans = [];
|
||||
for (const literal of literals) {
|
||||
const inventory = await rea.strings({query: literal, limit: 128});
|
||||
if (!inventory.ok) return inventory;
|
||||
const exact = inventory.data.items.filter(row => row.value === literal);
|
||||
if (exact.length !== 1 || inventory.data.next_offset !== null)
|
||||
return {unknown: "Resolve the inventory ambiguity or remaining pages", literal};
|
||||
const row = exact[0];
|
||||
if (row.string.byte_length < 1)
|
||||
return {unknown: "No nonempty encoded byte extent", literal};
|
||||
spans.push({start: row.address,
|
||||
end_inclusive: "0x" + (BigInt(row.address) + BigInt(row.string.byte_length) - 1n).toString(16)});
|
||||
}
|
||||
const references = await rea.references({ranges: spans, kinds: ["data"],
|
||||
fields: ["fromFunction"], limit: 20});
|
||||
return references;
|
||||
@@ -0,0 +1,72 @@
|
||||
# Frozen SQLite pooled-string task
|
||||
|
||||
Freeze this question and oracle before the three model runs. The target is the
|
||||
Linux x86-64 `/usr/lib/x86_64-linux-gnu/libsqlite3.so.0.8.6`, SHA-256
|
||||
`2e6eef9a727f081f0d453b4e5e6cbd8b9ef8b6f86cbf7681cbad444d3b0b55c8`.
|
||||
It is inspected statically, never executed. The independent fixture oracle is
|
||||
`verify-string-span-machine.py`; its fixed expectations are checked against ELF
|
||||
loaded bytes, GNU disassembly and relocation records, without reading Kuna output.
|
||||
This is a selected corpus check, not a proof of global reference completeness.
|
||||
|
||||
Both exact inventory strings are ASCII with an observed NUL and 14 encoded bytes:
|
||||
|
||||
| String | Inclusive span | Target offsets and occurrence counts |
|
||||
| ------------- | ----------------- | ------------------------------------ |
|
||||
| no such table | 0x11b376–0x11b383 | 0: 1; 8: 6 |
|
||||
| sqlite_schema | 0x11b33f–0x11b34c | 0: 1; 7: 3 |
|
||||
|
||||
The complete selected data occurrence set is:
|
||||
|
||||
| From | Target | Owner entry | Native name |
|
||||
| ------- | -------- | ----------- | ------------------------- |
|
||||
| 0x4215c | 0x11b376 | 0x420d0 | sqlite3LocateTable |
|
||||
| 0x2d709 | 0x11b37e | 0x2d600 | sqlite3AlterRenameTable |
|
||||
| 0x4565e | 0x11b37e | 0x45590 | sqlite3StartTable |
|
||||
| 0x4573d | 0x11b37e | 0x45590 | sqlite3StartTable |
|
||||
| 0x48b2e | 0x11b37e | 0x47ff0 | sqlite3EndTable |
|
||||
| 0xbad38 | 0x11b37e | 0xb90e0 | sqlite3Pragma |
|
||||
| 0xbd1f0 | 0x11b37e | 0xbd1b0 | sqlite3InitOne |
|
||||
| 0x422c5 | 0x11b33f | 0x42280 | sqlite3PreferredTableName |
|
||||
| 0x41f0b | 0x11b346 | 0x41db0 | sqlite3FindTable |
|
||||
| 0x41f65 | 0x11b346 | 0x41db0 | sqlite3FindTable |
|
||||
| 0x41fb2 | 0x11b346 | 0x41db0 | sqlite3FindTable |
|
||||
|
||||
Order is irrelevant; all eleven distinct sites and owner entries are required.
|
||||
Nine interior-target occurrences are suffix pointers (`table`, `schema`), not
|
||||
nine uses of the entire original inventory literal. Scope is retained static
|
||||
data references, not exhaustive machine dataflow, dynamic aliases or executed use.
|
||||
|
||||
At `0x2d709`, bytes `488d156edc0e00` form `0x11b37e` in RDX. There is no RDX
|
||||
overwrite before the direct call at `0x2d716` to `0x29060`. The PLT jump slot is
|
||||
`0x159fc8`, whose ELF JUMP_SLOT symbol is `sqlite3CheckObjectName`; this does not
|
||||
establish the runtime bound implementation. Decoding must start at a known
|
||||
instruction boundary, e.g. `0x2d706`, rather than a guessed mid-instruction start.
|
||||
|
||||
At `0x42158`, `AND R13D,1` (`4183e501`) sets flags. The LEAs at `0x4215c`
|
||||
(`488d1513920d00`) and `0x42163` load the base `no such table` pointer into RDX
|
||||
and `no such view` (`0x11b369`) into RAX. These LEAs preserve flags.
|
||||
`CMOVNE RDX,RAX` at `0x4216a` (`480f45d0`) keeps the table component when the
|
||||
masked result is zero and selects view when nonzero. The separate test/branch
|
||||
at `0x4216e`/`0x42171` must not be substituted for this selection. On the
|
||||
selected fallthrough path, RDX is still the message component at the call at
|
||||
`0x42185` to `0x2a1e0`; RSI separately receives the format pointer `0x11b38c`
|
||||
at `0x4217c`. The PLT slot `0x15a888` has symbol `sqlite3ErrorMsg`.
|
||||
Runtime values, feasibility, effects and binding remain unknown.
|
||||
|
||||
Grade complete answers against these facts and verify cited Evidence IDs,
|
||||
artifact/engine identity and selected source rows. An answer that misses interior
|
||||
targets, merges the three FindTable sites, claims whole-message use from span
|
||||
membership or reverses CMOVNE behavior fails. Record failures and retries rather
|
||||
than selecting only successful runs. Full real-model task costs include tool
|
||||
schemas, reference help, rejected calls, responses, cached/uncached input, output
|
||||
and reasoning tokens, wall time, local family CPU and kernel family peak memory.
|
||||
|
||||
Use one frozen runtime, engine and target for traditional (the complete supported
|
||||
16-tool selector), atomic Direct (22 tools) and Code Mode (4 tools), serially in
|
||||
fresh isolated profiles. All have the common client JavaScript executor.
|
||||
The canonical provider has exact-address xrefs, so complete spans can be
|
||||
enumerated; only the atomic API exposes the new native span occurrence query.
|
||||
Consequently this is a workflow/profile comparison, not a controlled test of
|
||||
adapter overhead alone. Native Kuna already aggregates string-span counts and
|
||||
owner functions, but that aggregation lacks individual reference endpoints and
|
||||
is not exposed as an equivalent traditional MCP operation.
|
||||
@@ -0,0 +1,148 @@
|
||||
# Incoming reference spans and pooled strings
|
||||
|
||||
This experiment adds individually traceable incoming reference occurrences for
|
||||
inclusive target-address spans. It does not establish whole-string use from a
|
||||
reference into shared storage. No PR or integration is intended.
|
||||
|
||||
Before this work, the branch refreshed main to `0c7b6d834` in `c8d831936`.
|
||||
The native overlay remains based on Kuna `fe01d6294eb0d1053ce343c8943646df88e143fc`.
|
||||
The immutable tested executable SHA-256 is
|
||||
`2e398e7229e30b6d2387a802db8a0050764119a6b457020d334855e36e69d6c5`;
|
||||
the full overlay is `kuna-native-ir.patch`. Node is 24.18.0 / npm 11.16.0.
|
||||
|
||||
## Existing capability and missing composition
|
||||
|
||||
Native Kuna already aggregates xrefs and owner functions over a string's encoded
|
||||
extent. Its string output does not include each reference's source/target pair.
|
||||
REA's cheap inventory intentionally skips that analysis; the existing reference
|
||||
operation selects exact addresses, so complete occurrence retrieval previously
|
||||
required enumerating the encoded span. This is an interface/composition gap,
|
||||
not evidence that Kuna had no string-span aggregation.
|
||||
|
||||
On real Linux SQLite, exact `no such table` occupies `0x11b376–0x11b383` and
|
||||
`sqlite_schema` occupies `0x11b33f–0x11b34c`, both including NUL. Base-address
|
||||
queries return one occurrence each; their full spans retain seven and four.
|
||||
Nine interior occurrences target the `table` / `schema` suffixes. Calling all
|
||||
seven references uses of the complete error message would be wrong. The frozen
|
||||
[task oracle](string-span-oracle.md) separates occurrence completeness, symbol
|
||||
membership, bytes, register passing and conditional message selection.
|
||||
|
||||
## Implementation and bounds
|
||||
|
||||
The existing named `references` operation accepts explicit `ranges`, preserving
|
||||
the old exact-address and outgoing-function selectors. CLI, SDK, Direct MCP and
|
||||
Code Mode use the same contract and owner. There remain 19 named methods, 22
|
||||
Direct tools and four Code Mode tools. Both advertised catalogs were validated
|
||||
after actual SDK conversion, including valid/invalid reference selectors.
|
||||
|
||||
The native BTreeMap index owns sparse inclusive range lookup, including u64 max
|
||||
without end-address increment. The string aggregate also uses this owner,
|
||||
removing its per-byte lookup loop and quadratic function deduplication. Legacy
|
||||
and range wire rows share endpoint/function interpretation and one JSON escaping
|
||||
writer. A batch builds one index, supports 16 spans and 128 selected rows per
|
||||
span, and streams into an 8 MiB encoded-output budget. Selected row pointers are
|
||||
bounded; serialization failure returns an error without partial JSON stdout.
|
||||
These bounds cap native work/serialization and match the experimental fact page.
|
||||
|
||||
The TypeScript provider boundary owns exact u64 parsing, row consistency and
|
||||
normalization for both formats. Range metadata binds by explicit query index,
|
||||
not catalog/array order. Overlapping and duplicate caller ranges remain separate;
|
||||
`source_row` indexes the retained batch's original flat xrefs array. It is not a
|
||||
global occurrence ID. Malformed or unavailable data remains an error with source
|
||||
Evidence where captured, never an empty observation.
|
||||
|
||||
Native capture cache keys cover target/engine identity, ordered spans, kinds,
|
||||
offset and limit. Fields project the same retained source. One immutable parsed
|
||||
range batch is retained per lease and cleared after successful close. Different
|
||||
pages still rebuild a native index; this is not a persistent analysis database.
|
||||
Coverage explicitly leaves discovery, indirect targets, runtime binding, memory
|
||||
access overlap and whole-string use unknown/not established. Target-coordinate
|
||||
membership does not expand import aliases or establish all bytes touched by an
|
||||
access.
|
||||
|
||||
## Executed verification
|
||||
|
||||
- Native release tests: 25 `listing::xrefs`, two range CLI tests and 15 JSON
|
||||
writer tests passed, including sparse/full-u64 selection, selector conflicts
|
||||
and escaped-byte streaming bounds. Release binary rebuilt after final changes.
|
||||
A real one-byte output-budget query exited with the explicit capacity error and
|
||||
zero stdout bytes, confirming fail-closed serialization at the CLI boundary.
|
||||
- Focused production boundary: 23 existing reference tests plus four new range
|
||||
groups passed (27 total), with full runtime compilation. Focused owner graph
|
||||
typecheck, changed-file lint and formatting passed. Full `check:fast` was
|
||||
attempted but stopped at the 3.25 GiB threshold after 45.806 s; sampled peak
|
||||
3,330.8 MiB, sampled OOM=0, final payload counters unavailable. Its processes
|
||||
exited after owned-scope cleanup. This is not a passing full root typecheck.
|
||||
`check:pr` and an exhaustive native corpus were not run. Experiment pushes
|
||||
bypass the resource-limited pre-push gate rather than repeat it unbounded.
|
||||
- All 90 experimental JavaScript tests passed. Five resource receipt/policy
|
||||
tests passed, including rejecting unsafe bounds before process acquisition.
|
||||
- Real SDK, Direct MCP and Code Mode range verifiers passed all eleven exact
|
||||
occurrences/owners, field projection/source reuse, overlaps, repeated spans,
|
||||
empty u64-max page, pagination, selected bytes/instructions, raw source hashes
|
||||
and source-row traceability. Each verified cleanup.
|
||||
- The public experimental CLI ran `string-span-example.txt` and returned totals
|
||||
seven/four. Native string aggregate JSON remained semantically identical to
|
||||
the previous engine, including counts and function lists. Real x86 SSH complete
|
||||
function/prototype fidelity and AArch64 xref/return verifiers passed on the new
|
||||
engine. Targets were never executed.
|
||||
- `verify-string-span-machine.py` independently passed ELF loaded bytes, all
|
||||
eleven selected GNU LEA targets, exported function interval membership, RIP
|
||||
displacement math, mask/CMOV bytes and the two ELF PLT-slot relocations.
|
||||
This is not a proof of dynamic binding or global xref completeness.
|
||||
- The canonical native overlay passed forward application against a temporary
|
||||
upstream index and reverse application against the owned patched worktree.
|
||||
|
||||
The first public SDK attempt exposed a stale registry refinement that rejected
|
||||
the new selector despite the standalone contract passing. That redundant guard
|
||||
was removed; the regression now checks the final registry, and the second SDK
|
||||
attempt passed. Preserve the failed attempt (`range-sdk.log`) in the record.
|
||||
An initial focused build crossed the default 3 GiB early stop, without OOM. A
|
||||
validated optional threshold allows the build to use 3.25 GiB; its successful
|
||||
focused run took 35.964 s / 35.703191 CPU s, peak 3,084.7 MiB. The hard cap
|
||||
remains 4 GiB, swap zero, one CPU and 192 tasks. Analysis/model runs retain the
|
||||
default 3 GiB early stop. Payload-exit counters verify cleanup and OOM=0 for all
|
||||
successful bounded runs here; kernel process-family memory includes file cache,
|
||||
not just RSS. Exited transient cgroup final counters are unavailable.
|
||||
|
||||
## Measured equivalent reference task
|
||||
|
||||
The exact-address baseline enumerates both 14-byte spans and retains all raw
|
||||
sources. The new range query obtains the same eleven occurrences and owners
|
||||
with one native batch. These are reference-phase measurements after inventory
|
||||
selection, not complete model costs; cold means fresh lease/capture cache, not
|
||||
cold kernel file pages. The same new engine is used in both rows.
|
||||
|
||||
| SDK reference workflow | Cold wall | Local family CPU | Warm wall |
|
||||
| ------------------------ | --------: | ---------------: | --------: |
|
||||
| 28 exact-address queries | 48.123 s | 47.791799 s | 9.899 ms |
|
||||
| One two-span batch | 1.821 s | 1.806206 s | 0.984 ms |
|
||||
|
||||
The selected cold phase is about 26.4 times faster. This is shared native work,
|
||||
not a Code Mode speedup. Old-engine exact enumeration also agreed on every fact
|
||||
and took 46.806 s. The independent Direct and Code Mode batch preflights took
|
||||
1.755 / 1.798 s cold and 3.066 / 9.542 ms warm. They have different transport
|
||||
overheads and are single observations, not benchmark distributions.
|
||||
|
||||
Whole verifier resource receipts (which include additional validation queries):
|
||||
|
||||
| Run | Wall | Local family CPU | Kernel family peak |
|
||||
| ------------------------- | -------: | ---------------: | -----------------: |
|
||||
| New-engine exact baseline | 50.557 s | 50.055582 s | 475.6 MiB |
|
||||
| SDK range verifier | 14.616 s | 14.279055 s | 482.7 MiB |
|
||||
| Direct range verifier | 15.120 s | 14.742037 s | 312.6 MiB |
|
||||
| Code Mode range verifier | 15.630 s | 15.257697 s | 331.8 MiB |
|
||||
|
||||
These suites have different validation work; their peak values do not establish
|
||||
an equivalent-task memory reduction. Scripted checks have no model token usage.
|
||||
Raw receipts/captures live in the ignored `.cache/atomic-codemode/range-*` paths.
|
||||
Selected receipt SHA-256 values:
|
||||
|
||||
- `range-exact-after.log`: `cb1d19a4f58face2d442868add0289c3ca62f070ef14f5e4bf0cc101b3bd9ed8`
|
||||
- `range-sdk-v2.log`: `f203ab548d7509b37a15c39aaf8e466e61f7591eddcb1868be97477811a698c8`
|
||||
- `range-direct.log`: `c4efbcee95e087fa676b560f70bbb8f6617475be122289c3052f19af02efb2b8`
|
||||
- `range-codemode.log`: `1d967347c31c99f8caf71391502a7e24ef5fcdcaaaddc5c8d1272eb5b8d7eeae`
|
||||
|
||||
Real-model results will be recorded separately after the frozen three-profile
|
||||
trial. No token, general complex-software or adapter superiority claim follows
|
||||
from these preflights.
|
||||
@@ -51,6 +51,7 @@ for (const direct of [false, true]) {
|
||||
);
|
||||
const prefix = direct ? "facts_" : "rea.";
|
||||
const ids = [
|
||||
"references",
|
||||
"spaces",
|
||||
"registers",
|
||||
"switches",
|
||||
@@ -118,6 +119,30 @@ for (const direct of [false, true]) {
|
||||
assert.ok(catalog.tools.some((tool) => tool.name === method.name));
|
||||
}
|
||||
for (const [name, good, bad] of [
|
||||
[
|
||||
"references",
|
||||
{ addresses: ["0x1"], direction: "outgoing", scope: "function" },
|
||||
{ addresses: ["0x1"], direction: "incoming", scope: "function" },
|
||||
],
|
||||
[
|
||||
"references",
|
||||
{ addresses: ["0x1"] },
|
||||
{
|
||||
addresses: ["0x1"],
|
||||
ranges: [{ start: "0x0", end_inclusive: "0x1" }],
|
||||
},
|
||||
],
|
||||
[
|
||||
"references",
|
||||
{
|
||||
ranges: [{ start: "0x0", end_inclusive: "0xffffffffffffffff" }],
|
||||
fields: [],
|
||||
},
|
||||
{
|
||||
ranges: [{ start: "0x0", end_inclusive: "0x1" }],
|
||||
direction: "outgoing",
|
||||
},
|
||||
],
|
||||
["spaces", { indices: [3], fields: ["kind"] }, { indices: ["3"] }],
|
||||
[
|
||||
"registers",
|
||||
|
||||
@@ -0,0 +1,286 @@
|
||||
// Real SDK/MCP range facts, source identity, paging and bounded native work.
|
||||
import assert from "node:assert/strict";
|
||||
import { createHash } from "node:crypto";
|
||||
import { writeFile } from "node:fs/promises";
|
||||
import { resolve } from "node:path";
|
||||
import { Client } from "@modelcontextprotocol/client";
|
||||
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
|
||||
import { AtomicSession } from "./session.mjs";
|
||||
import { familyCounters } from "./family-counters.mjs";
|
||||
import {
|
||||
createVerifierRun,
|
||||
completeVerifierRun,
|
||||
assertVerifierCleanupComplete,
|
||||
} from "../../scripts/lib/verifier-run.mjs";
|
||||
|
||||
const [binary, output, adapter = "sdk"] = process.argv.slice(2);
|
||||
assert(binary && output && ["sdk", "direct", "codemode"].includes(adapter));
|
||||
const verifier = createVerifierRun(),
|
||||
session = adapter === "sdk" ? new AtomicSession() : null;
|
||||
const client = session
|
||||
? null
|
||||
: new Client({ name: "rea-range-verifier", version: "0.1.0" });
|
||||
const transport = session
|
||||
? null
|
||||
: new StdioClientTransport({
|
||||
command: process.execPath,
|
||||
args: [resolve("experiments/atomic-codemode/server.mjs")],
|
||||
env: { ...process.env, REA_ATOMIC_ADAPTER: adapter },
|
||||
stderr: "pipe",
|
||||
});
|
||||
const observations = [],
|
||||
sources = new Map(),
|
||||
phases = [],
|
||||
failures = [];
|
||||
const invoke = async (name, input) => {
|
||||
const reply = await client.callTool(
|
||||
{ name, arguments: input },
|
||||
{ timeout: 180000 },
|
||||
);
|
||||
return JSON.parse(reply.content[0].text);
|
||||
};
|
||||
const read = async (method, input) => {
|
||||
const reply = session
|
||||
? await session.call(method, input)
|
||||
: adapter === "direct"
|
||||
? await invoke(`facts_${method}`, input)
|
||||
: (
|
||||
await invoke("execute_javascript", {
|
||||
code: `return await rea.${method}(${JSON.stringify(input)});`,
|
||||
})
|
||||
).value;
|
||||
assert.equal(reply.ok, true, JSON.stringify(reply));
|
||||
assert.equal(
|
||||
reply.context.artifact_sha256,
|
||||
"2e6eef9a727f081f0d453b4e5e6cbd8b9ef8b6f86cbf7681cbad444d3b0b55c8",
|
||||
);
|
||||
for (const value of reply.data.results ?? [reply.data]) {
|
||||
if (Object.hasOwn(value, "ok"))
|
||||
assert.equal(value.ok, true, JSON.stringify(value));
|
||||
if (value.source) sources.set(value.source.evidence_id, value.source);
|
||||
}
|
||||
observations.push({ method, input, data: reply.data });
|
||||
return reply.data;
|
||||
};
|
||||
const measure = async (phase, work) => {
|
||||
const a = await familyCounters(),
|
||||
start = performance.now(),
|
||||
value = await work(),
|
||||
b = await familyCounters();
|
||||
phases.push({
|
||||
phase,
|
||||
elapsed_ms: performance.now() - start,
|
||||
family_cpu_seconds: (b.cpu - a.cpu) / 1e6,
|
||||
cumulative_family_memory_peak_bytes: b.peak,
|
||||
});
|
||||
return value;
|
||||
};
|
||||
const key = (row) => [
|
||||
row.from,
|
||||
row.to,
|
||||
row.kind,
|
||||
row.fromFunction?.address ?? null,
|
||||
row.fromFunction?.name ?? null,
|
||||
];
|
||||
const expected = [
|
||||
["0x4215c", "0x11b376", "data", "0x420d0", "sqlite3LocateTable"],
|
||||
["0x2d709", "0x11b37e", "data", "0x2d600", "sqlite3AlterRenameTable"],
|
||||
["0x4565e", "0x11b37e", "data", "0x45590", "sqlite3StartTable"],
|
||||
["0x4573d", "0x11b37e", "data", "0x45590", "sqlite3StartTable"],
|
||||
["0x48b2e", "0x11b37e", "data", "0x47ff0", "sqlite3EndTable"],
|
||||
["0xbad38", "0x11b37e", "data", "0xb90e0", "sqlite3Pragma"],
|
||||
["0xbd1f0", "0x11b37e", "data", "0xbd1b0", "sqlite3InitOne"],
|
||||
];
|
||||
const schemaExpected = [
|
||||
["0x422c5", "0x11b33f", "data", "0x42280", "sqlite3PreferredTableName"],
|
||||
...["0x41f0b", "0x41f65", "0x41fb2"].map((from) => [
|
||||
from,
|
||||
"0x11b346",
|
||||
"data",
|
||||
"0x41db0",
|
||||
"sqlite3FindTable",
|
||||
]),
|
||||
];
|
||||
let context;
|
||||
try {
|
||||
if (session) {
|
||||
const opened = await session.open(binary);
|
||||
context = opened.context;
|
||||
} else {
|
||||
await client.connect(transport);
|
||||
const opened = await invoke("open_binary", { path: binary });
|
||||
assert.equal(opened.ok, true);
|
||||
context = opened.context;
|
||||
}
|
||||
const ranges = [];
|
||||
for (const value of ["no such table", "sqlite_schema"]) {
|
||||
const strings = await read("strings", { query: value, limit: 128 });
|
||||
const matches = strings.items.filter((s) => s.value === value);
|
||||
assert.equal(matches.length, 1);
|
||||
const string = matches[0];
|
||||
assert.equal(string.string.encoding, "ascii");
|
||||
assert.equal(string.string.byte_length, 14);
|
||||
ranges.push({
|
||||
start: string.address,
|
||||
end_inclusive: `0x${(BigInt(string.address) + BigInt(string.string.byte_length) - 1n).toString(16)}`,
|
||||
});
|
||||
}
|
||||
const input = {
|
||||
ranges,
|
||||
kinds: ["data"],
|
||||
fields: ["fromFunction", "toFunction", "instruction"],
|
||||
limit: 128,
|
||||
};
|
||||
const cold = await measure("cold-two-span-batch", () =>
|
||||
read("references", input),
|
||||
);
|
||||
assert.deepEqual(cold.results[0].items.map(key), expected);
|
||||
assert.deepEqual(cold.results[1].items.map(key), schemaExpected);
|
||||
assert.equal(
|
||||
new Set(cold.results.map((r) => r.source.evidence_id)).size,
|
||||
1,
|
||||
"one source covers both ranges",
|
||||
);
|
||||
const warm = await measure("warm-same-two-span-batch", () =>
|
||||
read("references", input),
|
||||
);
|
||||
assert.deepEqual(warm, cold);
|
||||
const narrow = await read("references", { ...input, fields: [] });
|
||||
assert.deepEqual(narrow.results[0].source, cold.results[0].source);
|
||||
assert.deepEqual(Object.keys(narrow.results[0].items[0]), [
|
||||
"source_row",
|
||||
"from",
|
||||
"to",
|
||||
"kind",
|
||||
]);
|
||||
for (const page of cold.results) {
|
||||
assert.equal(page.coverage.discovery, "unknown");
|
||||
assert.equal(page.coverage.whole_string_use, "not-established");
|
||||
assert.equal(page.next_offset, null);
|
||||
}
|
||||
const overlap = await read("references", {
|
||||
ranges: [
|
||||
ranges[0],
|
||||
{ start: "0x11b37e", end_inclusive: ranges[0].end_inclusive },
|
||||
ranges[0],
|
||||
{ start: "0xffffffffffffffff", end_inclusive: "0xffffffffffffffff" },
|
||||
],
|
||||
kinds: ["data"],
|
||||
limit: 128,
|
||||
});
|
||||
assert.deepEqual(
|
||||
overlap.results.map((r) => r.total),
|
||||
[7, 6, 7, 0],
|
||||
);
|
||||
assert.deepEqual(
|
||||
overlap.results[0].items.map(key),
|
||||
overlap.results[2].items.map(key),
|
||||
);
|
||||
const recovered = [];
|
||||
for (let offset = 0; offset !== null;) {
|
||||
const reply = await read("references", {
|
||||
ranges: [ranges[0]],
|
||||
kinds: ["data"],
|
||||
offset,
|
||||
limit: 2,
|
||||
});
|
||||
assert.equal(reply.results[0].total, 7);
|
||||
recovered.push(...reply.results[0].items.map(key));
|
||||
offset = reply.results[0].next_offset;
|
||||
}
|
||||
assert.deepEqual(recovered, expected);
|
||||
const bytes = await read("bytes", {
|
||||
windows: ranges.map((r) => ({ address: r.start, length: 14 })),
|
||||
});
|
||||
assert.deepEqual(
|
||||
bytes.results.map((r) => Buffer.from(r.hex, "hex").toString("ascii")),
|
||||
["no such table\0", "sqlite_schema\0"],
|
||||
);
|
||||
const machine = await read("instructions", {
|
||||
windows: [
|
||||
{ address: "0x2d706", count: 6 },
|
||||
{ address: "0x42158", count: 12 },
|
||||
],
|
||||
fields: ["bytes", "text"],
|
||||
});
|
||||
for (const [at, bytes] of [
|
||||
["0x2d709", "488d156edc0e00"],
|
||||
["0x4215c", "488d1513920d00"],
|
||||
["0x4216a", "480f45d0"],
|
||||
])
|
||||
assert(
|
||||
machine.results.some((r) =>
|
||||
r.items.some((i) => i.address === at && i.bytes === bytes),
|
||||
),
|
||||
at,
|
||||
);
|
||||
// Check each source row against its actual native batch position and page.
|
||||
for (const source of sources.values()) {
|
||||
let text = "",
|
||||
offset = 0;
|
||||
do {
|
||||
const page = await read("evidence", {
|
||||
evidence_id: source.evidence_id,
|
||||
offset,
|
||||
limit: 16384,
|
||||
});
|
||||
text += page.text;
|
||||
offset = page.next_offset;
|
||||
} while (offset !== null);
|
||||
assert.equal(Buffer.byteLength(text), source.bytes);
|
||||
assert.equal(
|
||||
createHash("sha256").update(text).digest("hex"),
|
||||
source.sha256,
|
||||
);
|
||||
const raw = JSON.parse(text);
|
||||
const pages = observations
|
||||
.filter((o) => o.method === "references")
|
||||
.flatMap((o) => o.data.results)
|
||||
.filter((p) => p.source.evidence_id === source.evidence_id);
|
||||
for (const page of pages)
|
||||
for (const fact of page.items) {
|
||||
const row = raw.xrefs[fact.source_row];
|
||||
assert.equal(row.range_index, page.range_index);
|
||||
assert.equal(row.from_address_hex, fact.from);
|
||||
assert.equal(row.to_address_hex, fact.to);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
failures.push(error.stack ?? String(error));
|
||||
} finally {
|
||||
if (session) await session.close();
|
||||
else {
|
||||
try {
|
||||
await invoke("close_binary", {});
|
||||
} finally {
|
||||
await client.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
const lifecycle = await completeVerifierRun(verifier);
|
||||
assertVerifierCleanupComplete(lifecycle.process_lineage);
|
||||
await writeFile(
|
||||
output,
|
||||
JSON.stringify({
|
||||
adapter,
|
||||
context,
|
||||
observations,
|
||||
phases,
|
||||
failures,
|
||||
lifecycle,
|
||||
source_count: sources.size,
|
||||
scope:
|
||||
"Selected static SDK/MCP range facts and original-source fidelity. No target execution, complete discovery or whole-string-use proof.",
|
||||
}),
|
||||
{ flag: "wx" },
|
||||
);
|
||||
console.log(
|
||||
JSON.stringify({
|
||||
output,
|
||||
adapter,
|
||||
phases,
|
||||
failures,
|
||||
source_count: sources.size,
|
||||
}),
|
||||
);
|
||||
if (failures.length) process.exitCode = 1;
|
||||
@@ -0,0 +1,125 @@
|
||||
"""Independent ELF/GNU oracle for the pinned SQLite pooled-string task."""
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import re
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
if sys.flags.optimize:
|
||||
raise RuntimeError('Run the oracle without Python optimization')
|
||||
binary, output = map(Path, sys.argv[1:])
|
||||
raw = binary.read_bytes()
|
||||
digest = hashlib.sha256(raw).hexdigest()
|
||||
assert digest == '2e6eef9a727f081f0d453b4e5e6cbd8b9ef8b6f86cbf7681cbad444d3b0b55c8'
|
||||
assert raw[:6] == b'\x7fELF\x02\x01' and struct.unpack_from('<H', raw, 18)[0] == 62
|
||||
phoff = struct.unpack_from('<Q', raw, 32)[0]
|
||||
phsize, phcount = struct.unpack_from('<HH', raw, 54)
|
||||
assert phsize >= 56 and phoff + phsize * phcount <= len(raw)
|
||||
segments = [struct.unpack_from('<IIQQQQQQ', raw, phoff + i * phsize) for i in range(phcount)]
|
||||
|
||||
|
||||
def loaded(address, length):
|
||||
matches = [p for p in segments if p[0] == 1 and p[3] <= address
|
||||
and address + length <= p[3] + p[5]]
|
||||
assert len(matches) == 1
|
||||
segment = matches[0]
|
||||
start = segment[2] + address - segment[3]
|
||||
assert start + length <= len(raw)
|
||||
return raw[start:start + length]
|
||||
|
||||
|
||||
spans = [(0x11b376, 0x11b383, b'no such table\0'),
|
||||
(0x11b33f, 0x11b34c, b'sqlite_schema\0')]
|
||||
for start, end, text in spans:
|
||||
assert loaded(start, end - start + 1) == text
|
||||
expected = {(0x4215c, 0x11b376), (0x2d709, 0x11b37e),
|
||||
(0x4565e, 0x11b37e), (0x4573d, 0x11b37e),
|
||||
(0x48b2e, 0x11b37e), (0xbad38, 0x11b37e),
|
||||
(0xbd1f0, 0x11b37e), (0x422c5, 0x11b33f),
|
||||
(0x41f0b, 0x11b346), (0x41f65, 0x11b346), (0x41fb2, 0x11b346)}
|
||||
observed = set()
|
||||
# Decode from the ELF's normal instruction sections, not arbitrary mid-instruction
|
||||
# window starts. Nearest/synthetic symbol labels do not establish target identity.
|
||||
with subprocess.Popen(['objdump', '-d', '-w', '--no-show-raw-insn', str(binary)],
|
||||
stdout=subprocess.PIPE, text=True) as child:
|
||||
for line in child.stdout:
|
||||
match = re.match(r'\s*([0-9a-f]+):\s+lea\w*\s+.*#\s*([0-9a-f]+)\b', line)
|
||||
if match:
|
||||
source, target = (int(value, 16) for value in match.groups())
|
||||
if any(start <= target <= end for start, end, _ in spans):
|
||||
observed.add((source, target))
|
||||
assert child.wait() == 0
|
||||
assert observed == expected
|
||||
owners = {
|
||||
0x4215c: (0x420d0, 'sqlite3LocateTable'),
|
||||
0x2d709: (0x2d600, 'sqlite3AlterRenameTable'),
|
||||
0x4565e: (0x45590, 'sqlite3StartTable'),
|
||||
0x4573d: (0x45590, 'sqlite3StartTable'),
|
||||
0x48b2e: (0x47ff0, 'sqlite3EndTable'),
|
||||
0xbad38: (0xb90e0, 'sqlite3Pragma'),
|
||||
0xbd1f0: (0xbd1b0, 'sqlite3InitOne'),
|
||||
0x422c5: (0x42280, 'sqlite3PreferredTableName'),
|
||||
0x41f0b: (0x41db0, 'sqlite3FindTable'),
|
||||
0x41f65: (0x41db0, 'sqlite3FindTable'),
|
||||
0x41fb2: (0x41db0, 'sqlite3FindTable'),
|
||||
}
|
||||
symbols = set()
|
||||
for line in subprocess.check_output(['readelf', '-Ws', str(binary)], text=True).splitlines():
|
||||
fields = line.split()
|
||||
if len(fields) == 8 and fields[3] == 'FUNC' and fields[6] != 'UND':
|
||||
symbols.add((int(fields[1], 16), int(fields[2]), fields[7]))
|
||||
for at, (entry, name) in owners.items():
|
||||
assert any(start == entry and symbol == name and start <= at < start + size
|
||||
for start, size, symbol in symbols)
|
||||
|
||||
|
||||
def lea(at):
|
||||
data = loaded(at, 7)
|
||||
assert data[0] & 0xf8 == 0x48 and data[1] == 0x8d and data[2] & 0xc7 == 0x05
|
||||
register = ((data[2] >> 3) & 7) | ((data[0] & 4) << 1)
|
||||
names = ['rax', 'rcx', 'rdx', 'rbx', 'rsp', 'rbp', 'rsi', 'rdi',
|
||||
'r8', 'r9', 'r10', 'r11', 'r12', 'r13', 'r14', 'r15']
|
||||
return at + 7 + struct.unpack_from('<i', data, 3)[0], names[register]
|
||||
|
||||
|
||||
for source, target in expected:
|
||||
assert lea(source)[0] == target
|
||||
assert lea(0x2d709) == (0x11b37e, 'rdx')
|
||||
assert lea(0x4215c) == (0x11b376, 'rdx')
|
||||
assert lea(0x42163) == (0x11b369, 'rax')
|
||||
assert lea(0x4217c) == (0x11b38c, 'rsi')
|
||||
assert loaded(0x11b369, 13) == b'no such view\0'
|
||||
assert loaded(0x42158, 4).hex() == '4183e501' # AND R13D,1: sets flags
|
||||
assert loaded(0x4216a, 4).hex() == '480f45d0' # CMOVNE RDX,RAX
|
||||
relocations = subprocess.check_output(['readelf', '-Wr', str(binary)], text=True)
|
||||
calls = []
|
||||
for at, target, name in [(0x2d716, 0x29060, 'sqlite3CheckObjectName'),
|
||||
(0x42185, 0x2a1e0, 'sqlite3ErrorMsg')]:
|
||||
call = loaded(at, 5)
|
||||
assert call[0] == 0xe8 and at + 5 + struct.unpack_from('<i', call, 1)[0] == target
|
||||
stub = target
|
||||
if loaded(stub, 4) == b'\xf3\x0f\x1e\xfa':
|
||||
stub += 4
|
||||
if loaded(stub, 1) == b'\xf2':
|
||||
stub += 1
|
||||
jump = loaded(stub, 6)
|
||||
assert jump[:2] == b'\xff\x25'
|
||||
slot = stub + 6 + struct.unpack_from('<i', jump, 2)[0]
|
||||
assert re.search(rf'^0*{slot:x}\s+[0-9a-f]+\s+R_X86_64_JUMP_SLOT\s+[0-9a-f]+\s+{name}\s+\+\s+0$',
|
||||
relocations, re.MULTILINE)
|
||||
calls.append({'origin': hex(at), 'plt_entry': hex(target), 'slot': hex(slot), 'relocation_symbol': name})
|
||||
report = {'binary_sha256': digest, 'elf_machine': 62,
|
||||
'spans': [{'start': hex(s), 'end_inclusive': hex(e), 'bytes_with_nul': e - s + 1} for s, e, _ in spans],
|
||||
'gnu_lea_and_file_target_pairs': [[hex(a), hex(b)] for a, b in sorted(expected)],
|
||||
'elf_function_symbol_membership': [
|
||||
{'from': hex(at), 'entry': hex(entry), 'name': name}
|
||||
for at, (entry, name) in sorted(owners.items())],
|
||||
'selected_passing_register': 'rdx', 'selected_plt_calls': calls,
|
||||
'conditional_message_selection': {'mask_at': '0x42158', 'mask': 1, 'cmovne_at': '0x4216a',
|
||||
'zero_keeps': '0x11b376', 'nonzero_selects': '0x11b369'},
|
||||
'scope': 'Independent fixed-fixture file/GNU checks; not Kuna output, global reference completeness, target execution or live PLT binding.'}
|
||||
with output.open('x') as stream:
|
||||
json.dump(report, stream, indent=2)
|
||||
print(json.dumps(report))
|
||||
@@ -5,3 +5,6 @@ export const KUNA_FUNCTION_TEXT_BYTES = 16 * 1024 * 1024;
|
||||
export const KUNA_DIAGNOSTIC_BYTES = 8 * 1024 * 1024;
|
||||
|
||||
export const KUNA_STRING_MIN_LENGTH = 5;
|
||||
|
||||
/** Buffered xref source bytes also bound the materialized native rows. */
|
||||
export const KUNA_XREF_OUTPUT_BYTES = 16 * 1024 * 1024;
|
||||
|
||||
@@ -7,14 +7,20 @@ import { err, ok, type Result } from "../domain/result.js";
|
||||
import { compareKunaAddresses } from "./KunaAddress.js";
|
||||
import { parseKunaJson } from "./KunaJson.js";
|
||||
import { KunaJsonDocumentReader, parseKunaRow } from "./KunaJsonStream.js";
|
||||
import { KUNA_XREF_OUTPUT_BYTES } from "./KunaLimits.js";
|
||||
|
||||
/** The raw xref budget also bounds its materialized rows. */
|
||||
export const KUNA_XREF_OUTPUT_BYTES = 16 * 1024 * 1024;
|
||||
|
||||
const addressKeys = new Set(["address", "from_address", "to_address"]);
|
||||
const address = z.string().regex(/^0x(?:0|[1-9a-f][0-9a-f]{0,15})$/u);
|
||||
const count = z.number().int().min(0).max(Number.MAX_SAFE_INTEGER);
|
||||
const kind = z.enum(["call", "jump", "data", "read", "write"]);
|
||||
import {
|
||||
kunaReferenceAddressKeys as addressKeys,
|
||||
kunaReferenceAddressSchema as address,
|
||||
kunaReferenceCountSchema as count,
|
||||
kunaReferenceKindSchema as kind,
|
||||
kunaReferenceFunctionSchema as xrefFunction,
|
||||
kunaReferenceRowSchema as xrefRow,
|
||||
validateKunaReferenceRow,
|
||||
kunaReferenceFact as referenceFact,
|
||||
type KunaReferenceFact,
|
||||
} from "./KunaReferenceRows.js";
|
||||
export type { KunaReferenceFact } from "./KunaReferenceRows.js";
|
||||
|
||||
const graphHeader = z.object({
|
||||
schemaVersion: z.literal(4),
|
||||
@@ -206,23 +212,6 @@ export class KunaCallGraphReader extends KunaJsonDocumentReader<KunaCallGraph> {
|
||||
}
|
||||
}
|
||||
|
||||
const xrefFunction = z.object({
|
||||
name: z.string().min(1),
|
||||
address,
|
||||
address_hex: address,
|
||||
});
|
||||
const xrefRow = z.object({
|
||||
address,
|
||||
address_hex: address,
|
||||
kind,
|
||||
from_address: address,
|
||||
from_address_hex: address,
|
||||
to_address: address,
|
||||
to_address_hex: address,
|
||||
from_function: xrefFunction.nullable(),
|
||||
to_function: xrefFunction.nullable(),
|
||||
instruction: z.string(),
|
||||
});
|
||||
const xrefsDocument = z.object({
|
||||
binary: z.string(),
|
||||
direction: z.enum(["to", "from"]),
|
||||
@@ -242,20 +231,7 @@ const parseReferenceRow = (
|
||||
aliases: ReadonlySet<string>,
|
||||
query: KunaReferenceQuery,
|
||||
) => {
|
||||
const row = xrefRow.parse(raw);
|
||||
if (
|
||||
row.address !== row.address_hex ||
|
||||
row.address !==
|
||||
(query.direction === "to" ? row.from_address : row.to_address) ||
|
||||
row.from_address !== row.from_address_hex ||
|
||||
row.to_address !== row.to_address_hex
|
||||
)
|
||||
throw new Error(
|
||||
"xref endpoints disagree with numeric or directional fields",
|
||||
);
|
||||
for (const fn of [row.from_function, row.to_function])
|
||||
if (fn !== null && fn.address !== fn.address_hex)
|
||||
throw new Error("xref function address disagrees with its hex field");
|
||||
const row = validateKunaReferenceRow(xrefRow.parse(raw), query.direction);
|
||||
if (query.direction === "to" && !aliases.has(row.to_address))
|
||||
throw new Error(
|
||||
"xref row targets neither the requested address nor an advertised alias",
|
||||
@@ -280,22 +256,6 @@ export interface KunaReferenceAddresses {
|
||||
}
|
||||
|
||||
/** Experimental fact projection, owned by the native protocol parser. */
|
||||
export type KunaReferenceFact = {
|
||||
readonly source_row: number;
|
||||
readonly from: string;
|
||||
readonly to: string;
|
||||
readonly kind: z.infer<typeof kind>;
|
||||
readonly fromFunction: {
|
||||
readonly address: string;
|
||||
readonly name: string;
|
||||
} | null;
|
||||
readonly toFunction: {
|
||||
readonly address: string;
|
||||
readonly name: string;
|
||||
} | null;
|
||||
readonly instruction: string;
|
||||
};
|
||||
|
||||
export type KunaReferenceQuery = (
|
||||
| {
|
||||
readonly direction: "to";
|
||||
@@ -328,31 +288,6 @@ const referenceSelection = (
|
||||
return row.from_function.address === query.address ? "selected" : "scope";
|
||||
};
|
||||
|
||||
const referenceFact = (
|
||||
row: z.infer<typeof xrefRow>,
|
||||
source_row: number,
|
||||
): KunaReferenceFact => ({
|
||||
source_row,
|
||||
from: row.from_address,
|
||||
to: row.to_address,
|
||||
kind: row.kind,
|
||||
fromFunction:
|
||||
row.from_function === null
|
||||
? null
|
||||
: {
|
||||
address: row.from_function.address,
|
||||
name: row.from_function.name,
|
||||
},
|
||||
toFunction:
|
||||
row.to_function === null
|
||||
? null
|
||||
: {
|
||||
address: row.to_function.address,
|
||||
name: row.to_function.name,
|
||||
},
|
||||
instruction: row.instruction,
|
||||
});
|
||||
|
||||
/** One native-format boundary, with caller-selected endpoint/function scope. */
|
||||
const parseReferenceDocument = (
|
||||
text: string,
|
||||
|
||||
@@ -10,13 +10,13 @@ import { ProviderAdapterError } from "../domain/providerAdapterError.js";
|
||||
import { err, ok, type Result } from "../domain/result.js";
|
||||
import type { KunaInstallation } from "./KunaInstallation.js";
|
||||
import {
|
||||
KUNA_XREF_OUTPUT_BYTES,
|
||||
KunaCallGraphReader,
|
||||
parseKunaIncomingReferences,
|
||||
type KunaCallGraph,
|
||||
type KunaGraphNode,
|
||||
type KunaReferenceAddresses,
|
||||
} from "./KunaReferenceFiles.js";
|
||||
import { KUNA_XREF_OUTPUT_BYTES } from "./KunaLimits.js";
|
||||
import { KUNA_PROVIDER_ID } from "./KunaRelease.js";
|
||||
import type { KunaPendingQuery, KunaQueryWork } from "./KunaQueryWork.js";
|
||||
import { kunaUnavailable } from "./KunaUnavailable.js";
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
// Paged native range batches: query identity and coverage stay with their owner.
|
||||
import { createHash } from "node:crypto";
|
||||
import { z } from "zod";
|
||||
import { err, ok } from "../domain/result.js";
|
||||
import { parseKunaJson } from "./KunaJson.js";
|
||||
import { KUNA_XREF_OUTPUT_BYTES } from "./KunaLimits.js";
|
||||
import {
|
||||
kunaReferenceAddressKeys,
|
||||
kunaReferenceAddressSchema as address,
|
||||
kunaReferenceCountSchema as count,
|
||||
kunaReferenceKindSchema,
|
||||
kunaReferenceRowSchema,
|
||||
validateKunaReferenceRow,
|
||||
kunaReferenceFact,
|
||||
type KunaReferenceFact,
|
||||
} from "./KunaReferenceRows.js";
|
||||
|
||||
const span = z.strictObject({ start: address, end_inclusive: address });
|
||||
const metadata = span.extend({
|
||||
index: count,
|
||||
offset: count,
|
||||
limit: count.min(1).max(128),
|
||||
total: count,
|
||||
count,
|
||||
next_offset: count.nullable(),
|
||||
});
|
||||
const wire = z.object({
|
||||
schema_version: z.literal(1),
|
||||
binary: z.string(),
|
||||
direction: z.literal("to"),
|
||||
ranges: z.array(metadata).min(1).max(16),
|
||||
count,
|
||||
xrefs: z.array(kunaReferenceRowSchema.extend({ range_index: count })),
|
||||
});
|
||||
export interface KunaReferenceRangeQuery {
|
||||
readonly ranges: readonly z.infer<typeof span>[];
|
||||
readonly offset: number;
|
||||
readonly limit: number;
|
||||
readonly kinds?: readonly z.infer<typeof kunaReferenceKindSchema>[];
|
||||
}
|
||||
export interface KunaReferenceRangePage {
|
||||
readonly range: z.infer<typeof span>;
|
||||
readonly range_index: number;
|
||||
readonly total: number;
|
||||
readonly next_offset: number | null;
|
||||
readonly facts: readonly KunaReferenceFact[];
|
||||
}
|
||||
|
||||
type NativeRangeDocument = z.infer<typeof wire>;
|
||||
type ParsedPage = {
|
||||
metadata: z.infer<typeof metadata>;
|
||||
facts: KunaReferenceFact[];
|
||||
keys: Set<string>;
|
||||
};
|
||||
const readPages = (
|
||||
entries: NativeRangeDocument["ranges"],
|
||||
query: KunaReferenceRangeQuery,
|
||||
) => {
|
||||
const pages = new Map<number, ParsedPage>();
|
||||
for (const meta of entries) {
|
||||
const expected = query.ranges[meta.index];
|
||||
if (
|
||||
pages.has(meta.index) ||
|
||||
expected === undefined ||
|
||||
meta.start !== expected.start ||
|
||||
meta.end_inclusive !== expected.end_inclusive ||
|
||||
BigInt(meta.start) > BigInt(meta.end_inclusive) ||
|
||||
meta.offset !== query.offset ||
|
||||
meta.limit !== query.limit
|
||||
)
|
||||
throw new Error("xref range metadata is not bound to the selected query");
|
||||
const selected = Math.min(
|
||||
meta.limit,
|
||||
Math.max(0, meta.total - meta.offset),
|
||||
);
|
||||
if (
|
||||
meta.count !== selected ||
|
||||
meta.next_offset !==
|
||||
(meta.offset + selected < meta.total ? meta.offset + selected : null)
|
||||
)
|
||||
throw new Error("xref range pagination contradicts examined coverage");
|
||||
pages.set(meta.index, { metadata: meta, facts: [], keys: new Set() });
|
||||
}
|
||||
return pages;
|
||||
};
|
||||
const readRows = (
|
||||
rows: NativeRangeDocument["xrefs"],
|
||||
pages: Map<number, ParsedPage>,
|
||||
kinds: KunaReferenceRangeQuery["kinds"],
|
||||
) => {
|
||||
for (const [source_row, raw] of rows.entries()) {
|
||||
const row = validateKunaReferenceRow(raw, "to"),
|
||||
page = pages.get(row.range_index);
|
||||
if (
|
||||
page === undefined ||
|
||||
BigInt(row.to_address) < BigInt(page.metadata.start) ||
|
||||
BigInt(row.to_address) > BigInt(page.metadata.end_inclusive) ||
|
||||
(kinds !== undefined && !kinds.includes(row.kind))
|
||||
)
|
||||
throw new Error("xref row violates selected range or kind scope");
|
||||
const key = JSON.stringify([row.from_address, row.to_address, row.kind]);
|
||||
if (page.keys.has(key))
|
||||
throw new Error("xref range page repeats an occurrence");
|
||||
page.keys.add(key);
|
||||
page.facts.push(kunaReferenceFact(row, source_row));
|
||||
}
|
||||
};
|
||||
|
||||
export const parseKunaReferenceRanges = (
|
||||
text: string,
|
||||
input: string,
|
||||
query: KunaReferenceRangeQuery,
|
||||
) => {
|
||||
try {
|
||||
const document = wire.parse(
|
||||
parseKunaJson(text, KUNA_XREF_OUTPUT_BYTES, kunaReferenceAddressKeys),
|
||||
);
|
||||
if (
|
||||
document.binary !== input ||
|
||||
document.count !== document.xrefs.length ||
|
||||
document.ranges.length !== query.ranges.length
|
||||
)
|
||||
throw new Error(
|
||||
"xref range document has foreign input or inconsistent counts",
|
||||
);
|
||||
const pages = readPages(document.ranges, query);
|
||||
readRows(document.xrefs, pages, query.kinds);
|
||||
const results: KunaReferenceRangePage[] = query.ranges.map(
|
||||
(range, index) => {
|
||||
const page = pages.get(index);
|
||||
if (page === undefined || page.facts.length !== page.metadata.count)
|
||||
throw new Error("xref range page is missing declared observations");
|
||||
return {
|
||||
range: { ...range },
|
||||
range_index: index,
|
||||
total: page.metadata.total,
|
||||
next_offset: page.metadata.next_offset,
|
||||
facts: page.facts,
|
||||
};
|
||||
},
|
||||
);
|
||||
return ok({
|
||||
results,
|
||||
bytes: Buffer.byteLength(text),
|
||||
sha256: createHash("sha256").update(text).digest("hex"),
|
||||
});
|
||||
} catch (cause: unknown) {
|
||||
return err(cause instanceof Error ? cause.message : String(cause));
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,102 @@
|
||||
// Provider wire endpoints and normalized facts have one interpretation.
|
||||
import { z } from "zod";
|
||||
export const kunaReferenceAddressKeys = new Set([
|
||||
"address",
|
||||
"from_address",
|
||||
"to_address",
|
||||
]);
|
||||
export const kunaReferenceAddressSchema = z
|
||||
.string()
|
||||
.regex(/^0x(?:0|[1-9a-f][0-9a-f]{0,15})$/u);
|
||||
export const kunaReferenceCountSchema = z
|
||||
.number()
|
||||
.int()
|
||||
.min(0)
|
||||
.max(Number.MAX_SAFE_INTEGER);
|
||||
export const kunaReferenceKindSchema = z.enum([
|
||||
"call",
|
||||
"jump",
|
||||
"data",
|
||||
"read",
|
||||
"write",
|
||||
]);
|
||||
const address = kunaReferenceAddressSchema,
|
||||
kind = kunaReferenceKindSchema;
|
||||
export const kunaReferenceFunctionSchema = z.object({
|
||||
name: z.string().min(1),
|
||||
address,
|
||||
address_hex: address,
|
||||
});
|
||||
export const kunaReferenceRowSchema = z.object({
|
||||
address,
|
||||
address_hex: address,
|
||||
kind,
|
||||
from_address: address,
|
||||
from_address_hex: address,
|
||||
to_address: address,
|
||||
to_address_hex: address,
|
||||
from_function: kunaReferenceFunctionSchema.nullable(),
|
||||
to_function: kunaReferenceFunctionSchema.nullable(),
|
||||
instruction: z.string(),
|
||||
});
|
||||
export const validateKunaReferenceRow = <
|
||||
T extends z.infer<typeof kunaReferenceRowSchema>,
|
||||
>(
|
||||
row: T,
|
||||
direction: "to" | "from",
|
||||
): T => {
|
||||
if (
|
||||
row.address !== row.address_hex ||
|
||||
row.address !== (direction === "to" ? row.from_address : row.to_address) ||
|
||||
row.from_address !== row.from_address_hex ||
|
||||
row.to_address !== row.to_address_hex
|
||||
)
|
||||
throw new Error(
|
||||
"xref endpoints disagree with numeric or directional fields",
|
||||
);
|
||||
for (const fn of [row.from_function, row.to_function])
|
||||
if (fn !== null && fn.address !== fn.address_hex)
|
||||
throw new Error("xref function address disagrees with its hex field");
|
||||
return row;
|
||||
};
|
||||
|
||||
export type KunaReferenceFact = {
|
||||
readonly source_row: number;
|
||||
readonly from: string;
|
||||
readonly to: string;
|
||||
readonly kind: z.infer<typeof kind>;
|
||||
readonly fromFunction: {
|
||||
readonly address: string;
|
||||
readonly name: string;
|
||||
} | null;
|
||||
readonly toFunction: {
|
||||
readonly address: string;
|
||||
readonly name: string;
|
||||
} | null;
|
||||
readonly instruction: string;
|
||||
};
|
||||
|
||||
export const kunaReferenceFact = (
|
||||
row: z.infer<typeof kunaReferenceRowSchema>,
|
||||
source_row: number,
|
||||
): KunaReferenceFact => ({
|
||||
source_row,
|
||||
from: row.from_address,
|
||||
to: row.to_address,
|
||||
kind: row.kind,
|
||||
fromFunction:
|
||||
row.from_function === null
|
||||
? null
|
||||
: {
|
||||
address: row.from_function.address,
|
||||
name: row.from_function.name,
|
||||
},
|
||||
toFunction:
|
||||
row.to_function === null
|
||||
? null
|
||||
: {
|
||||
address: row.to_function.address,
|
||||
name: row.to_function.name,
|
||||
},
|
||||
instruction: row.instruction,
|
||||
});
|
||||
@@ -0,0 +1,181 @@
|
||||
import { expect, it } from "vitest";
|
||||
import { parseKunaReferenceRanges } from "../../../../src/kuna/KunaReferenceRanges.js";
|
||||
|
||||
const input = "/snapshot/program";
|
||||
const maximum = "0xffffffffffffffff";
|
||||
const query = {
|
||||
ranges: [
|
||||
{ start: "0x20000000000001", end_inclusive: maximum },
|
||||
{ start: maximum, end_inclusive: maximum },
|
||||
],
|
||||
offset: 0,
|
||||
limit: 2,
|
||||
kinds: ["data"] as const,
|
||||
};
|
||||
const encoded = (value: unknown) =>
|
||||
JSON.stringify(value).replace(/"@(\d+)"/gu, "$1");
|
||||
const row = (range_index: number) => ({
|
||||
range_index,
|
||||
address: "@9007199254740995",
|
||||
address_hex: "0x20000000000003",
|
||||
kind: "data",
|
||||
from_address: "@9007199254740995",
|
||||
from_address_hex: "0x20000000000003",
|
||||
to_address: "@18446744073709551615",
|
||||
to_address_hex: maximum,
|
||||
from_function: {
|
||||
name: "caller 中文",
|
||||
address: "@9007199254740992",
|
||||
address_hex: "0x20000000000000",
|
||||
},
|
||||
to_function: null,
|
||||
instruction: "LEA RDX,[target]",
|
||||
});
|
||||
const document = () => ({
|
||||
schema_version: 1,
|
||||
binary: input,
|
||||
direction: "to",
|
||||
count: 2,
|
||||
ranges: query.ranges
|
||||
.map((range, index) => ({
|
||||
...range,
|
||||
index,
|
||||
offset: 0,
|
||||
limit: 2,
|
||||
total: 1,
|
||||
count: 1,
|
||||
next_offset: null as number | null,
|
||||
}))
|
||||
.reverse(),
|
||||
xrefs: [row(1), row(0)],
|
||||
});
|
||||
|
||||
it("binds reordered metadata by named range index, preserves u64 and original source rows", () => {
|
||||
const result = parseKunaReferenceRanges(encoded(document()), input, query);
|
||||
expect(result.ok).toBe(true);
|
||||
if (!result.ok) return;
|
||||
expect(
|
||||
result.value.results.map((p) => [
|
||||
p.range_index,
|
||||
p.range,
|
||||
p.facts[0]?.source_row,
|
||||
p.total,
|
||||
]),
|
||||
).toEqual([
|
||||
[0, query.ranges[0], 1, 1],
|
||||
[1, query.ranges[1], 0, 1],
|
||||
]);
|
||||
expect(result.value.results[0]?.facts[0]).toMatchObject({
|
||||
from: "0x20000000000003",
|
||||
to: maximum,
|
||||
kind: "data",
|
||||
fromFunction: { name: "caller 中文" },
|
||||
toFunction: null,
|
||||
});
|
||||
expect(result.value.results[0]?.range).not.toBe(query.ranges[0]);
|
||||
});
|
||||
|
||||
it("rejects foreign scope, dangling or repeated queries, malformed endpoints and inconsistent paging", () => {
|
||||
for (const mutate of [
|
||||
(v: ReturnType<typeof document>) => {
|
||||
v.binary = "/other";
|
||||
},
|
||||
(v: ReturnType<typeof document>) => {
|
||||
v.count = 3;
|
||||
},
|
||||
(v: ReturnType<typeof document>) => {
|
||||
v.ranges[0]!.index = 0;
|
||||
},
|
||||
(v: ReturnType<typeof document>) => {
|
||||
v.ranges[0]!.end_inclusive = "0x1";
|
||||
},
|
||||
(v: ReturnType<typeof document>) => {
|
||||
v.ranges[0]!.offset = 1;
|
||||
},
|
||||
(v: ReturnType<typeof document>) => {
|
||||
v.ranges[0]!.next_offset = 1;
|
||||
},
|
||||
(v: ReturnType<typeof document>) => {
|
||||
v.ranges[0]!.total = 2;
|
||||
},
|
||||
(v: ReturnType<typeof document>) => {
|
||||
v.xrefs[0]!.range_index = 7;
|
||||
},
|
||||
(v: ReturnType<typeof document>) => {
|
||||
v.xrefs[0]!.kind = "call";
|
||||
},
|
||||
(v: ReturnType<typeof document>) => {
|
||||
v.xrefs[0]!.to_address = "@1";
|
||||
v.xrefs[0]!.to_address_hex = "0x1";
|
||||
},
|
||||
(v: ReturnType<typeof document>) => {
|
||||
v.xrefs[0]!.address = "@1";
|
||||
},
|
||||
(v: ReturnType<typeof document>) => {
|
||||
v.xrefs[0]!.from_function.address = "@1";
|
||||
},
|
||||
]) {
|
||||
const value = document();
|
||||
mutate(value);
|
||||
expect(parseKunaReferenceRanges(encoded(value), input, query).ok).toBe(
|
||||
false,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it("preserves overlap between queries but rejects a duplicated occurrence within one page", () => {
|
||||
const value = document();
|
||||
value.count = 3;
|
||||
value.xrefs.push(row(0));
|
||||
const first = value.ranges.find((r) => r.index === 0)!;
|
||||
first.total = 2;
|
||||
first.count = 2;
|
||||
const result = parseKunaReferenceRanges(encoded(value), input, query);
|
||||
expect(result).toMatchObject({
|
||||
ok: false,
|
||||
error: expect.stringContaining("repeats an occurrence"),
|
||||
});
|
||||
});
|
||||
|
||||
it("distinguishes a complete page from an empty out-of-range page with observed matches", () => {
|
||||
const one = {
|
||||
ranges: [query.ranges[1]!],
|
||||
offset: 1,
|
||||
limit: 1,
|
||||
kinds: query.kinds,
|
||||
};
|
||||
const value = {
|
||||
schema_version: 1,
|
||||
binary: input,
|
||||
direction: "to",
|
||||
count: 1,
|
||||
ranges: [
|
||||
{
|
||||
...one.ranges[0],
|
||||
index: 0,
|
||||
offset: 1,
|
||||
limit: 1,
|
||||
total: 3,
|
||||
count: 1,
|
||||
next_offset: 2,
|
||||
},
|
||||
],
|
||||
xrefs: [row(0)],
|
||||
};
|
||||
expect(parseKunaReferenceRanges(encoded(value), input, one)).toMatchObject({
|
||||
ok: true,
|
||||
value: { results: [{ total: 3, next_offset: 2 }] },
|
||||
});
|
||||
const empty = {
|
||||
...value,
|
||||
count: 0,
|
||||
xrefs: [],
|
||||
ranges: [{ ...value.ranges[0], offset: 4, count: 0, next_offset: null }],
|
||||
};
|
||||
expect(
|
||||
parseKunaReferenceRanges(encoded(empty), input, { ...one, offset: 4 }),
|
||||
).toMatchObject({
|
||||
ok: true,
|
||||
value: { results: [{ total: 3, next_offset: null, facts: [] }] },
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user