Compare commits

...
13 changed files with 684 additions and 2 deletions

No files matched your search

+33 -2
View File
@@ -179,6 +179,7 @@ public final class ReaGhidraBridge extends HeadlessScript {
private List<FunctionEntry> procedureInventory;
private List<InventoryItem> stringInventory;
private DecompInterface decompiler;
private boolean existingProject;
// A refused Program may only report its admission failure to the client.
private String identityAdmissionFailure;
/** Per-function native decompiler limit from the session; 0 leaves it unbounded. */
@@ -199,6 +200,7 @@ public final class ReaGhidraBridge extends HeadlessScript {
Path descriptorPath = Path.of(arguments[0]);
checkpointDirectory = descriptorPath.toAbsolutePath().normalize().getParent();
SessionDescriptor descriptor = readDescriptor(descriptorPath);
existingProject = descriptor.existingProject;
decompileTimeoutSeconds = descriptor.decompileTimeoutSeconds();
// Windows retains a native immutable bearer descriptor lease until
// runtime_close; POSIX removes it after this read. Normal cleanup removes
@@ -3716,7 +3718,7 @@ public final class ReaGhidraBridge extends HeadlessScript {
return result;
}
private static JsonObject callRelationshipCoverage(
private JsonObject callRelationshipCoverage(
CallBoundaryObservation calls, Integer missingCallers) {
JsonObject coverage = completeResolvedCallCoverage();
Integer omitted = calls == null ? null : calls.unrepresentedCalleeCalls();
@@ -3733,7 +3735,7 @@ public final class ReaGhidraBridge extends HeadlessScript {
if (calls == null && missingCallers == null) directions.add("callers");
directions.add("callees");
coverage.add("limited_directions", directions);
return coverage;
return qualifyProjectCallCoverage(coverage, calls, missingCallers);
}
JsonArray limitations = new JsonArray();
if (callerLimitation != null) limitations.add(callerLimitation);
@@ -3747,6 +3749,35 @@ public final class ReaGhidraBridge extends HeadlessScript {
coverage.add("limitations", limitations);
coverage.add("limited_directions", directions);
}
return qualifyProjectCallCoverage(coverage, calls, missingCallers);
}
private JsonObject qualifyProjectCallCoverage(
JsonObject coverage, CallBoundaryObservation calls, Integer missingCallers) {
if (!existingProject) return coverage;
String reason = "The selected project's prior analysis completeness is unknown; " +
"the recorded call relationships may omit undiscovered callers or callees.";
String status = coverage.get("status").getAsString();
// A single-direction read with known omissions is still partial; an
// unknown project history adds a limitation rather than erasing them.
if (status.equals("partial") && (calls == null || missingCallers == null)) {
coverage.getAsJsonArray("limitations").add(reason);
return coverage;
}
if (status.equals("unknown")) reason += " " + coverage.get("reason").getAsString();
if (status.equals("partial")) {
for (JsonElement limitation : coverage.getAsJsonArray("limitations"))
reason += " " + limitation.getAsString();
}
JsonArray directions = new JsonArray();
if (missingCallers != null || (calls == null && missingCallers == null))
directions.add("callers");
if (calls != null || (calls == null && missingCallers == null))
directions.add("callees");
coverage.addProperty("status", "unknown");
coverage.addProperty("reason", reason);
coverage.remove("limitations");
coverage.add("limited_directions", directions);
return coverage;
}
+5
View File
@@ -1558,6 +1558,11 @@ For supported header-selected MIPS ELF targets, the profile also binds the
selected target's ELF metadata; it does not claim a fresh-import MIPS lane was
used for the saved project.
A matching recorded import digest does not claim that the database is unedited.
Caller and callee coverage therefore remains unknown. A single-direction query
with known omissions remains partial and also reports the unknown project history.
Observed call edges are still available: a path to an observed caller or callee
can be reached, but a missing path is unknown when the project's prior analysis
cannot establish completeness.
Saved REA seed/analyzer reports remain historical facts in retained source Evidence;
they are not interpreted as overrides applied by this session.
Project content participates in snapshot identity, so different project states
+1
View File
@@ -45,6 +45,7 @@ prove behavior on the verified Ghidra 12.1.4 and JDK 21 build.
| Ghidra lane | Supported runner/target | Additional local tools |
| ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- |
| `npm run verify:ghidra` | Linux x64/arm64 ELF or macOS x64/arm64 Mach-O | Host C compiler, Ghidra 12.1.4, and full JDK 21 |
| `npm run verify:ghidra:project-coverage` | POSIX host; selected-project call coverage, real MCP, retained Evidence and an unexecuted host fixture | Host C compiler, Ghidra 12.1.4, and full JDK 21 |
| `npm run verify:ghidra:seeds` | POSIX; raw x86 fixture; seed ordering, BSS labels, analyzer readback, not-applicable and unknown analyzer names | Ghidra 12.1.4 and full JDK 21 |
| `npm run verify:ghidra:swift` | macOS x64/arm64 Mach-O | Host Swift compiler, Ghidra 12.1.4, and full JDK 21 |
| `npm run verify:ghidra:riscv` | Linux/macOS x64/arm64 host; source-owned RV32/RV64 double-float and RV32 ilp32 ELF through CLI and MCP | RISC-V Clang, LLD, LLVM readelf and objdump, Ghidra 12.1.4, and full JDK 21 |
+1
View File
@@ -207,6 +207,7 @@
"verify:flutter": "npm run build:cached && node scripts/verify-flutter.mjs",
"verify:adb": "npm run build:cached && node scripts/verify-adb.mjs",
"verify:ghidra": "npm run build:cached && node scripts/verify-real-ghidra.mjs",
"verify:ghidra:project-coverage": "npm run build:cached && node scripts/verify-real-ghidra-project-coverage.mjs",
"verify:ghidra:checkpoints": "npm run build:cached && node scripts/verify-real-ghidra-checkpoints.mjs",
"verify:ghidra:seeds": "npm run build:cached && node scripts/verify/ghidra/seeds.mjs",
"verify:ghidra:mips": "npm run build:cached && node scripts/verify-real-ghidra-mips.mjs",
+21
View File
@@ -0,0 +1,21 @@
import type { Client, Transport } from "@modelcontextprotocol/client";
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
/** Share an in-flight SDK close, including one started during failed initialization. */
export class VerifierStdioTransport extends StdioClientTransport {}
type VerifierConnection = {
client: Client;
transport: Transport;
definitions: Map<
string,
Awaited<ReturnType<Client["listTools"]>>["tools"][number]
>;
};
/** Own MCP transports from connection through discovery, use, and cleanup retry. */
export function createVerifierMcpOwner(): {
open(client: Client, transport: Transport): Promise<VerifierConnection>;
closeConnection(connection: VerifierConnection): Promise<void>;
close(): Promise<void>;
};
+54
View File
@@ -0,0 +1,54 @@
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
import {
closeVerifierResources,
withVerifierCleanup,
} from "./verifier-cleanup.mjs";
/** Share an in-flight SDK close, including one started during failed initialization. */
export class VerifierStdioTransport extends StdioClientTransport {
#closing;
close() {
if (this.#closing !== undefined) return this.#closing;
this.#closing = super.close().catch((error) => {
this.#closing = undefined;
throw error;
});
return this.#closing;
}
}
/** Own MCP transports from connection through discovery, use, and cleanup retry. */
export function createVerifierMcpOwner() {
const owned = new Set();
const closeConnection = async (connection) => {
await closeVerifierResources([
() => connection.client.close(),
() => connection.transport.close(),
]);
owned.delete(connection);
};
return {
async open(client, transport) {
const connection = { client, transport, definitions: new Map() };
owned.add(connection);
try {
await client.connect(transport);
connection.definitions = new Map(
(await client.listTools()).tools.map((tool) => [tool.name, tool]),
);
return connection;
} catch (cause) {
return withVerifierCleanup(() => {
throw cause;
}, [() => closeConnection(connection)]);
}
},
closeConnection,
close() {
return closeVerifierResources(
[...owned].map((connection) => () => closeConnection(connection)),
);
},
};
}
@@ -0,0 +1,411 @@
#!/usr/bin/env node
import assert from "node:assert/strict";
import { execFile } from "node:child_process";
import { createHash } from "node:crypto";
import {
mkdir,
mkdtemp,
readFile,
readdir,
rm,
writeFile,
} from "node:fs/promises";
import { realpathSync } from "node:fs";
import { tmpdir } from "node:os";
import { isAbsolute, join } from "node:path";
import { promisify } from "node:util";
import { fileURLToPath } from "node:url";
import {
OwnedCommandFailure,
runOwnedCommand,
} from "../dist/process/OwnedCommand.js";
import { createVerifierRun, completeVerifierRun } from "./lib/verifier-run.mjs";
import { Client } from "@modelcontextprotocol/client";
import { createAnalysisProfile } from "../dist/domain/analysisProfile.js";
import { createEvidenceBundle } from "../dist/domain/evidenceBundle.js";
import { inspectVerifierGhidraInstallation } from "./lib/ghidra-installation-inspection.mjs";
import { withVerifierCleanup } from "./lib/verifier-cleanup.mjs";
import {
createVerifierMcpOwner,
VerifierStdioTransport,
} from "./lib/verifier-mcp-owner.mjs";
import {
requireMcpOutput,
requireMcpToolError,
} from "./lib/mcp-verifier-results.mjs";
const exec = promisify(execFile);
export async function verifyProjectCoverage(run) {
const installDir = process.env.GHIDRA_INSTALL_DIR;
if (installDir === undefined || !isAbsolute(installDir))
throw new Error(
"Set GHIDRA_INSTALL_DIR to an absolute Ghidra installation path.",
);
const installation = await inspectVerifierGhidraInstallation({
environment: process.env,
installDir,
...(process.env.JAVA_HOME === undefined
? {}
: { javaHome: process.env.JAVA_HOME }),
});
if (installation.status !== "available")
throw new Error(`Ghidra is unavailable: ${JSON.stringify(installation)}`);
if (process.platform === "win32")
throw new Error(
"REA selected Ghidra projects are supported on POSIX hosts.",
);
const scriptRoot = fileURLToPath(
new URL("../tests/conformance/ghidra/", import.meta.url),
);
const entrypoint = fileURLToPath(new URL("./rea.mjs", import.meta.url));
const workspace = await mkdtemp(
join(tmpdir(), "rea-ghidra-project-coverage-"),
);
const connections = createVerifierMcpOwner();
const target = join(workspace, "fixture");
const projects = join(workspace, "projects");
const env = {
PATH: process.env.PATH ?? "/usr/bin:/bin",
JAVA_HOME: installation.javaHome,
GHIDRA_INSTALL_DIR: installation.installDir,
REA_MCP_OUTPUT_SCHEMA_PROFILE: "compact",
REA_LOG_LEVEL: "silent",
REA_PROCESS_RUN_ID: run.run_id,
};
const hash = (data) => createHash("sha256").update(data).digest("hex");
async function manifest(directory) {
const entries = await readdir(directory, { withFileTypes: true });
const files = await Promise.all(
entries.map(async (entry) => {
const path = join(directory, entry.name);
return entry.isDirectory()
? manifest(path)
: entry.isFile()
? [[path, hash(await readFile(path))]]
: [];
}),
);
return files.flat().sort((a, b) => a[0].localeCompare(b[0]));
}
async function connect(environment) {
const client = new Client({
name: "selected-project-coverage-verifier",
version: "1",
});
const transport = new VerifierStdioTransport({
command: process.execPath,
args: [entrypoint, "mcp"],
env: environment,
stderr: "pipe",
});
return connections.open(client, transport);
}
async function call(connection, name, args = {}) {
const reply = await connection.client.callTool(
{ name, arguments: args },
{ timeout: 180000, toolDefinition: connection.definitions.get(name) },
);
return requireMcpOutput(reply, name);
}
let commandCleanupIncomplete = false;
const result = await withVerifierCleanup(async () => {
await mkdir(projects);
await exec(
// The invoking developer selects the compiler; execFile does not use a shell.
process.env.REA_CC ?? "cc",
[
"-O0",
"-g",
"-fno-inline",
"-fno-optimize-sibling-calls",
join(scriptRoot, "selected-project-coverage.c"),
"-o",
target,
],
{ env, timeout: 120000 },
);
for (const name of ["partial", "analyzed"]) {
const args = [projects, name, "-import", target];
if (name === "partial")
args.push(
"-noanalysis",
"-scriptPath",
scriptRoot,
"-postScript",
"ReaSelectedProjectSeeds.java",
);
let result;
try {
result = await runOwnedCommand(
{
command: installation.analyzeHeadlessPath,
arguments: args,
env,
runId: run.run_id,
expectedCommand: null,
},
{ timeoutMs: 180000, diagnosticBytes: 16 * 1024 * 1024 },
);
} catch (cause) {
commandCleanupIncomplete =
cause instanceof OwnedCommandFailure &&
cause.cleanupOwner !== undefined;
throw cause;
}
if (name === "partial")
for (const seed of [
"rea_project_leaf",
"rea_project_caller",
"rea_project_other",
])
assert.match(
result.stdout.text,
new RegExp(`REA_PROJECT_SEED ${seed} `),
);
}
const before = {
target: hash(await readFile(target)),
projects: await manifest(projects),
};
const report = [];
for (const mode of ["fresh", "partial", "analyzed"]) {
const selected = mode !== "fresh";
const connection = await connect({
...env,
...(selected
? {
REA_GHIDRA_PROJECT_PATH: join(projects, `${mode}.gpr`),
REA_GHIDRA_PROGRAM_NAME: "fixture",
}
: {}),
});
const bundlePath = join(workspace, `${mode}.json`);
const paths = [];
await withVerifierCleanup(async () => {
await call(connection, "open_binary", {
path: target,
provider_id: "ghidra",
});
const inventory = (
await call(connection, "list_procedures", {
detail: "complete",
})
).normalized_result;
const entry = (name) => {
const matches = inventory.filter(
(item) => item.value === name || item.value === `_${name}`,
);
assert.equal(matches.length, 1, `Expected one entry for ${name}`);
return matches[0].address;
};
const leaf = entry("rea_project_leaf"),
caller = entry("rea_project_caller"),
other = entry("rea_project_other");
const direct = await call(connection, "procedure_callees", {
procedure: caller,
});
assert.equal(
direct.relationship_coverage.status,
selected ? "unknown" : "complete",
);
assert.equal(
direct.normalized_result.includes(leaf),
mode !== "partial",
);
assert.equal(
direct.analysis_profile.parameters
.selected_project_relationship_coverage,
selected ? "prior-analysis-unknown-v1" : undefined,
);
for (const [start, goal, direction] of [
[caller, leaf, "forward"],
[leaf, caller, "backward"],
[other, caller, "backward"],
]) {
const evidence = await call(connection, "trace_call_path", {
start,
goal,
direction,
});
paths.push(evidence);
}
assert.equal(
paths[0].normalized_result.goal_status,
mode === "partial" ? "unknown" : "reached",
);
assert.equal(
paths[1].normalized_result.goal_status,
mode === "partial" ? "unknown" : "reached",
);
assert.equal(
paths[2].normalized_result.goal_status,
selected ? "unknown" : "not_reached",
);
if (selected)
for (const evidence of paths)
assert.ok(
Object.values(
evidence.normalized_result.relationship_coverage,
).some(
(coverage) =>
coverage.status === "unknown" &&
/prior analysis completeness is unknown/u.test(
coverage.reason,
),
),
);
if (mode === "analyzed") {
const dossier = await call(connection, "analyze_function", {
procedure: entry("rea_project_callback"),
detail: "complete",
});
assert.equal(dossier.relationship_coverage.status, "unknown");
assert.match(
dossier.relationship_coverage.reason,
/decoded call\(s\) without destinations/u,
);
assert.match(
dossier.relationship_coverage.reason,
/prior analysis completeness is unknown/u,
);
const callbackPath = await call(connection, "trace_call_path", {
start: entry("rea_project_callback"),
goal: leaf,
direction: "forward",
});
const coverages = Object.values(
callbackPath.normalized_result.relationship_coverage,
);
assert.ok(
coverages.some(
(coverage) =>
coverage.status === "partial" &&
coverage.limitations.some((text) =>
/decoded call\(s\) without destinations/u.test(text),
) &&
coverage.limitations.some((text) =>
/prior analysis completeness is unknown/u.test(text),
),
),
);
}
await call(connection, "export_evidence_bundle", { path: bundlePath });
if (mode === "partial") {
const snapshotPath = join(workspace, "partial-snapshot.json");
await call(connection, "close_binary", {
snapshot_path: snapshotPath,
});
const snapshot = JSON.parse(await readFile(snapshotPath, "utf8"));
assert.equal(
snapshot.binding.analysis_profile.parameters
.selected_project_relationship_coverage,
"prior-analysis-unknown-v1",
);
const profile = snapshot.binding.analysis_profile;
const oldParameters = { ...profile.parameters };
delete oldParameters.selected_project_relationship_coverage;
const stalePath = join(workspace, "partial-old-profile.json");
await writeFile(
stalePath,
JSON.stringify({
...snapshot,
binding: {
...snapshot.binding,
analysis_profile: createAnalysisProfile(
profile.provider,
oldParameters,
),
},
entries: [],
workflow_entries: [],
evidence_bundle: createEvidenceBundle([]),
}),
);
const staleBefore = await readFile(stalePath);
const refused = await connection.client.callTool({
name: "open_binary",
arguments: {
path: target,
provider_id: "ghidra",
snapshot_path: stalePath,
},
});
const error = requireMcpToolError(refused);
assert.equal(error.code, "evidence_integrity_mismatch");
assert.deepEqual(error.details.binding_mismatch.profile_digest, {
snapshot: createAnalysisProfile(profile.provider, oldParameters)
.digest,
selected: profile.digest,
});
assert.deepEqual(await readFile(stalePath), staleBefore);
await call(connection, "open_binary", {
path: target,
provider_id: "ghidra",
snapshot_path: snapshotPath,
});
const replay = await call(connection, "trace_call_path", {
start: caller,
goal: leaf,
direction: "forward",
});
assert.deepEqual(
replay.normalized_result,
paths[0].normalized_result,
);
await call(connection, "close_binary");
} else await call(connection, "close_binary");
}, [() => connections.closeConnection(connection)]);
const offline = await connect(env);
await withVerifierCleanup(async () => {
await call(offline, "import_evidence_bundle", { path: bundlePath });
const records = (
await call(offline, "get_evidence_bundle", { detail: "complete" })
).result.records;
for (const evidence of paths) {
const saved = records.find(
(record) => record.evidence_id === evidence.evidence_id,
);
assert.ok(saved);
assert.deepEqual(saved.normalized_result, evidence.normalized_result);
assert.deepEqual(saved.limitations, evidence.limitations);
}
}, [() => connections.closeConnection(offline)]);
report.push({
mode,
statuses: paths.map(
(evidence) => evidence.normalized_result.goal_status,
),
});
}
assert.deepEqual(
{
target: hash(await readFile(target)),
projects: await manifest(projects),
},
before,
);
return {
ok: true,
provider: installation.providerVersion,
results: report,
};
}, [
async () => {
await connections.close();
if (!commandCleanupIncomplete)
await rm(workspace, { recursive: true, force: true });
},
]);
return result;
}
if (
process.argv[1] !== undefined &&
realpathSync(process.argv[1]) === fileURLToPath(import.meta.url)
) {
const run = createVerifierRun();
const report = await verifyProjectCoverage(run);
await completeVerifierRun(run);
process.stdout.write(`${JSON.stringify(report)}\n`);
}
+7
View File
@@ -1,5 +1,7 @@
#!/usr/bin/env node
import { verifyProjectCoverage } from "./verify-real-ghidra-project-coverage.mjs";
import { execFile } from "node:child_process";
import { mkdtemp, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
@@ -320,6 +322,10 @@ try {
const customPath = process.env.GHIDRA_TARGET_PATH;
const custom =
customPath === undefined ? null : await verifyTarget(customPath, "custom");
const projectCoverage =
!crossFormat && !aarch64JumpTableOnly && process.platform !== "win32"
? await verifyProjectCoverage(verifierRun)
: null;
report = {
ok: true,
provider: { id: "ghidra", version: SUPPORTED_GHIDRA_VERSION },
@@ -340,6 +346,7 @@ try {
? (crossResults[0]?.native_api_cli ?? null)
: jumpTable.native_api_cli,
native_value_e2e: debug.native_values,
selected_project_coverage: projectCoverage,
relocation_symbols: relocationSymbols,
custom_target: custom === null ? null : summary(custom),
cleanup: "complete",
+1
View File
@@ -485,6 +485,7 @@ export const existingGhidraAnalysisProfile = (
...(mipsElf === undefined ? {} : { mips_elf: mipsElf }),
import_mode: "existing-project-private-copy-v1",
...sharedAnalysisSemantics,
selected_project_relationship_coverage: "prior-analysis-unknown-v1",
annotation_policy: "read-only-existing-project-v1",
existing_project: project,
original_analysis: { completeness: "unknown", settings: "unknown" },
@@ -0,0 +1,74 @@
import { readFile } from "node:fs/promises";
import { join } from "node:path";
import { fileURLToPath } from "node:url";
import { Client, type Transport } from "@modelcontextprotocol/client";
import { expect, it } from "vitest";
import {
createVerifierMcpOwner,
VerifierStdioTransport,
} from "../../../scripts/lib/verifier-mcp-owner.mjs";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
it.each(["initialize", "discovery"])(
"closes the real MCP child when %s fails before ownership transfers",
async (mode) => {
const workspace = await createTestTempDirectory("rea-verifier-mcp-owner-");
const marker = join(workspace, "exit.txt");
const client = new Client({ name: "verifier-owner-test", version: "1" });
const transport = new VerifierStdioTransport({
command: process.execPath,
args: [
fileURLToPath(
new URL("../../fixtures/verifier-mcp-failure.mjs", import.meta.url),
),
],
env: {
PATH: process.env.PATH ?? "",
REA_TEST_EXIT_MARKER: marker,
REA_TEST_FAILURE_MODE: mode,
},
stderr: "pipe",
});
const owner = createVerifierMcpOwner();
try {
await expect(owner.open(client, transport)).rejects.toThrow();
expect(await readFile(marker, "utf8")).toBe("exited");
} finally {
await owner.close();
}
},
);
it("retains an owner after failed cleanup and retries every close", async () => {
const failure = new Error("discovery failed");
const cleanupFailure = new Error("client close failed");
let clientCloses = 0;
let transportCloses = 0;
const client = {
connect: async () => {},
listTools: async () => {
throw failure;
},
close: async () => {
if (++clientCloses === 1) throw cleanupFailure;
},
} as unknown as Client;
const transport = {
close: async () => {
transportCloses++;
},
} as unknown as Transport;
const owner = createVerifierMcpOwner();
let thrown: unknown;
try {
await owner.open(client, transport);
} catch (cause) {
thrown = cause;
}
expect(thrown).toBeInstanceOf(AggregateError);
expect((thrown as AggregateError).cause).toBe(failure);
expect(transportCloses).toBe(1);
await owner.close();
expect(clientCloses).toBe(2);
expect(transportCloses).toBe(2);
});
@@ -0,0 +1,26 @@
import ghidra.app.script.GhidraScript;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSet;
import ghidra.program.model.listing.Function;
import ghidra.program.model.symbol.SourceType;
import ghidra.program.model.symbol.Symbol;
import java.util.List;
// Create addressable entries without decoding their instructions or analyzing calls.
public class ReaSelectedProjectSeeds extends GhidraScript {
@Override public void run() throws Exception {
for (String name : List.of("rea_project_leaf", "rea_project_caller", "rea_project_other")) {
List<Symbol> symbols = getSymbols(name, null);
if (symbols.isEmpty()) symbols = getSymbols("_" + name, null);
if (symbols.size() != 1) throw new IllegalStateException("Expected one symbol for " + name);
Address entry = symbols.get(0).getAddress();
Function function = getFunctionAt(entry);
if (function == null)
function = currentProgram.getFunctionManager().createFunction(
symbols.get(0).getName(), entry, new AddressSet(entry, entry), SourceType.USER_DEFINED);
if (currentProgram.getListing().getInstructions(function.getBody(), true).hasNext())
throw new IllegalStateException("Expected an unanalyzed function: " + name);
println("REA_PROJECT_SEED " + name + " " + entry);
}
}
}
@@ -0,0 +1,14 @@
volatile int rea_project_global = 7;
__attribute__((noinline, used)) int rea_project_leaf(int x) {
return x + rea_project_global;
}
__attribute__((noinline, used)) int rea_project_caller(int x) {
return rea_project_leaf(x);
}
__attribute__((noinline, used)) int rea_project_other(int x) {
return x - rea_project_global;
}
__attribute__((noinline, used)) int rea_project_callback(int (*fn)(int), int x) {
return fn(x) + rea_project_global;
}
int main(void) { return rea_project_caller(rea_project_global); }
+36
View File
@@ -0,0 +1,36 @@
import { writeFileSync } from "node:fs";
import { createInterface } from "node:readline";
const marker = process.env.REA_TEST_EXIT_MARKER;
const mode = process.env.REA_TEST_FAILURE_MODE;
if (!marker || !mode) throw new Error("Missing verifier fixture configuration");
const keepAlive = setInterval(() => {}, 1000);
// The test harness selects its own temporary exit marker.
process.on("exit", () => writeFileSync(marker, "exited"));
process.stdin.on("end", () => clearInterval(keepAlive));
const input = createInterface({ input: process.stdin });
input.on("line", (line) => {
const request = JSON.parse(line);
if (request.id === undefined) return;
if (request.method === "initialize" && mode !== "initialize") {
process.stdout.write(
`${JSON.stringify({
jsonrpc: "2.0",
id: request.id,
result: {
protocolVersion: request.params.protocolVersion,
capabilities: { tools: {} },
serverInfo: { name: "verifier-failure-fixture", version: "1" },
},
})}\n`,
);
return;
}
process.stdout.write(
`${JSON.stringify({
jsonrpc: "2.0",
id: request.id,
error: { code: -32603, message: `intentional ${mode} failure` },
})}\n`,
);
});