test: strengthen independent oracles and consolidate boundary coverage

Require delivered facts, exact cancellation and malformed-input boundaries, and independent metadata expectations. Transfer unique assertions into stronger keepers before removing duplicate cases and overwritten fixture writes; preserve all consumed core fixture bytes.

Validation: 1,190 focused tests passed with 39 platform-dependent skips; 134 composition and conformance tests passed. Typecheck, lint, formatting, discovery and Knip passed. Independent preservation reviews and targeted regression controls passed.
This commit is contained in:
morluto committed 2026-11-10 17:03:59 -05:00
1 parent 31018cf037
commit dcee59f273
34 files changed
+408 -618

No files matched your search

+1
View File
@@ -24,6 +24,7 @@ export interface RealHopperFixtureTargets {
readonly manifestPath: string;
readonly primary: RealHopperFixtureTarget;
readonly secondary: RealHopperFixtureTarget;
readonly unicode?: RealHopperFixtureTarget;
readonly large: RealHopperFixtureTarget;
readonly oracle: RealHopperFixtureOracle;
readonly largeOracle: RealHopperLargeFixtureOracle;
+1 -2
View File
@@ -34,9 +34,8 @@ def note_header(content, note, **changes):
# Authored metadata carriers exercise actual parser projection through public
# inspection, independently of the host that produced the captured amd64 core.
# The named ARCHEXT/UNWIND carriers are written once below using numeric type IDs.
for name, machine, kind in [
("aarch64-archext", "EM_AARCH64", "PT_AARCH64_ARCHEXT"),
("aarch64-unwind", "EM_AARCH64", "PT_AARCH64_UNWIND"),
("aarch64-mte-opaque", "EM_AARCH64", 0x70000002),
("amd64-processor-opaque", "EM_X86_64", 0x70000001),
]:
+15 -14
View File
@@ -196,17 +196,6 @@ try {
assert.equal(value.threads.length, 1);
cases++;
}
for (const [name, kind, label] of [
["aarch64-archext", 0x70000000, "PT_AARCH64_ARCHEXT"],
["aarch64-unwind", 0x70000001, "PT_AARCH64_UNWIND"],
]) {
const value = await basic.inspect(mode, fixtures[name]);
assert.equal(value.architecture, "aarch64-little-endian");
assert.equal(value.segments[0]?.type_id, kind);
assert.equal(value.segments[0]?.type, label);
assert.equal(value.threads.length, 0);
cases++;
}
const noSignals = await basic.inspect(mode, fixtures["unknown-signals"]);
assert.equal(noSignals.signals.length, 0);
assert.equal(noSignals.threads.length, 2);
@@ -216,9 +205,19 @@ try {
assert.equal(noNotes.threads.length, 0);
assert.equal(noNotes.note_interpretation_completeness, "unknown");
cases++;
for (const [name, architecture, type] of [
["aarch64-archext", "aarch64-little-endian", "PT_AARCH64_ARCHEXT"],
["aarch64-unwind", "aarch64-little-endian", "PT_AARCH64_UNWIND"],
for (const [name, architecture, type, typeId] of [
[
"aarch64-archext",
"aarch64-little-endian",
"PT_AARCH64_ARCHEXT",
0x70000000,
],
[
"aarch64-unwind",
"aarch64-little-endian",
"PT_AARCH64_UNWIND",
0x70000001,
],
["aarch64-mte-opaque", "aarch64-little-endian", 0x70000002],
["amd64-processor-opaque", "x86_64-little-endian", 0x70000001],
]) {
@@ -226,6 +225,8 @@ try {
assert.equal(auxiliary.architecture, architecture);
assert.equal(auxiliary.segments.length, 1);
assert.equal(auxiliary.segments[0].type, type);
if (typeId !== undefined)
assert.equal(auxiliary.segments[0].type_id, typeId);
assert.equal(auxiliary.notes.length, 0);
assert.equal(auxiliary.threads.length, 0);
assert.equal(auxiliary.note_interpretation_completeness, "unknown");
@@ -1159,44 +1159,6 @@ describe("DeepSeek Harness alias collapse", () => {
});
describe("DeepSeek Harness alias targets", () => {
it("collapses an alias of the insert list", async () => {
const { client } = await harnessClient();
await writeFile(
client.configPath,
`- holder: &rows
- id: other-tool
name: other
config:
command: keep-me
- id: rea-mcp
name: "@deepseek-ai/dsh-mcp-client"
config:
serverName: rea
transport: stdio
command: stale-launcher
args: [mcp]
- insert: *rows
`,
);
expect(
(await configureClientConfiguration(client, {}, command)).status,
).toBe("configured");
const text = await readFile(client.configPath, "utf8");
expect(text).toContain("other-tool");
expect(text).not.toContain("*rows");
expect(text).toContain("stale-launcher");
const patches = parseYaml(text) as { insert?: { id?: string }[] }[];
expect(
patches.flatMap(({ insert }) =>
(insert ?? []).filter((entry) => entry.id === "rea-mcp"),
),
).toHaveLength(1);
expect(
(await configureClientConfiguration(client, {}, command)).status,
).toBe("unchanged");
});
it("keeps a Cordis tag when the config map is an alias", async () => {
const { client } = await harnessClient();
await writeFile(
@@ -1256,7 +1218,10 @@ describe("DeepSeek Harness alias identity", () => {
expect(
(await configureClientConfiguration(client, {}, command)).status,
).toBe("configured");
const patches = parseYaml(await readFile(client.configPath, "utf8")) as {
const text = await readFile(client.configPath, "utf8");
expect(text).not.toContain("*rows");
expect(text).toContain("stale-launcher");
const patches = parseYaml(text) as {
insert?: { id?: string; config?: { command?: string } }[];
}[];
const inserted = patches.flatMap((patch) => patch.insert ?? []);
@@ -125,7 +125,9 @@ describe("Webpack and Rspack runtime adapters", () => {
),
).toBe(false);
});
});
describe("esbuild and Vite runtime adapters", () => {
it("recovers esbuild wrapper modules and Vite preload dependencies", async () => {
const root = await esmRuntimeFixture();
const { graph } = await reconstructJavaScriptFixture(root);
@@ -142,14 +144,31 @@ describe("Webpack and Rspack runtime adapters", () => {
runtime: "esbuild-esm",
module_key: "src/core.js",
});
const dependencies = graph.edges.filter(
({ relation, properties }) =>
relation === "imports" &&
properties.kind === "dynamic-import" &&
["./feature.js", "./main.css"].includes(String(properties.specifier)),
);
expect(
graph.edges.filter(
({ relation, properties }) =>
relation === "imports" &&
properties.kind === "dynamic-import" &&
["./feature.js", "./main.css"].includes(String(properties.specifier)),
),
).toHaveLength(2);
dependencies.map(({ properties }) => properties.specifier).sort(),
).toEqual(["./feature.js", "./main.css"]);
expect(
dependencies.find(
({ properties }) => properties.specifier === "./feature.js",
)?.properties,
).toMatchObject({
resolution_status: "resolved",
resolved_path: "feature.js",
});
expect(
dependencies.find(
({ properties }) => properties.specifier === "./main.css",
)?.properties,
).toMatchObject({
resolution_status: "unavailable",
candidate_path: "main.css",
});
});
});
@@ -1195,6 +1195,25 @@ describe("JavaScript export return-shape projection", () => {
expect(result.coverage.status).toBe("complete-within-inputs");
expect(result.coverage.left_omitted_fields).toBe(0);
expect(result.coverage.right_omitted_fields).toBe(0);
const paths = [
"/a_type",
...Array.from({ length: 70 }, (_, i) => `/field${String(i)}`),
].sort();
expect(result.property_inventories.map(({ side }) => side).sort()).toEqual([
"left",
"right",
]);
for (const inventory of result.property_inventories) {
expect(inventory.paired).toBe(true);
expect([...inventory.properties].sort()).toEqual(paths);
}
expect(result.changes).toEqual([]);
expect(result.summary).toEqual({
added: 0,
removed: 0,
changed: 0,
unknown: 0,
});
});
});
@@ -104,38 +104,35 @@ it.each([
},
);
it.each(["npx", "bunx"] as const)(
"keeps the Windows %s launcher aligned through Codex TOML setup and removal",
async (runner) => {
const home = await createTestTempDirectory("rea-windows-codex-");
await mkdir(join(home, ".codex"));
const configPath = join(home, ".codex", "config.toml");
const original =
'\uFEFF# Keep this explanation.\nnotify = ["a", "b"]\nliteral = \'C:\\demo\\path\'\ndisabled_mcp_servers = ["rea"]\n\n[mcp_servers.other]\ncommand = "another-server"\n';
await writeFile(configPath, original);
const client = { name: "codex", configPath, format: "toml" } as const;
const command = currentClientRegistrationCommand({ platform: "win32" });
expect(
await configureClientConfiguration(client, {}, command),
).toMatchObject({ status: "configured" });
expect(
await readClientRegistrationStatuses(home, undefined, {
platform: "win32",
environment: {},
}),
).toContainEqual(
expect.objectContaining({ client: "codex", state: "aligned" }),
);
expect(await configureClientConfiguration(client, {}, command)).toEqual({
status: "unchanged",
});
expect(await readFile(`${configPath}.rea.backup`, "utf8")).toBe(original);
expect(await systemUninstallHost(home).removeClient(client)).toMatchObject({
status: "removed",
});
expect(await readFile(configPath, "utf8")).toBe(original);
},
);
it("keeps the Windows direct launcher aligned through Codex TOML setup and removal", async () => {
const home = await createTestTempDirectory("rea-windows-codex-");
await mkdir(join(home, ".codex"));
const configPath = join(home, ".codex", "config.toml");
const original =
'\uFEFF# Keep this explanation.\nnotify = ["a", "b"]\nliteral = \'C:\\demo\\path\'\ndisabled_mcp_servers = ["rea"]\n\n[mcp_servers.other]\ncommand = "another-server"\n';
await writeFile(configPath, original);
const client = { name: "codex", configPath, format: "toml" } as const;
const command = currentClientRegistrationCommand({ platform: "win32" });
expect(await configureClientConfiguration(client, {}, command)).toMatchObject(
{ status: "configured" },
);
expect(
await readClientRegistrationStatuses(home, undefined, {
platform: "win32",
environment: {},
}),
).toContainEqual(
expect.objectContaining({ client: "codex", state: "aligned" }),
);
expect(await configureClientConfiguration(client, {}, command)).toEqual({
status: "unchanged",
});
expect(await readFile(`${configPath}.rea.backup`, "utf8")).toBe(original);
expect(await systemUninstallHost(home).removeClient(client)).toMatchObject({
status: "removed",
});
expect(await readFile(configPath, "utf8")).toBe(original);
});
it("retains rea-named custom shell registrations without writing a backup", async () => {
const home = await createTestTempDirectory("rea-custom-shell-registration-");
@@ -70,8 +70,8 @@ it.each([false, true])(
cancelled: !cancelled.ok && (cancelled.error.error.cleanupPrimary ?? cancelled.error.error)._tag === 'AnalysisCancelledError',
exactReason: !cancelled.ok && (cancelled.error.error.cleanupPrimary ?? cancelled.error.error).cause === reason,
cleanupIncomplete: !cancelled.ok && cancelled.error.error.cleanupIncomplete,
parseStatus: facts.javascript?.parse_status, hasModule: facts.module !== null,
hasProjection: facts.projection !== null, reusable: worker.canReuse() };
parseStatus: facts.javascript?.parse_status, hasModule: facts.module != null,
hasProjection: facts.projection != null, exportedNames: facts.module?.moduleLinks.filter(link => link.kind === 'export').map(link => link.exportedName), projectedModule: facts.projection?.nodes.some(node => node.kind === 'module' && node.identity.module_path === 'cancelled.mjs'), reusable: worker.canReuse() };
} finally {
fs.promises.rm = nativeRm;
syncBuiltinESMExports();
@@ -101,6 +101,8 @@ it.each([false, true])(
parseStatus: "complete",
hasModule: true,
hasProjection: true,
exportedNames: ["observed"],
projectedModule: true,
reusable: false,
rootRemoved: true,
childExited: true,
@@ -117,6 +117,10 @@ it("returns the summary of complete Evidence retained for later views", async ()
normalized_result: {
kind: "page",
parent_evidence_id: completeEvidence.evidence_id,
items: expect.arrayContaining([
expect.objectContaining({ kind: "javascript-module", path: "main.js" }),
expect.objectContaining({ kind: "javascript-module", path: "util.js" }),
]),
},
});
});
@@ -171,7 +175,24 @@ it("returns the summary of retained binary layout Evidence", async () => {
});
expect(sections.isError).not.toBe(true);
expect(sections.structuredContent).toMatchObject({
normalized_result: { kind: "page", parent_evidence_id: parentId },
normalized_result: {
kind: "page",
parent_evidence_id: parentId,
items: [
expect.objectContaining({
index: 0,
name: expect.objectContaining({ display: ".text" }),
}),
expect.objectContaining({
index: 1,
name: expect.objectContaining({ display: ".text" }),
}),
expect.objectContaining({
index: 2,
name: expect.objectContaining({ display: ".data" }),
}),
],
},
});
});
@@ -318,6 +318,7 @@ describe("repeated partial export comparisons", () => {
const evidence = parseEvidence(response.structuredContent);
expect(evidence.normalized_result).toMatchObject({
changes: [],
summary: { added: 0, removed: 0, changed: 0, unknown: 0 },
coverage: { status: "partial" },
});
return evidence.evidence_id;
@@ -366,6 +367,13 @@ describe("repeated partial export comparisons", () => {
true,
);
const retained = session.exportEvidenceBundle();
for (const id of evidenceIds)
expect(retained.unknowns).toContainEqual(
expect.objectContaining({
domain: "javascript-export-shape",
supporting_evidence_ids: [id],
}),
);
await Promise.all(pairs.map((pair) => compare(pair)));
expect(session.exportEvidenceBundle()).toEqual(retained);
},
@@ -373,50 +381,6 @@ describe("repeated partial export comparisons", () => {
});
describe("source-produced export comparison inventories", () => {
it("records partial comparison coverage even when matching unknown projections produce no changes", async () => {
const source = `export default function make() {
const result = { kind: "record", count: 1 };
delete result.count;
return result;
}`;
const [left, right] = await Promise.all([
analyzeSourceEvidence(source),
analyzeSourceEvidence(source),
]);
const { client, session, close } = await createApplicationMcpHarness();
onTestFinished(close);
const response = await client.callTool({
name: "compare_javascript_export_shapes",
arguments: {
left,
right,
left_module_path: "parser.mjs",
left_export_name: "default",
right_module_path: "parser.mjs",
right_export_name: "default",
},
});
expect(response.isError).not.toBe(true);
expect(response.structuredContent).toMatchObject({
normalized_result: {
changes: [],
summary: { added: 0, removed: 0, changed: 0, unknown: 0 },
coverage: { status: "partial" },
},
});
const { evidence_id: evidenceId } = z
.object({ evidence_id: z.string() })
.parse(response.structuredContent);
expect(session.exportEvidenceBundle().unknowns).toEqual(
expect.arrayContaining([
expect.objectContaining({
domain: "javascript-export-shape",
supporting_evidence_ids: [evidenceId],
}),
]),
);
});
it("retains large source-produced export inventories through comparison schemas", async () => {
const fields = Array.from(
{ length: 64 },
@@ -196,6 +196,7 @@ const readBody = async (
) => {
let text = "";
let pages = 0;
const nextOffsets: (number | null)[] = [];
let offset: number | null = 0;
while (offset !== null) {
const page = await view(harness, parentId, {
@@ -208,9 +209,10 @@ const readBody = async (
offset = z
.looseObject({ next_offset: z.number().nullable() })
.parse(page.coverage).next_offset;
nextOffsets.push(offset);
pages++;
}
return { text, pages };
return { text, pages, nextOffsets };
};
/** A typed refusal whose issue names the rejected selection. */
@@ -408,7 +410,8 @@ it("pages one body exactly without splitting a Unicode character", async () => {
expect(JSON.stringify(evidence.normalized_result)).toContain("int main");
// A limit ending inside a surrogate pair stops before it, so pages rebuild the text.
const { text, pages } = await readBody(harness, parent, 3, 30);
const { text, pages, nextOffsets } = await readBody(harness, parent, 3, 31);
expect(nextOffsets[0]).toBe(30);
expect(text).toBe(EMOJI_BODY);
expect(pages).toBeGreaterThan(1);
expect(
@@ -135,7 +135,7 @@ it("reports a failed package list download with apt's own message", async () =>
});
it("downloads the package lists again when an update exits 0 with no list", async () => {
const { client } = await fakes({
const { client, rea } = await fakes({
env: { FAKE_APT_EMPTY: "1", FAKE_APT_IGNORE_ERROR_MODE: "1" },
});
const id = await startBuildComputer(client, BUILD_REQUEST);
@@ -148,12 +148,13 @@ it("downloads the package lists again when an update exits 0 with no list", asyn
expect(value.package_lists_error, `start ${String(attempt)}`).toContain(
"without a package list",
);
expect(await rea.notes("apt.jsonl")).toHaveLength(attempt);
await callOk(client, "stop_build_capture", { computer_id: id });
}
});
it("retries an empty apt update that Error-Mode turns into a failure", async () => {
const { client } = await fakes({ env: { FAKE_APT_EMPTY: "1" } });
const { client, rea } = await fakes({ env: { FAKE_APT_EMPTY: "1" } });
const id = await startBuildComputer(client, BUILD_REQUEST);
for (const attempt of [1, 2]) {
const value = toolContract("start_build_capture").outputSchema.parse(
@@ -164,12 +165,13 @@ it("retries an empty apt update that Error-Mode turns into a failure", async ()
expect(value.package_lists_error, `start ${String(attempt)}`).toContain(
"exited 100",
);
expect(await rea.notes("apt.jsonl")).toHaveLength(attempt);
await callOk(client, "stop_build_capture", { computer_id: id });
}
});
it("downloads the package lists again on the next start after an update that fetched some and failed", async () => {
const { client } = await fakes({
const { client, rea } = await fakes({
env: { FAKE_APT_EXIT: "100", FAKE_APT_PARTIAL: "1" },
});
const id = await startBuildComputer(client, BUILD_REQUEST);
@@ -182,6 +184,7 @@ it("downloads the package lists again on the next start after an update that fet
expect(value.package_lists_error, `start ${String(attempt)}`).toContain(
"exited 100",
);
expect(await rea.notes("apt.jsonl")).toHaveLength(attempt);
await callOk(client, "stop_build_capture", { computer_id: id });
}
});
+14 -105
View File
@@ -4,7 +4,6 @@ import { expect, it } from "vitest";
import { chmod, rm, writeFile } from "node:fs/promises";
import { createServer, type Socket } from "node:net";
import { join } from "node:path";
import { randomUUID } from "node:crypto";
import {
authenticatedServer,
@@ -19,101 +18,6 @@ import { createToolResultDelivery } from "../../../src/server/toolResult.js";
import { silentLogger } from "../../../src/logger.js";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
it.skipIf(process.platform === "win32")(
"closes cancelled discovery probes, stops queued probes and reuses the MCP connection",
async () => {
const directory = await createTestTempDirectory("rea-cutter-discovery-");
await chmod(directory, 0o700);
const sockets = new Set<Socket>();
let accepted = 0;
const peer = createServer((socket) => {
accepted += 1;
sockets.add(socket);
socket.on("error", () => undefined);
socket.once("close", () => sockets.delete(socket));
// Drain authentication bytes so a paused readable cannot hide peer EOF.
socket.resume();
});
const descriptors: string[] = [];
const service = new CutterBridgeService(
new CutterBridgeClient({ REA_CUTTER_BRIDGE_DIR: directory }),
);
const server = new McpServer({ name: "cutter-discovery", version: "1" });
registerCutterTools(
server,
service,
silentLogger,
createToolResultDelivery(undefined),
);
const client = new Client({ name: "cutter-discovery", version: "1" });
const [clientTransport, serverTransport] =
InMemoryTransport.createLinkedPair();
const controller = new AbortController();
try {
await new Promise<void>((resolve) =>
peer.listen(0, "127.0.0.1", resolve),
);
const address = peer.address();
if (address === null || typeof address === "string")
throw new Error("Expected private loopback listener");
for (let index = 0; index < 9; index += 1) {
const sessionId = randomUUID();
const path = join(directory, `cutter-${process.pid}-${sessionId}.json`);
descriptors.push(path);
await writeFile(
path,
JSON.stringify({
session_id: sessionId,
pid: process.pid,
host: "127.0.0.1",
port: address.port,
token: "discovery-test-transport-token",
document_generation: 0,
current_file: null,
cutter_version: null,
identity_status: "partial",
}),
{ mode: 0o600 },
);
}
await server.connect(serverTransport);
await client.connect(clientTransport);
const request = client.callTool(
{ name: "list_cutter_sessions", arguments: {} },
{ signal: controller.signal },
);
await expect.poll(() => accepted, { timeout: 5_000 }).toBe(8);
controller.abort();
await expect(request).rejects.toThrow(/abort/iu);
// Inspect the native peers; rejecting the SDK promise alone proves no cleanup.
await expect.poll(() => sockets.size, { timeout: 1_000 }).toBe(0);
expect(accepted).toBe(8);
for (const path of descriptors) await rm(path);
const reply = await client.callTool({
name: "list_cutter_sessions",
arguments: {},
});
expect(reply.isError).not.toBe(true);
expect(
toolContract("list_cutter_sessions").outputSchema.parse(
reply.structuredContent,
),
).toMatchObject({
sessions: [],
discovery_status: "no_live_bridge_found",
bridge_directory_security: "private_verified",
});
expect(accepted).toBe(8);
} finally {
for (const socket of sockets) socket.destroy();
await client.close();
await server.close();
await new Promise<void>((resolve) => peer.close(() => resolve()));
await rm(directory, { recursive: true, force: true });
}
},
);
it("propagates MCP cancellation into an active Cutter command", async () => {
let markDispatched: (() => void) | undefined;
let observedAbort = false;
@@ -275,15 +179,9 @@ it("shares one cleanup proof across concurrent Cutter MCP commands without Binar
});
it("cancels active discovery sockets, stops queued probes and recovers on the same MCP connection", async () => {
const { createServer } = await import("node:net");
const { writeFile, rm } = await import("node:fs/promises");
const { join } = await import("node:path");
const { CutterBridgeClient } =
await import("../../../src/cutter/CutterBridgeClient.js");
const { createTestTempDirectory } =
await import("../../fixtures/temporaryDirectory.js");
const root = await createTestTempDirectory("rea-cutter-discovery-cancel-");
const sockets = new Set<import("node:net").Socket>();
if (process.platform !== "win32") await chmod(root, 0o700);
const sockets = new Set<Socket>();
let connections = 0;
let ready!: () => void;
const connected = new Promise<void>((resolve) => {
@@ -356,7 +254,7 @@ it("cancels active discovery sockets, stops queued probes and recovers on the sa
);
await connected;
controller.abort(new Error("selected discovery cancellation"));
await expect(request).rejects.toThrow();
await expect(request).rejects.toThrow("selected discovery cancellation");
await expect.poll(() => settled).toBe(true);
await expect.poll(() => sockets.size).toBe(0);
expect(connections).toBe(8);
@@ -366,6 +264,17 @@ it("cancels active discovery sockets, stops queued probes and recovers on the sa
arguments: {},
});
expect(recovered.isError).not.toBe(true);
expect(
toolContract("list_cutter_sessions").outputSchema.parse(
recovered.structuredContent,
),
).toMatchObject({
sessions: [],
discovery_status: "no_live_bridge_found",
...(process.platform === "win32"
? {}
: { bridge_directory_security: "private_verified" }),
});
expect(connections).toBe(8);
} finally {
controller.abort();
@@ -455,38 +455,6 @@ describe("enhanced MCP batch cancellation", () => {
expect(signals).toEqual(expectedCalls.map(() => false));
},
);
it("stops later batch stages across the input when one entry lookup is cancelled", async () => {
const calls: string[] = [];
const client = await connect({
execute: async (name, arguments_) => {
const selector = String(arguments_.procedure ?? arguments_.address);
calls.push(`${name}:${selector}`);
if (name === "procedure_address") return ok(selector);
if (name === "address_name" && selector === "0x1")
return err(new AnalysisCancelledError(name));
if (name === "address_name") return ok("fixture_name");
if (name === "procedure_pseudo_code") return ok("return 0;");
throw new Error(`Unexpected operation: ${name}`);
},
});
const response = await client.callTool({
name: "batch_decompile",
arguments: { addresses: ["0x1", "0x2"] },
});
expect(response.isError).toBe(true);
expect(response.structuredContent).toBeUndefined();
expect(parseMcpToolError(response)).toMatchObject({
error: { code: "cancelled", details: { operation: "address_name" } },
});
expect(calls).toEqual([
"procedure_address:0x1",
"procedure_address:0x2",
"address_name:0x1",
]);
});
});
describe("enhanced MCP batches", () => {
@@ -4,7 +4,6 @@ import { err, ok } from "../../../src/domain/result.js";
import { AnalysisCapabilityUnavailableError } from "../../../src/domain/analysisErrorCore.js";
import { parseEvidence } from "../../../src/domain/evidence.js";
import { connect, closeEnhancedToolResources } from "./enhancedToolsHarness.js";
import { parseMcpToolError } from "../../fixtures/mcpToolError.js";
afterEach(closeEnhancedToolResources);
@@ -98,41 +97,3 @@ it("counts canonical inventory rows in the composed overview without altering la
});
expect([JSON.stringify(procedures), JSON.stringify(strings)]).toEqual(before);
});
it.each(["list_procedures", "list_strings"])(
"refuses legacy %s dictionaries instead of declaring a complete overview",
async (invalidOperation) => {
const client = await connect({
execute: async (operation) => {
if (operation === "read_binary_inventory_summary")
return err(
new AnalysisCapabilityUnavailableError(
"fixture",
operation,
"No native overview",
),
);
return ok(
createAnalysisExecution(
operation === "current_document"
? "fixture"
: operation === invalidOperation
? { "0x1000": "legacy" }
: [],
{ id: "fixture", name: "Fixture", version: "1" },
),
);
},
});
const response = await client.callTool({
name: "binary_overview",
arguments: {},
});
expect(parseMcpToolError(response).error).toMatchObject({
code: "unreadable_output",
details: {
operation: invalidOperation,
},
});
},
);
@@ -312,7 +312,7 @@ it("refuses an existing or parentless output directory and leaves it as it was",
});
});
it("refuses unknown, unnamed loopback and non-capture Evidence, and repeated rules", async () => {
it("refuses unknown and unnamed loopback Evidence, and repeated rules", async () => {
const { client, session } = await connect();
const root = await createTestTempDirectory("rea-bundle-mcp-refuse-");
const output_directory = join(root, "env");
@@ -308,29 +308,6 @@ describe("function comparison MCP integration", () => {
},
});
});
it("accepts inline Evidence without prior session registration", async () => {
const session = createTestBinarySession(() => ({
health: () => Promise.resolve(),
execute: () => Promise.resolve(observed(null)),
close: () => Promise.resolve(resultOk(null)),
}));
const { client } = await createApplicationMcpHarness({
session,
clientIdentity: { name: "function-link-test", version: "1" },
});
const result = await client.callTool({
name: "compare_functions",
arguments: {
left: FUNCTION_COMPARISON_EXAMPLE.left,
right: FUNCTION_COMPARISON_EXAMPLE.right,
},
});
expect(result.isError).not.toBe(true);
expect(result.structuredContent).toMatchObject({
normalized_result: { status: expect.any(String) },
});
});
});
describe("function comparison input-kind diagnostics", () => {
+5 -2
View File
@@ -1297,11 +1297,13 @@ it("bounds expansion before allocation and distinguishes unsupported pickle and
const pickle = await provider.inspect({ path, hash: "header" });
if (pickle.ok) throw new Error("Pickle admitted");
expect(pickle.error._tag).toBe("AnalysisUnsupportedTargetError");
await writeFile(path, ggufFixture({ version: 999 }).bytes);
await writeFile(path, ggufFixture().bytes);
expect((await provider.inspect({ path, hash: "header" })).ok).toBe(true);
const alias = join(root, "link");
await symlink(path, alias);
const linked = await provider.inspect({ path: alias, hash: "header" });
expect(linked.ok).toBe(false);
if (linked.ok) throw new Error("Selected symlink admitted");
expect(linked.error.message).toMatch(/symlink|symbolic.link/iu);
const service = createModelArtifactService();
onTestFinished(() => service.close());
const invalid = await service.inspect({
@@ -1464,6 +1466,7 @@ it("yields while validating a large metadata array, preserves cancellation and r
_tag: "AnalysisCancelledError",
cause: reason,
});
expect(result.error.cause).toBe(reason);
await writeFile(path, ggufFixture().bytes);
const recovered = await provider.inspect({ path, hash: "header" });
if (!recovered.ok) throw recovered.error;
-13
View File
@@ -409,19 +409,6 @@ it("reports a non-capture file as unsupported", async () => {
});
});
it("rejects a capture that is not a readable file", async () => {
const { provider } = await providerWith();
const result = await provider.execute({
operation: "inspect_pcap_capture",
input: { path: "/nonexistent/capture.pcap" },
});
expect(result.ok).toBe(false);
if (result.ok) return;
expect(projectAnalysisError(result.error)).toMatchObject({
code: "invalid_request",
});
});
it("reports relative configuration and missing capinfos as capability gaps", async () => {
const root = await createTestTempDirectory("rea-pcap-config-");
const { tshark } = await writeStubTools(root);
@@ -157,22 +157,13 @@ export class AdbDeviceAnalysisService {
const posix = it.skipIf(process.platform === "win32");
posix(
"runs the production verifier successfully and closes its provider",
"removes exactly its own pushed probe before reporting success",
async () => {
const result = await runVerifier();
expect(result.status, `${result.stdout}\n${result.stderr}`).toBe(0);
expect(result.stdout).toContain("all adb device checks passed");
expect(result.closed).toBe("closed");
},
);
posix(
"removes exactly its own pushed probe before reporting success",
async () => {
const result = await runVerifier();
expect(result.status, `${result.stdout}\n${result.stderr}`).toBe(0);
expect(result.adbCalls).toHaveLength(1);
expect(result.adbCalls[0]).toMatch(
/^-s test shell rm -- \/data\/local\/tmp\/rea-verify-\d+\.txt$/u,
+3 -2
View File
@@ -28,8 +28,8 @@ const service = \`
export class FridaInstrumentationService {
async listProcesses() { return { ok: true, value: { processes: [{ pid: 41, name: 'selected' }] } }; }
async instrument() {
const pid = process.env.REA_PROBE_KIND === 'missing' ? undefined : process.env.REA_PROBE_KIND === 'correct' ? 41 : 42;
return { ok: true, value: { cleanupError: null, evidence: { normalized_result: { pid, messages: [{ type: 'send', payload: { rea_frida_verification: 'ok', pid } }] } } } };
const pid = process.env.REA_PROBE_KIND === 'missing' ? undefined : ['correct', 'wrong-marker'].includes(process.env.REA_PROBE_KIND) ? 41 : 42;
return { ok: true, value: { cleanupError: null, evidence: { normalized_result: { pid, messages: [{ type: 'send', payload: { rea_frida_verification: 'ok', pid: process.env.REA_PROBE_KIND === 'wrong-marker' ? 42 : pid } }] } } } };
}
}\`;
const manager = \`export class FridaInstrumentationManager { async closeAll() {} }\`;
@@ -56,6 +56,7 @@ registerHooks({ resolve(specifier, context, next) {
for (const [kind, remote, message] of [
["missing", false, "Frida returned no valid target PID"],
["wrong", true, "Frida observed a different target PID"],
["wrong-marker", true, "Selected target did not return the Frida marker"],
] as const) {
await expect(invoke(kind, remote)).rejects.toMatchObject({
stderr: expect.stringContaining(message),
+16 -20
View File
@@ -557,7 +557,7 @@ it("keeps idle retirement after refusing a mapping for a loaded engine", async (
});
it("completes large class inventories with exact case-sensitive search semantics", async () => {
const { service, apk, provider } = await setup("large-inventory");
const { service, apk, provider, launches } = await setup("large-inventory");
const full = await service.execute("search_android_classes", {
path: apk,
query: "",
@@ -578,7 +578,22 @@ it("completes large class inventories with exact case-sensitive search semantics
mixedCase.value.normalized_result,
).matches,
).toEqual([]);
const filtered = await service.execute("search_android_classes", {
path: apk,
query: "Class",
});
if (!filtered.ok) throw filtered.error;
const matches = androidResultSchemas.search_android_classes.parse(
filtered.value.normalized_result,
);
expect(matches.total_classes).toBe(9001);
expect(matches.matches).toHaveLength(9000);
expect(matches.matches[0]).toBe("fixture.Class0");
expect(matches.matches[4096]).toBe("fixture.Class4096");
expect(matches.matches.at(-1)).toBe("fixture.Class8999");
expect(launches).toHaveLength(1);
await provider.close();
await verifyCleanup(launches);
});
it("close cancels active and queued work and prevents later acquisition", async () => {
@@ -640,25 +655,6 @@ it("retains APK identity, original input, raw producer data and normalized obser
await verifyCleanup(launches);
});
it("pages matching classes independently of the total APK class count", async () => {
const { service, apk, launches, provider } = await setup("large-inventory");
const outcome = await service.execute("search_android_classes", {
path: apk,
query: "Class",
});
if (!outcome.ok) throw outcome.error;
const result = androidResultSchemas.search_android_classes.parse(
outcome.value.normalized_result,
);
expect(result.total_classes).toBe(9001);
expect(result.matches).toHaveLength(9000);
expect(result.matches[0]).toBe("fixture.Class0");
expect(result.matches[4096]).toBe("fixture.Class4096");
expect(result.matches.at(-1)).toBe("fixture.Class8999");
await provider.close();
await verifyCleanup(launches);
});
it("preserves duplicate class-name rows and occurrence counts", async () => {
const { service, apk, launches, provider } = await setup(
"duplicate-class-name",
@@ -408,25 +408,25 @@ it.each([
"POSIX Error",
"linux",
new Error("pipe state unknown"),
/inherited output stream settlement could not be verified: pipe state unknown/,
"Owned GDB inherited output stream settlement could not be verified: pipe state unknown",
],
[
"POSIX non-Error",
"linux",
"pipe state unknown",
/inherited output stream settlement could not be verified: pipe state unknown/,
"Owned GDB inherited output stream settlement could not be verified: pipe state unknown",
],
[
"Windows Error",
"win32",
new Error("job state unknown"),
/Windows Job Object settlement could not be verified: job state unknown/,
"Owned GDB Windows Job Object settlement could not be verified: job state unknown",
],
[
"Windows non-Error",
"win32",
"job state unknown",
/Windows Job Object settlement could not be verified: job state unknown/,
"Owned GDB Windows Job Object settlement could not be verified: job state unknown",
],
] as const)(
"retains an exited debugger when output settlement throws (%s)",
@@ -451,7 +451,7 @@ it.each([
{ platform },
);
expect(result).toMatchObject({
expect(result).toEqual({
status: "incomplete",
reason: expectedReason,
});
@@ -940,38 +940,6 @@ it("uses the host platform when an exited debugger is stopped without options",
expect(outputUnrefs).toBe(process.platform === "win32" ? 0 : 1);
});
it.each([
["linux", "inherited output stream"],
["win32", "Windows Job Object"],
] as const)(
"retains a non-Error output-settlement failure on %s",
async (platform, resource) => {
const result = await stopOwnedDebuggerProcess(
{
id: `output-settlement-${platform}`,
exited: true,
launched: { process: { kill: () => true } },
supervisor: {
waitForExit: async () => true,
waitForOutputClose: async () => {
throw "output status unavailable";
},
limitAdditionalOutputBytes: () => {},
dispose: () => {},
unrefOutputStreams: () => {},
},
},
"GDB",
{ platform },
);
expect(result).toEqual({
status: "incomplete",
reason: `Owned GDB ${resource} settlement could not be verified: output status unavailable`,
});
},
);
it.skipIf(process.platform === "win32")(
"keeps supervision until a late-writing descendant closes inherited output after leader exit",
async () => {
@@ -715,6 +715,7 @@ test("cancels after the input snapshot copy and before contacting the device", a
if (result.ok) throw new Error("expected cancellation");
expect(result.error._tag).toBe("AnalysisCancelledError");
expect(execFileOutput).toHaveBeenCalledTimes(1);
expect(execFileBytes).not.toHaveBeenCalled();
expect(await readFile(f.source, "utf8")).toBe("immutable payload");
await f.provider.close();
});
@@ -742,5 +743,6 @@ test("reports input snapshot capacity failures before contacting the device", as
expect(result.error._tag).toBe("AnalysisOutputError");
expect(result.error.cause).toBe(capacityFailure);
expect(execFileOutput).toHaveBeenCalledTimes(1);
expect(execFileBytes).not.toHaveBeenCalled();
await f.provider.close();
});
@@ -230,6 +230,7 @@ it.each([false, true])(
it("blocks a fresh launch while cleanup remains unverified", async () => {
const launcher = new RetryCleanupLauncher();
const { client, child } = await failedStartup(launcher);
expect(client.operationHealth()).toMatchObject({ state: "not_started" });
await expect(client.start()).resolves.toMatchObject({
ok: false,
error: {
@@ -250,32 +251,6 @@ it("blocks a fresh launch while cleanup remains unverified", async () => {
});
});
it("preserves failed startup state through a blocked retry until cleanup succeeds", async () => {
const launcher = new RetryCleanupLauncher();
const { client } = await failedStartup(launcher);
// The startup timeout leaves the client in not_started with retained
// resources; cleanup is still incomplete.
expect(client.operationHealth()).toMatchObject({ state: "not_started" });
// An intervening retry is blocked by the retained resources, and now
// surfaces the recorded startup failure (upstream 069308bb) instead of
// a generic protocol error...
await expect(client.start()).resolves.toMatchObject({
ok: false,
error: {
_tag: "ProviderAdapterError",
cleanupIncomplete: true,
cause: { cause: { _tag: "HopperTimeoutError" } },
},
});
// ...but must not wipe the failed-startup state: the client still
// reports not_started (never idle) until cleanup succeeds, so a
// same-target open_binary keeps retiring the wedged client instead of
// fast-pathing it.
expect(client.operationHealth()).toMatchObject({ state: "not_started" });
launcher.allowCleanup = true;
await expect(client.close()).resolves.toEqual({ ok: true, value: null });
});
it("permits a fresh launch after stopping an owned bridge-request provider without an acknowledgement", async () => {
const fixture = new RetryCleanupLauncher();
fixture.allowCleanup = true;
@@ -678,7 +678,11 @@ it("keeps answering pseudocode one function at a time when the export fails", as
});
expect(pseudocode.rawResult).toMatchObject({
source: { kind: "single-function" },
project_export: { state: "failed", failure_records: "not_exposed" },
project_export: {
state: "failed",
failure_records: "not_exposed",
reason: expect.stringContaining("not in recognized object file format"),
},
});
expect(pseudocode.limitations).toEqual(
expect.arrayContaining([
@@ -1058,25 +1062,6 @@ it("recovers when another server evicts the export this session adopted", async
await client.close();
});
it("reports a failed export as failed rather than building", async () => {
const { open } = await session("rea-kuna-failed-state-", {
project: "exit-failure",
});
const client = open();
await client.execute("procedure_assembly", { procedure: "main" });
expect(
(await run(client, "procedure_pseudo_code", { procedure: "main" }))
.rawResult,
).toMatchObject({
project_export: {
state: "failed",
reason: expect.stringContaining("not in recognized object file format"),
},
});
await client.close();
});
it.each([
{
name: "export deadline",
@@ -2280,13 +2280,32 @@ describe("managed member comparison of raw-signature contention", () => {
...base,
result: { ...base.result, methods: [], fields: [field] },
};
for (const result of [
compareManagedMembers(withOwnerless, resolvedOnly),
compareManagedMembers(resolvedOnly, withOwnerless),
])
for (const [result, leftTokens, rightTokens] of [
[
compareManagedMembers(withOwnerless, resolvedOnly),
[field.token, "0x04000002"],
[field.token],
],
[
compareManagedMembers(resolvedOnly, withOwnerless),
[field.token],
[field.token, "0x04000002"],
],
] as const) {
expect(
result.fields.filter(({ match }) => match.status === "matched"),
).toEqual([]);
expect(result.fields.length).toBeGreaterThan(0);
expect(comparedTokens(result, "fields", "left")).toEqual(
[...leftTokens].sort(),
);
expect(comparedTokens(result, "fields", "right")).toEqual(
[...rightTokens].sort(),
);
expect(result.fields.every((item) => item.status === "unknown")).toBe(
true,
);
}
});
});
@@ -2668,6 +2687,9 @@ describe("managed member comparison of unresolved owners", () => {
},
});
const result = compareManagedMembers(side("Main"), side("Renamed"));
expect(result.methods.length).toBeGreaterThan(0);
expect(comparedTokens(result, "methods", "left")).toEqual([method.token]);
expect(comparedTokens(result, "methods", "right")).toEqual([method.token]);
for (const item of result.methods)
expect(item.match.status).not.toBe("matched");
@@ -2786,12 +2808,16 @@ describe("managed member comparison of unresolved owners", () => {
},
};
for (const result of [
compareManagedMembers(left, right),
compareManagedMembers(right, left),
])
for (const [result, leftToken, rightToken] of [
[compareManagedMembers(left, right), method.token, "0x06000002"],
[compareManagedMembers(right, left), "0x06000002", method.token],
] as const) {
expect(result.methods.length).toBeGreaterThan(0);
expect(comparedTokens(result, "methods", "left")).toEqual([leftToken]);
expect(comparedTokens(result, "methods", "right")).toEqual([rightToken]);
for (const item of result.methods)
expect(item.match.status).not.toBe("matched");
}
});
});
@@ -3447,6 +3473,25 @@ const withTwin = (
};
};
/** Every retained source token, including unresolved candidates with no selected identity. */
const comparedTokens = (
result: ReturnType<typeof compareManagedMembers>,
kind: "methods" | "fields",
side: "left" | "right",
) =>
[
...new Set(
result[kind]
.flatMap((item) => [
item[side]?.token,
...(side === "left"
? item.match.candidate_left_tokens
: item.match.candidate_right_tokens),
])
.filter((token) => token !== undefined),
),
].sort();
const inspect = (bytes: Buffer, path: string, typeNames?: string[]) => {
const target = managedPeFixtureTarget(bytes, path);
const result = inspectManagedMembersBytes(
+42 -28
View File
@@ -146,12 +146,17 @@ describe("reverse engineering command services", () => {
path: directory,
operation: "file_headers",
});
expect(result.ok).toBe(false);
if (!result.ok)
expect(result.error.cause).toMatchObject({
code: "EINVAL",
message: "Artifact digest requires a regular file",
});
expect(result).toMatchObject({
ok: false,
error: {
_tag: "AnalysisInputError",
operation: "inspect_with_objdump",
cause: {
code: "EINVAL",
message: "Artifact digest requires a regular file",
},
},
});
expect(invoked).toBe(false);
} finally {
await rm(directory, { recursive: true, force: true });
@@ -484,29 +489,38 @@ describe("Rizin command service", () => {
}
});
it("rejects multiline Rizin command input before launching the provider", async () => {
const directory = await mkdtemp(join(tmpdir(), "rea-rizin-input-"));
const path = join(directory, "fixture.bin");
await writeFile(path, "fixture");
let invoked = false;
const service = new ReverseEngineeringService({
run: async () => {
invoked = true;
return { stdout: "", stderr: "" };
},
});
try {
const result = await service.executeRizinCommand({
path,
command: "iI\nq",
it.each(["iI\nq", "wx\u0000 00"])(
"rejects malformed Rizin command %j before launching the provider",
async (command) => {
const directory = await mkdtemp(join(tmpdir(), "rea-rizin-input-"));
const path = join(directory, "fixture.bin");
await writeFile(path, "fixture");
let invoked = false;
const service = new ReverseEngineeringService({
run: async () => {
invoked = true;
return { stdout: "", stderr: "" };
},
});
expect(result.ok).toBe(false);
expect(invoked).toBe(false);
} finally {
await rm(directory, { recursive: true, force: true });
}
});
try {
const result = await service.executeRizinCommand({
path,
command,
});
expect(result).toMatchObject({
ok: false,
error: {
_tag: "AnalysisInputError",
operation: "execute_rizin_command",
},
});
expect(invoked).toBe(false);
} finally {
await rm(directory, { recursive: true, force: true });
}
},
);
it("preserves pre and post artifact digests when an unrestricted Rizin command changes the file", async () => {
const directory = await mkdtemp(join(tmpdir(), "rea-rizin-mutation-"));
@@ -385,27 +385,6 @@ it("rejects malformed Rizin requests before touching the provider", async () =>
expect(run).not.toHaveBeenCalled();
});
it("rejects directories as artifacts before invoking the provider", async () => {
const { directory } = await createArtifact();
const run = vi.fn(async () => ({ stdout: "", stderr: "" }));
const service = new ReverseEngineeringService({ run });
try {
const result = await service.inspectWithObjdump({
path: directory,
operation: "file_headers",
});
expect(result).toMatchObject({
ok: false,
error: { _tag: "AnalysisInputError", operation: "inspect_with_objdump" },
});
expect(run).not.toHaveBeenCalled();
} finally {
await rm(directory, { recursive: true, force: true });
}
});
it("rejects new operations after service shutdown begins", async () => {
const { directory, path } = await createArtifact();
const run = vi.fn(async () => ({ stdout: "", stderr: "" }));
@@ -1,31 +0,0 @@
import { rm } from "node:fs/promises";
import { createArtifact } from "../support/reverseEngineeringCompositionFixtures.js";
import { expect, it, vi } from "vitest";
import { TestReverseEngineeringService as ReverseEngineeringService } from "../support/reverseEngineeringService.js";
it("rejects NUL in a Rizin command before touching the provider", async () => {
const { directory, path } = await createArtifact();
let invoked = false;
const service = new ReverseEngineeringService({
run: async () => {
invoked = true;
return { stdout: "", stderr: "" };
},
});
try {
const result = await service.executeRizinCommand({
path,
command: "wx\u0000 00",
});
expect(result).toMatchObject({
ok: false,
error: { _tag: "AnalysisInputError", operation: "execute_rizin_command" },
});
expect(invoked).toBe(false);
} finally {
await rm(directory, { recursive: true, force: true });
}
});
+10 -42
View File
@@ -52,7 +52,7 @@ describe.each(["attached", "headless"] as const)(
expect(operations).toContain("resolve_containing_procedure");
});
it.each(operations)(
"declares %s UI effects matching the upstream tools it reaches",
"serializes %s against its named advertised output contract",
async (operation) => {
const { root, target } = await createIdaTarget();
roots.push(root);
@@ -69,50 +69,18 @@ describe.each(["attached", "headless"] as const)(
target,
producer,
);
try {
const execution = await client.execute(
operation,
parametersFor(operation),
);
if (mode === "headless" && operation === "procedure_callers")
expect(execution).toMatchObject({
ok: false,
error: { _tag: "AnalysisCapabilityUnavailableError" },
});
else if (!execution.ok) throw execution.error;
} finally {
expect((await client.close()).ok).toBe(true);
}
const descriptor = idaCapabilities(
IDA_COMPATIBILITY_PROFILES[mode === "attached" ? "legacy" : "modern"],
true,
).find((capability) => capability.operation === operation);
expect(descriptor?.effects.mayShowUi).toBe(
producer.calls.some(({ name }) => GUI_NAVIGATING_TOOLS.has(name)),
);
},
);
it.each(operations)(
"serializes %s against its named advertised output contract",
async (operation) => {
const { root, target } = await createIdaTarget();
roots.push(root);
const client = new IdaSessionClient(
{
command: "fixture",
args: [],
env: {},
mode,
workspaceRoot: root,
timeoutMs: 1000,
},
target,
new RecordingIdaMcp(target, mode),
);
try {
const parameters = parametersFor(operation);
const execution = await client.execute(operation, parameters);
const descriptor = idaCapabilities(
IDA_COMPATIBILITY_PROFILES[
mode === "attached" ? "legacy" : "modern"
],
true,
).find((capability) => capability.operation === operation);
expect(descriptor?.effects.mayShowUi).toBe(
producer.calls.some(({ name }) => GUI_NAVIGATING_TOOLS.has(name)),
);
if (mode === "headless" && operation === "procedure_callers") {
expect(execution.ok).toBe(false);
if (!execution.ok)
@@ -51,11 +51,4 @@ describe("packed tar listing", () => {
"package/b",
]);
});
it("splits a bare newline listing the way the Windows bug appeared", () => {
// Guard the reported regression: the previous parse kept `\r` on members.
const naive = listing("\r\n").split("\n");
expect(naive).not.toContain(packedBridges[0]);
expect(parseTarListing(listing("\r\n"))).toContain(packedBridges[0]);
});
});
+3
View File
@@ -158,7 +158,10 @@ const FAKE_APT_GET = String.raw`
import os
import re
import sys
import json
with open(os.path.join(os.environ["FAKE_REA_NOTES"], "apt.jsonl"), "a") as handle:
handle.write(json.dumps({"argv": sys.argv[1:]}) + "\n")
with open(os.environ["APT_CONFIG"]) as handle:
conf = handle.read()
lists = re.search(r'Dir::State::Lists "([^"]+)"', conf).group(1)
+107 -5
View File
@@ -1,11 +1,112 @@
import {
il2cppMetadataSectionNames,
type IL2cppMetadataSectionName,
} from "../../../src/domain/il2cpp/il2cppMetadata.js";
import type { IL2cppMetadataSectionName } from "../../../src/domain/il2cpp/il2cppMetadata.js";
type SectionName = IL2cppMetadataSectionName;
type Width = 1 | 2 | 4;
/** Authored serialized header slots; never derive fixture bytes from the decoder catalog. */
const V24_HEADER = [
"stringLiterals",
"stringLiteralData",
"strings",
"events",
"properties",
"methods",
"parameterDefaultValues",
"fieldDefaultValues",
"fieldAndParameterDefaultValueData",
"fieldMarshaledSizes",
"parameters",
"fields",
"genericParameters",
"genericParameterConstraints",
"genericContainers",
"nestedTypes",
"interfaces",
"vtableMethods",
"interfaceOffsets",
"typeDefinitions",
"images",
"assemblies",
"metadataUsageLists",
"metadataUsagePairs",
"fieldRefs",
"referencedAssemblies",
"attributesInfo",
"attributeTypes",
"unresolvedVirtualCallParameterTypes",
"unresolvedVirtualCallParameterRanges",
"windowsRuntimeTypeNames",
"exportedTypeDefinitions",
] as const satisfies readonly SectionName[];
const V27_HEADER = [
"stringLiterals",
"stringLiteralData",
"strings",
"events",
"properties",
"methods",
"parameterDefaultValues",
"fieldDefaultValues",
"fieldAndParameterDefaultValueData",
"fieldMarshaledSizes",
"parameters",
"fields",
"genericParameters",
"genericParameterConstraints",
"genericContainers",
"nestedTypes",
"interfaces",
"vtableMethods",
"interfaceOffsets",
"typeDefinitions",
"images",
"assemblies",
"fieldRefs",
"referencedAssemblies",
"attributesInfo",
"attributeTypes",
"unresolvedVirtualCallParameterTypes",
"unresolvedVirtualCallParameterRanges",
"windowsRuntimeTypeNames",
"windowsRuntimeStrings",
"exportedTypeDefinitions",
] as const satisfies readonly SectionName[];
const MODERN_HEADER = [
"stringLiterals",
"stringLiteralData",
"strings",
"events",
"properties",
"methods",
"parameterDefaultValues",
"fieldDefaultValues",
"fieldAndParameterDefaultValueData",
"fieldMarshaledSizes",
"parameters",
"fields",
"genericParameters",
"genericParameterConstraints",
"genericContainers",
"nestedTypes",
"interfaces",
"vtableMethods",
"interfaceOffsets",
"typeDefinitions",
"images",
"assemblies",
"fieldRefs",
"referencedAssemblies",
"attributeData",
"attributeDataRanges",
"unresolvedIndirectCallParameterTypes",
"unresolvedIndirectCallParameterRanges",
"windowsRuntimeTypeNames",
"windowsRuntimeStrings",
"exportedTypeDefinitions",
] as const satisfies readonly SectionName[];
/** Synthetic version 39 metadata small enough to read in a hex dump. */
interface Il2cppMetadataFixtureOptions {
/** Serialized TypeIndex width. */
@@ -180,7 +281,8 @@ const assembleFile = (
) => {
const fixed = version !== 39;
const entryBytes = fixed ? 8 : 12;
const names = il2cppMetadataSectionNames(version);
const names: readonly SectionName[] =
version === 24 ? V24_HEADER : version === 27 ? V27_HEADER : MODERN_HEADER;
const headerBytes = 8 + names.length * entryBytes;
const header = Buffer.alloc(headerBytes);
header.writeUInt32LE(0xfab11baf, 0);