Files
rea/tests/boundary/providers/kuna/kunaStringInventory.test.ts
T
Ysopwnable c6b62754b0 perf(kuna): reuse sorted string inventory for exact lookups
Reuse the validated string inventory's numeric address order for O(log n + matches) exact reads, preserving every encoding at the selected address without a second lookup index. Keep streaming validation, session cache lifetime, full listings, provenance and caller choices.

Validation: complete latest-head CI passed. Original real Kuna 1.833 Linux correctness and warm-lookup measurements remain pinned to their recorded feature revisions in the PR. The latest local 300 ms export-deadline observation and unchanged 31-test provider recheck are explicitly documented there; no assertion or timeout was weakened.

Refs #4695.
2026-10-17 07:04:16 +00:00

327 lines
10 KiB
TypeScript

import { createHash } from "node:crypto";
import { Readable } from "node:stream";
import { pipeline } from "node:stream/promises";
import * as v8 from "node:v8";
import { expect, it, vi } from "vitest";
import {
KunaStringInventoryReader,
kunaStringsAtAddress,
} from "../../../../src/kuna/KunaStringInventory.js";
vi.mock("node:v8", { spy: true });
const input = "/private/program";
const row = {
address: "@9007199254740993",
address_hex: "0x20000000000001",
text: "a\nΔelta",
length: 7,
byte_length: 9,
nul_terminated: true,
encoding: "utf8",
section: ".rodata",
xrefs_count: 0,
functions: [],
};
const document = () => ({
binary: input,
encoding: "all",
min_length: 5,
filter: null,
section: null,
termination: "any",
scanned: "sections",
xrefs: false,
count: 1,
strings: [row],
});
const wire = (value: unknown) =>
JSON.stringify(value).replace(/"@(\d+)"/gu, "$1");
/** Stream `text` through a reader in `chunkBytes` pieces, as Kuna's stdout arrives. */
const read = async (text: string, chunkBytes = 64 * 1024) => {
const bytes = Buffer.from(text);
const chunks: Buffer[] = [];
for (let offset = 0; offset < bytes.byteLength; offset += chunkBytes)
chunks.push(bytes.subarray(offset, offset + chunkBytes));
const reader = new KunaStringInventoryReader();
await pipeline(Readable.from(chunks), reader);
return { reader, result: reader.finish(input) };
};
it("preserves exact u64 string identity, multiline text and typed source facts", async () => {
const text = wire(document());
const { result } = await read(text, 1);
expect(result).toMatchObject({
ok: true,
value: {
sha256: createHash("sha256").update(text).digest("hex"),
bytes: Buffer.byteLength(text),
scanned: "sections",
items: [
{
address: "0x20000000000001",
value: "a\nΔelta",
string: {
encoding: "utf8",
encoding_status: "observed",
termination: "present_or_not_required",
byte_length: 9,
},
},
],
},
});
});
it("skips members it does not interpret without retaining them", async () => {
const { result } = await read(
wire({
...document(),
padding: "x".repeat(1024),
notes: [{ a: [1] }],
strings: [{ ...row, score: 1, origin: { tool: [1] } }],
}).replace('"functions":[]', '"functions":[],"__proto__":{"text":1}'),
);
expect(result).toMatchObject({ ok: true, value: { items: [{}] } });
});
it.each([
["input", { binary: "/other/program" }],
["count", { count: 2 }],
["filter", { filter: "narrowed" }],
["xrefs", { xrefs: true }],
["encoding", { encoding: "ascii" }],
["scan scope", { scanned: "unknown" }],
["header shape", { section: [] }],
["missing rows", { strings: undefined }],
[
"address identity",
{ strings: [{ ...row, address_hex: "0x20000000000000" }] },
],
["u64 overflow", { strings: [{ ...row, address: "@18446744073709551616" }] }],
["textual address", { strings: [{ ...row, address: "0x20000000000001" }] }],
["partial row", { strings: [{ ...row, byte_length: null }] }],
["unexpected references", { strings: [{ ...row, xrefs_count: 1 }] }],
[
"referencing functions",
{ strings: [{ ...row, functions: [{ name: "main" }] }] },
],
["duplicate identity", { count: 2, strings: [row, row] }],
[
"nonadjacent duplicate identity",
{
count: 3,
strings: [row, { ...row, address: "@16", address_hex: "0x10" }, row],
},
],
])(
"rejects inconsistent %s before publishing any strings",
async (_label, changes) => {
const { result } = await read(wire({ ...document(), ...changes }));
expect(result).toMatchObject({
ok: false,
error: { _tag: "AnalysisOutputError", operation: "kuna:strings" },
});
},
);
it("indexes canonical u64 addresses and keeps each encoding at the same address", async () => {
const entry = (
address: bigint,
encoding: "ascii" | "utf8" | "utf16" = "ascii",
) => ({
...row,
address: `@${address}`,
address_hex: `0x${address.toString(16)}`,
text: "hello",
length: 5,
byte_length: encoding === "utf16" ? 12 : 6,
encoding,
});
const rows = [
entry(0xffffffffffffffffn),
entry(16n, "utf16"),
entry(15n),
entry(16n),
entry(0x20000000000001n, "utf8"),
entry(0n),
entry(16n, "utf8"),
entry(17n),
];
const { result } = await read(
wire({ ...document(), count: rows.length, strings: rows }),
3,
);
expect(result.ok).toBe(true);
if (!result.ok) throw result.error;
expect(result.value.items.map(({ address }) => address)).toEqual([
"0x0",
"0xf",
"0x10",
"0x10",
"0x10",
"0x11",
"0x20000000000001",
"0xffffffffffffffff",
]);
for (const address of [
"0x0",
"0x1",
"0xe",
"0xf",
"0x10",
"0x11",
"0x12",
"0x20000000000000",
"0x20000000000001",
"0x20000000000002",
"0xffffffffffffffff",
])
expect(kunaStringsAtAddress(result.value, address)).toEqual(
result.value.items.filter((item) => item.address === address),
);
expect(
kunaStringsAtAddress(result.value, "0x10").map(
(item) => item.string?.encoding,
),
).toEqual(["ascii", "utf16", "utf8"]);
});
it("looks up an empty validated inventory", async () => {
const { result } = await read(wire({ ...document(), count: 0, strings: [] }));
expect(result.ok).toBe(true);
if (!result.ok) throw result.error;
expect(kunaStringsAtAddress(result.value, "0x0")).toEqual([]);
});
it.each([
["truncated", wire(document()).slice(0, -2)],
["trailing", `${wire(document())} {}`],
["array root", "[]"],
["empty", ""],
])("rejects a %s document", async (_label, text) => {
expect((await read(text)).result.ok).toBe(false);
});
it("rejects invalid UTF-8 instead of substituting characters", async () => {
const reader = new KunaStringInventoryReader();
const text = Buffer.from(wire(document()));
const at = text.indexOf("Δ");
text[at] = 0xff;
await pipeline(Readable.from([text]), reader);
expect(reader.finish(input).ok).toBe(false);
});
it("signals the first contradiction while Kuna is still writing", async () => {
const reader = new KunaStringInventoryReader();
const rows = [{ ...row, byte_length: 1 }, row];
const text = wire({ ...document(), count: 2, strings: rows });
reader.write(Buffer.from(text.slice(0, text.lastIndexOf("{"))));
await new Promise((resolve) => setImmediate(resolve));
expect(reader.rejected.aborted).toBe(true);
expect(reader.rejection).toMatchObject({ _tag: "AnalysisOutputError" });
reader.end(Buffer.from(text.slice(text.lastIndexOf("{"))));
await new Promise((resolve) => reader.once("finish", resolve));
expect(reader.finish(input)).toMatchObject({
ok: false,
error: reader.rejection,
});
});
it("refuses retention beyond live heap headroom and stops the scan", async () => {
const heap = v8.getHeapStatistics();
// The first retained row samples headroom.
vi.mocked(v8.getHeapStatistics).mockReturnValueOnce({
...heap,
total_available_size: 1024,
});
const { reader, result } = await read(wire(document()));
expect(reader.rejected.aborted).toBe(true);
expect(result).toMatchObject({
ok: false,
error: {
_tag: "AnalysisResourceConstraintError",
operation: "kuna:strings",
resource: "memory",
reportedLimits: { retained_rows: 0, available_heap_bytes: 1024 },
},
});
});
it("refuses a kept string while it is assembled, before Kuna finishes it", async () => {
const heap = v8.getHeapStatistics();
const text = wire(document());
const opening = text.slice(0, text.indexOf("a\\nΔelta"));
const reader = new KunaStringInventoryReader();
reader.write(Buffer.from(opening));
vi.mocked(v8.getHeapStatistics).mockReturnValueOnce({
...heap,
total_available_size: 1024,
});
// One sampling interval of text, still inside one unterminated string.
reader.write(Buffer.from("x".repeat(64 * 1024)));
await new Promise((resolve) => setImmediate(resolve));
expect(reader.rejection).toMatchObject({
_tag: "AnalysisResourceConstraintError",
resource: "memory",
});
reader.destroy();
});
it("bounds keys and numbers: overlong keys match no field, overlong kept numbers reject", async () => {
const longKey = `count${"x".repeat(1024 * 1024)}`;
const ignored = wire({ ...document(), [longKey]: "@1" + "0".repeat(4096) });
expect((await read(ignored, 4096)).result).toMatchObject({
ok: true,
value: { items: [{}] },
});
const kept = wire({ ...document(), count: "@1" + "0".repeat(64) });
expect((await read(kept)).result).toMatchObject({
ok: false,
error: { reason: "count is not a number Kuna writes" },
});
});
it("drops ignored strings unassembled, without sampling headroom", async () => {
const sampled = vi.mocked(v8.getHeapStatistics).mock.calls.length;
const { result } = await read(
wire({ ...document(), padding: "x".repeat(4 * 1024 * 1024) }),
);
expect(result.ok).toBe(true);
// Only the one retained row's admission is sampled.
expect(vi.mocked(v8.getHeapStatistics).mock.calls.length - sampled).toBe(1);
});
it.each([
{ encoding: "utf8", text: "😀😀😀😀😀", length: 5, byte_length: 21 },
{ encoding: "utf16", text: "café!", length: 5, byte_length: 12 },
])("counts $encoding source units without losing text", async (facts) => {
const value = { ...document(), strings: [{ ...row, ...facts }] };
expect((await read(wire(value), 3)).result).toMatchObject({
ok: true,
value: {
items: [
{ value: facts.text, string: { byte_length: facts.byte_length } },
],
},
});
});
it.each([
{ byte_length: 0 },
{ length: 6 },
{ nul_terminated: false },
{ encoding: "ascii" },
{ encoding: "utf16", text: "hello", length: 5, byte_length: 11 },
{ encoding: "utf8", text: "hi", length: 2, byte_length: 3 },
{ encoding: "utf8", text: "hello\u0000", length: 6, byte_length: 7 },
{ encoding: "utf8", text: "hello\ud800", length: 6, byte_length: 9 },
{ encoding: "utf16", text: "Δelta", length: 5, byte_length: 12 },
])("rejects conflicting encoded string facts: %j", async (facts) => {
const value = { ...document(), strings: [{ ...row, ...facts }] };
expect((await read(wire(value))).result.ok).toBe(false);
});