mirror of
https://github.com/morluto/rea.git
synced 2026-11-15 11:13:21 +00:00
A terminal call on one branch does not end the whole function: other branches can still reach the following code, which stays in the body and pseudocode. The function-level limitation now says Ghidra excluded that call's fall-through and that code reachable only through it is missing if the callee returns. It is listed before the call-specific facts so a bounded composed summary that admits limitations in order keeps it. The body-coverage tests now check the counted records and their reasons instead of freezing the explanatory sentence. Co-Authored-By: gensokyo-dev <gensokyo-dev@users.noreply.github.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
148 lines
4.5 KiB
TypeScript
148 lines
4.5 KiB
TypeScript
import { readdir, readFile, writeFile } from "node:fs/promises";
|
|
import { join } from "node:path";
|
|
import { describe, expect } from "vitest";
|
|
|
|
import { decompileDirectoryIndexSchema } from "../../../src/contracts/decompileDirectory.js";
|
|
import { elf } from "../../../src/domain/binaryTarget.fixture.js";
|
|
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
|
import { cliTest } from "../../support/cli/cliFixture.js";
|
|
import { writeFakeKuna } from "../providers/kuna/fakeKuna.js";
|
|
|
|
const FUNCTIONS = [
|
|
{ name: "main", addr: "0x1000" },
|
|
{ name: "parse", addr: "0x1040" },
|
|
{ name: "late", addr: "0x1080" },
|
|
] as const;
|
|
|
|
describe("decompile-all CLI", () => {
|
|
cliTest(
|
|
"maps filters, limit and a relative output directory to the export",
|
|
async ({ cli }) => {
|
|
const root = await createTestTempDirectory("rea-decompile-all-cli-");
|
|
const kuna = await writeFakeKuna(root, { functions: FUNCTIONS });
|
|
const target = join(root, "target.elf");
|
|
await writeFile(target, elf(2, 1, 62));
|
|
const environment = {
|
|
REA_LOG_LEVEL: "silent",
|
|
REA_KUNA_COMMAND: kuna,
|
|
REA_KUNA_CACHE_DIR: join(root, "cache"),
|
|
};
|
|
const result = await cli.run({
|
|
arguments: [
|
|
"decompile-all",
|
|
target,
|
|
"--provider",
|
|
"kuna",
|
|
"--output",
|
|
"out",
|
|
"--address-start",
|
|
"0X1000",
|
|
"--address-end",
|
|
"0X1040",
|
|
"--limit",
|
|
"1",
|
|
"--format",
|
|
"json",
|
|
],
|
|
cwd: root,
|
|
environment,
|
|
});
|
|
expect(result.exitCode, JSON.stringify(result.json)).toBe(0);
|
|
expect(result.json).toMatchObject({
|
|
operation: "decompile_all",
|
|
parameters: {
|
|
output_directory: join(root, "out"),
|
|
address_start: "0X1000",
|
|
address_end: "0X1040",
|
|
limit: 1,
|
|
layout: "address",
|
|
resume: false,
|
|
},
|
|
normalized_result: {
|
|
output_directory: join(root, "out"),
|
|
// The fake inventory also lists the import puts at 0x2000.
|
|
counts: { selected: 2, excluded: 2, written: 1, remaining: 1 },
|
|
},
|
|
});
|
|
expect(await readdir(join(root, "out", "functions"))).toEqual([
|
|
"0x1000.c",
|
|
]);
|
|
expect(JSON.stringify(result.json)).not.toContain(
|
|
await readFile(join(root, "out", "functions", "0x1000.c"), "utf8"),
|
|
);
|
|
const resumed = await cli.run({
|
|
arguments: [
|
|
"decompile-all",
|
|
target,
|
|
"--provider",
|
|
"kuna",
|
|
"--output",
|
|
"out",
|
|
"--resume",
|
|
"--address-start",
|
|
"0X1000",
|
|
"--address-end",
|
|
"0X1040",
|
|
"--limit",
|
|
"100001",
|
|
"--format",
|
|
"json",
|
|
],
|
|
cwd: root,
|
|
environment,
|
|
});
|
|
expect(resumed.exitCode, JSON.stringify(resumed.json)).toBe(0);
|
|
expect(resumed.json).toMatchObject({
|
|
parameters: { limit: 100001, resume: true },
|
|
normalized_result: {
|
|
counts: { selected: 2, written: 1, skipped: 1, remaining: 0 },
|
|
},
|
|
});
|
|
// Kuna lacks procedure_info, so every retained record keeps an
|
|
// unavailable body. A resume that writes nothing still summarizes them.
|
|
const bodyCaveat = expect.stringMatching(
|
|
/^2 exported function record.*procedure_info is unavailable/u,
|
|
);
|
|
expect(resumed.json).toMatchObject({
|
|
normalized_result: { limitations: [bodyCaveat] },
|
|
});
|
|
const noop = await cli.run({
|
|
arguments: [
|
|
"decompile-all",
|
|
target,
|
|
"--provider",
|
|
"kuna",
|
|
"--output",
|
|
"out",
|
|
"--resume",
|
|
"--address-start",
|
|
"0X1000",
|
|
"--address-end",
|
|
"0X1040",
|
|
"--format",
|
|
"json",
|
|
],
|
|
cwd: root,
|
|
environment,
|
|
});
|
|
expect(noop.exitCode, JSON.stringify(noop.json)).toBe(0);
|
|
expect(noop.json).toMatchObject({
|
|
normalized_result: {
|
|
counts: { selected: 2, requested: 0, skipped: 2 },
|
|
limitations: [bodyCaveat],
|
|
},
|
|
});
|
|
const index = decompileDirectoryIndexSchema.parse(
|
|
JSON.parse(
|
|
await readFile(join(root, "out", "decompile-index.json"), "utf8"),
|
|
),
|
|
);
|
|
expect(index.limitations).toEqual([bodyCaveat]);
|
|
expect(index.functions.map((record) => record.body.available)).toEqual([
|
|
false,
|
|
false,
|
|
]);
|
|
},
|
|
);
|
|
});
|