mirror of
https://github.com/morluto/rea.git
synced 2026-10-26 17:35:11 +00:00
fix(verify): own asynchronous Ghidra prerequisite inspection
Await installation inspection through one shared helper with a real resource scope at all 13 verifier call sites. Retain exact close owners, complete cleanup, and preserve primary plus cleanup failures. Keep compiler-only loading lazy and use the namespace workflow's selected environment. Add admission contract and native-file lifecycle controls, plus a real script prerequisite rejection check. Controlled Java/header observations do not qualify a real Ghidra engine. Combined verification selection: 122 tests passed across five files; static/format/discovery passed; two scoped source reviews clear. Refs #4912
This commit is contained in:
1 parent
823521085a
commit
7d79a6a85b
16 files changed
+245
-29
No files matched your search
@@ -0,0 +1,11 @@
|
|||||||
|
import type {
|
||||||
|
GhidraInstallationHost,
|
||||||
|
GhidraInstallationInspection,
|
||||||
|
GhidraInstallationOptions,
|
||||||
|
} from "../../src/ghidra/GhidraInstallation.js";
|
||||||
|
|
||||||
|
/** Inspect prerequisites with a real owner, including cleanup before completion. */
|
||||||
|
export function inspectVerifierGhidraInstallation(
|
||||||
|
options: Omit<GhidraInstallationOptions, "resourceOwner">,
|
||||||
|
host?: GhidraInstallationHost,
|
||||||
|
): Promise<GhidraInstallationInspection>;
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { ArtifactResourceScope } from "../../dist/artifacts/ArtifactResourceScope.js";
|
||||||
|
import { AnalysisCleanupLease } from "../../dist/artifacts/AnalysisCleanupLease.js";
|
||||||
|
import { inspectGhidraInstallation } from "../../dist/ghidra/GhidraInstallation.js";
|
||||||
|
import { closeVerifierResources } from "./verifier-cleanup.mjs";
|
||||||
|
|
||||||
|
/** Complete one prerequisite inspection and release its exact retained owners. */
|
||||||
|
export async function inspectVerifierGhidraInstallation(options, host) {
|
||||||
|
const resources = new ArtifactResourceScope();
|
||||||
|
const resourceOwner = {
|
||||||
|
retain(owner, resource) {
|
||||||
|
resources.retain({
|
||||||
|
kind: "lease",
|
||||||
|
resource,
|
||||||
|
lease: AnalysisCleanupLease.forCloseOwner(owner, resource),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
run: (operation) => resources.run(operation),
|
||||||
|
};
|
||||||
|
let primaryError;
|
||||||
|
try {
|
||||||
|
return await inspectGhidraInstallation({ ...options, resourceOwner }, host);
|
||||||
|
} catch (cause) {
|
||||||
|
primaryError = cause;
|
||||||
|
throw cause;
|
||||||
|
} finally {
|
||||||
|
await closeVerifierResources([() => resources.close()], primaryError);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,7 +7,7 @@ import { tmpdir } from "node:os";
|
|||||||
import { basename, dirname, join } from "node:path";
|
import { basename, dirname, join } from "node:path";
|
||||||
import { fileURLToPath } from "node:url";
|
import { fileURLToPath } from "node:url";
|
||||||
import { promisify } from "node:util";
|
import { promisify } from "node:util";
|
||||||
import { inspectGhidraInstallation } from "../../dist/ghidra/GhidraInstallation.js";
|
import { inspectVerifierGhidraInstallation } from "./ghidra-installation-inspection.mjs";
|
||||||
|
|
||||||
/** Exercise qualified annotation names against real demangled C++ ABI symbols. */
|
/** Exercise qualified annotation names against real demangled C++ ABI symbols. */
|
||||||
export async function verifyGhidraNamespaceAnnotations({
|
export async function verifyGhidraNamespaceAnnotations({
|
||||||
@@ -353,8 +353,8 @@ async function verifyAddressNames(call, symbols, kind) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function requireNamespaceDemangler(env) {
|
async function requireNamespaceDemangler(env) {
|
||||||
const inspection = inspectGhidraInstallation({
|
const inspection = await inspectVerifierGhidraInstallation({
|
||||||
environment: process.env,
|
environment: env,
|
||||||
installDir: env.GHIDRA_INSTALL_DIR,
|
installDir: env.GHIDRA_INSTALL_DIR,
|
||||||
...(env.JAVA_HOME === undefined ? {} : { javaHome: env.JAVA_HOME }),
|
...(env.JAVA_HOME === undefined ? {} : { javaHome: env.JAVA_HOME }),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { Client } from "@modelcontextprotocol/client";
|
|||||||
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
|
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
|
||||||
import { parseEvidence } from "../dist/domain/evidence.js";
|
import { parseEvidence } from "../dist/domain/evidence.js";
|
||||||
import { functionDossierSchema } from "../dist/domain/hopperValues.js";
|
import { functionDossierSchema } from "../dist/domain/hopperValues.js";
|
||||||
import { inspectGhidraInstallation } from "../dist/ghidra/GhidraInstallation.js";
|
import { inspectVerifierGhidraInstallation } from "./lib/ghidra-installation-inspection.mjs";
|
||||||
import { buildDosComFixture } from "../tests/conformance/ghidra/dos-com-fixture.mjs";
|
import { buildDosComFixture } from "../tests/conformance/ghidra/dos-com-fixture.mjs";
|
||||||
import { createVerifierRun, completeVerifierRun } from "./lib/verifier-run.mjs";
|
import { createVerifierRun, completeVerifierRun } from "./lib/verifier-run.mjs";
|
||||||
import { verifyGhidraAddressBoundaries } from "./lib/real-ghidra-address-boundaries.mjs";
|
import { verifyGhidraAddressBoundaries } from "./lib/real-ghidra-address-boundaries.mjs";
|
||||||
@@ -28,7 +28,7 @@ if (process.env.GHIDRA_INSTALL_DIR === undefined)
|
|||||||
throw new Error(
|
throw new Error(
|
||||||
"verify:ghidra:com prerequisite missing: GHIDRA_INSTALL_DIR (bring your own Ghidra).",
|
"verify:ghidra:com prerequisite missing: GHIDRA_INSTALL_DIR (bring your own Ghidra).",
|
||||||
);
|
);
|
||||||
const installation = inspectGhidraInstallation({
|
const installation = await inspectVerifierGhidraInstallation({
|
||||||
environment: process.env,
|
environment: process.env,
|
||||||
installDir: process.env.GHIDRA_INSTALL_DIR,
|
installDir: process.env.GHIDRA_INSTALL_DIR,
|
||||||
...(process.env.JAVA_HOME === undefined
|
...(process.env.JAVA_HOME === undefined
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import {
|
|||||||
functionBodySchema,
|
functionBodySchema,
|
||||||
functionDossierSchema,
|
functionDossierSchema,
|
||||||
} from "../dist/domain/hopperValues.js";
|
} from "../dist/domain/hopperValues.js";
|
||||||
import { inspectGhidraInstallation } from "../dist/ghidra/GhidraInstallation.js";
|
import { inspectVerifierGhidraInstallation } from "./lib/ghidra-installation-inspection.mjs";
|
||||||
import { buildDosMzFixture } from "../tests/conformance/ghidra/dos-mz-fixture.mjs";
|
import { buildDosMzFixture } from "../tests/conformance/ghidra/dos-mz-fixture.mjs";
|
||||||
import {
|
import {
|
||||||
assertVerifierCleanupComplete,
|
assertVerifierCleanupComplete,
|
||||||
@@ -42,7 +42,7 @@ if (process.env.GHIDRA_INSTALL_DIR === undefined)
|
|||||||
throw new Error(
|
throw new Error(
|
||||||
"verify:ghidra:dos prerequisite missing: GHIDRA_INSTALL_DIR (bring your own Ghidra).",
|
"verify:ghidra:dos prerequisite missing: GHIDRA_INSTALL_DIR (bring your own Ghidra).",
|
||||||
);
|
);
|
||||||
const installation = inspectGhidraInstallation({
|
const installation = await inspectVerifierGhidraInstallation({
|
||||||
environment: process.env,
|
environment: process.env,
|
||||||
installDir: process.env.GHIDRA_INSTALL_DIR,
|
installDir: process.env.GHIDRA_INSTALL_DIR,
|
||||||
...(process.env.JAVA_HOME === undefined
|
...(process.env.JAVA_HOME === undefined
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import { parseBinaryTarget } from "../dist/application/BinaryTargetResolver.js";
|
|||||||
import { parseEvidence } from "../dist/domain/evidence.js";
|
import { parseEvidence } from "../dist/domain/evidence.js";
|
||||||
import { parseAnalysisSnapshot } from "../dist/domain/analysisSnapshot.js";
|
import { parseAnalysisSnapshot } from "../dist/domain/analysisSnapshot.js";
|
||||||
import { functionDossierSchema } from "../dist/domain/hopperValues.js";
|
import { functionDossierSchema } from "../dist/domain/hopperValues.js";
|
||||||
import { inspectGhidraInstallation } from "../dist/ghidra/GhidraInstallation.js";
|
import { inspectVerifierGhidraInstallation } from "./lib/ghidra-installation-inspection.mjs";
|
||||||
import { buildMipsRelocatableFixture } from "../tests/conformance/ghidra/mips-fixture.mjs";
|
import { buildMipsRelocatableFixture } from "../tests/conformance/ghidra/mips-fixture.mjs";
|
||||||
import {
|
import {
|
||||||
assertMipsRelocations,
|
assertMipsRelocations,
|
||||||
@@ -34,7 +34,7 @@ if (!["linux", "darwin"].includes(process.platform))
|
|||||||
);
|
);
|
||||||
if (process.env.GHIDRA_INSTALL_DIR === undefined)
|
if (process.env.GHIDRA_INSTALL_DIR === undefined)
|
||||||
throw new Error("Missing prerequisite: caller-supplied GHIDRA_INSTALL_DIR");
|
throw new Error("Missing prerequisite: caller-supplied GHIDRA_INSTALL_DIR");
|
||||||
const installation = inspectGhidraInstallation({
|
const installation = await inspectVerifierGhidraInstallation({
|
||||||
environment: process.env,
|
environment: process.env,
|
||||||
installDir: process.env.GHIDRA_INSTALL_DIR,
|
installDir: process.env.GHIDRA_INSTALL_DIR,
|
||||||
...(process.env.JAVA_HOME === undefined
|
...(process.env.JAVA_HOME === undefined
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import {
|
|||||||
import { parseEvidence } from "../dist/domain/evidence.js";
|
import { parseEvidence } from "../dist/domain/evidence.js";
|
||||||
import { parseAnalysisSnapshot } from "../dist/domain/analysisSnapshot.js";
|
import { parseAnalysisSnapshot } from "../dist/domain/analysisSnapshot.js";
|
||||||
import { functionDossierSchema } from "../dist/domain/hopperValues.js";
|
import { functionDossierSchema } from "../dist/domain/hopperValues.js";
|
||||||
import { inspectGhidraInstallation } from "../dist/ghidra/GhidraInstallation.js";
|
import { inspectVerifierGhidraInstallation } from "./lib/ghidra-installation-inspection.mjs";
|
||||||
import { buildMipsFixture } from "../tests/conformance/ghidra/mips-fixture.mjs";
|
import { buildMipsFixture } from "../tests/conformance/ghidra/mips-fixture.mjs";
|
||||||
import {
|
import {
|
||||||
buildPspFixture,
|
buildPspFixture,
|
||||||
@@ -50,7 +50,7 @@ if (!["linux", "darwin"].includes(process.platform))
|
|||||||
);
|
);
|
||||||
if (process.env.GHIDRA_INSTALL_DIR === undefined)
|
if (process.env.GHIDRA_INSTALL_DIR === undefined)
|
||||||
throw new Error("Missing prerequisite: caller-supplied GHIDRA_INSTALL_DIR");
|
throw new Error("Missing prerequisite: caller-supplied GHIDRA_INSTALL_DIR");
|
||||||
const installation = inspectGhidraInstallation({
|
const installation = await inspectVerifierGhidraInstallation({
|
||||||
environment: process.env,
|
environment: process.env,
|
||||||
installDir: process.env.GHIDRA_INSTALL_DIR,
|
installDir: process.env.GHIDRA_INSTALL_DIR,
|
||||||
...(process.env.JAVA_HOME === undefined
|
...(process.env.JAVA_HOME === undefined
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { promisify } from "node:util";
|
|||||||
import { parseBinaryTarget } from "../dist/application/BinaryTargetResolver.js";
|
import { parseBinaryTarget } from "../dist/application/BinaryTargetResolver.js";
|
||||||
import { parseConfig } from "../dist/config/parseConfig.js";
|
import { parseConfig } from "../dist/config/parseConfig.js";
|
||||||
import { GhidraClient } from "../dist/ghidra/GhidraClient.js";
|
import { GhidraClient } from "../dist/ghidra/GhidraClient.js";
|
||||||
import { inspectGhidraInstallation } from "../dist/ghidra/GhidraInstallation.js";
|
import { inspectVerifierGhidraInstallation } from "./lib/ghidra-installation-inspection.mjs";
|
||||||
import { SUPPORTED_GHIDRA_VERSION } from "../dist/ghidra/GhidraReleasePolicy.js";
|
import { SUPPORTED_GHIDRA_VERSION } from "../dist/ghidra/GhidraReleasePolicy.js";
|
||||||
import { GhidraProvider } from "../dist/ghidra/GhidraProvider.js";
|
import { GhidraProvider } from "../dist/ghidra/GhidraProvider.js";
|
||||||
import { silentLogger } from "../dist/logger.js";
|
import { silentLogger } from "../dist/logger.js";
|
||||||
@@ -35,7 +35,7 @@ assert.ok(
|
|||||||
);
|
);
|
||||||
const config = parseConfig(process.env);
|
const config = parseConfig(process.env);
|
||||||
if (!config.ok) throw config.error;
|
if (!config.ok) throw config.error;
|
||||||
const installation = inspectGhidraInstallation({
|
const installation = await inspectVerifierGhidraInstallation({
|
||||||
environment: process.env,
|
environment: process.env,
|
||||||
installDir: config.value.ghidraInstallDir,
|
installDir: config.value.ghidraInstallDir,
|
||||||
javaHome: config.value.ghidraJavaHome,
|
javaHome: config.value.ghidraJavaHome,
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import {
|
|||||||
import { parseEvidence } from "../dist/domain/evidence.js";
|
import { parseEvidence } from "../dist/domain/evidence.js";
|
||||||
import { parseAnalysisSnapshot } from "../dist/domain/analysisSnapshot.js";
|
import { parseAnalysisSnapshot } from "../dist/domain/analysisSnapshot.js";
|
||||||
import { functionDossierSchema } from "../dist/domain/hopperValues.js";
|
import { functionDossierSchema } from "../dist/domain/hopperValues.js";
|
||||||
import { inspectGhidraInstallation } from "../dist/ghidra/GhidraInstallation.js";
|
import { inspectVerifierGhidraInstallation } from "./lib/ghidra-installation-inspection.mjs";
|
||||||
import { buildPowerPcFixture } from "../tests/conformance/ghidra/ppc-fixture.mjs";
|
import { buildPowerPcFixture } from "../tests/conformance/ghidra/ppc-fixture.mjs";
|
||||||
import { requireMcpOperationResult } from "./lib/mcp-verifier-results.mjs";
|
import { requireMcpOperationResult } from "./lib/mcp-verifier-results.mjs";
|
||||||
import { createVerifierRun, completeVerifierRun } from "./lib/verifier-run.mjs";
|
import { createVerifierRun, completeVerifierRun } from "./lib/verifier-run.mjs";
|
||||||
@@ -33,7 +33,7 @@ if (!["linux", "darwin"].includes(process.platform))
|
|||||||
);
|
);
|
||||||
if (process.env.GHIDRA_INSTALL_DIR === undefined)
|
if (process.env.GHIDRA_INSTALL_DIR === undefined)
|
||||||
throw new Error("Missing prerequisite: caller-supplied GHIDRA_INSTALL_DIR");
|
throw new Error("Missing prerequisite: caller-supplied GHIDRA_INSTALL_DIR");
|
||||||
const installation = inspectGhidraInstallation({
|
const installation = await inspectVerifierGhidraInstallation({
|
||||||
environment: process.env,
|
environment: process.env,
|
||||||
installDir: process.env.GHIDRA_INSTALL_DIR,
|
installDir: process.env.GHIDRA_INSTALL_DIR,
|
||||||
...(process.env.JAVA_HOME === undefined
|
...(process.env.JAVA_HOME === undefined
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { Client } from "@modelcontextprotocol/client";
|
|||||||
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
|
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
|
||||||
|
|
||||||
import { parseEvidence } from "../dist/domain/evidence.js";
|
import { parseEvidence } from "../dist/domain/evidence.js";
|
||||||
import { inspectGhidraInstallation } from "../dist/ghidra/GhidraInstallation.js";
|
import { inspectVerifierGhidraInstallation } from "./lib/ghidra-installation-inspection.mjs";
|
||||||
import { requireMcpOperationResult } from "./lib/mcp-verifier-results.mjs";
|
import { requireMcpOperationResult } from "./lib/mcp-verifier-results.mjs";
|
||||||
import { createVerifierRun, completeVerifierRun } from "./lib/verifier-run.mjs";
|
import { createVerifierRun, completeVerifierRun } from "./lib/verifier-run.mjs";
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@ if (process.env.GHIDRA_INSTALL_DIR === undefined)
|
|||||||
throw new Error(
|
throw new Error(
|
||||||
"Set GHIDRA_INSTALL_DIR to a bring-your-own Ghidra installation.",
|
"Set GHIDRA_INSTALL_DIR to a bring-your-own Ghidra installation.",
|
||||||
);
|
);
|
||||||
const installation = inspectGhidraInstallation({
|
const installation = await inspectVerifierGhidraInstallation({
|
||||||
environment: process.env,
|
environment: process.env,
|
||||||
installDir: process.env.GHIDRA_INSTALL_DIR,
|
installDir: process.env.GHIDRA_INSTALL_DIR,
|
||||||
...(process.env.JAVA_HOME === undefined
|
...(process.env.JAVA_HOME === undefined
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import { parseBinaryTarget } from "../dist/application/BinaryTargetResolver.js";
|
|||||||
import { parseAnalysisSnapshot } from "../dist/domain/analysisSnapshot.js";
|
import { parseAnalysisSnapshot } from "../dist/domain/analysisSnapshot.js";
|
||||||
import { parseEvidence } from "../dist/domain/evidence.js";
|
import { parseEvidence } from "../dist/domain/evidence.js";
|
||||||
import { functionDossierSchema } from "../dist/domain/hopperValues.js";
|
import { functionDossierSchema } from "../dist/domain/hopperValues.js";
|
||||||
import { inspectGhidraInstallation } from "../dist/ghidra/GhidraInstallation.js";
|
import { inspectVerifierGhidraInstallation } from "./lib/ghidra-installation-inspection.mjs";
|
||||||
import { buildRiscvFixture } from "../tests/conformance/ghidra/riscv-fixture.mjs";
|
import { buildRiscvFixture } from "../tests/conformance/ghidra/riscv-fixture.mjs";
|
||||||
import {
|
import {
|
||||||
mcpTextValue,
|
mcpTextValue,
|
||||||
@@ -42,7 +42,7 @@ for (const key of ["REA_GHIDRA_LANGUAGE_ID", "REA_GHIDRA_COMPILER_SPEC_ID"])
|
|||||||
throw new Error(
|
throw new Error(
|
||||||
`Unset ${key}: this lane verifies provider-pinned selection.`,
|
`Unset ${key}: this lane verifies provider-pinned selection.`,
|
||||||
);
|
);
|
||||||
const installation = inspectGhidraInstallation({
|
const installation = await inspectVerifierGhidraInstallation({
|
||||||
environment: process.env,
|
environment: process.env,
|
||||||
installDir: process.env.GHIDRA_INSTALL_DIR,
|
installDir: process.env.GHIDRA_INSTALL_DIR,
|
||||||
...(process.env.JAVA_HOME === undefined
|
...(process.env.JAVA_HOME === undefined
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import { fileURLToPath } from "node:url";
|
|||||||
import { promisify } from "node:util";
|
import { promisify } from "node:util";
|
||||||
import { Client } from "@modelcontextprotocol/client";
|
import { Client } from "@modelcontextprotocol/client";
|
||||||
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
|
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
|
||||||
import { inspectGhidraInstallation } from "../dist/ghidra/GhidraInstallation.js";
|
import { inspectVerifierGhidraInstallation } from "./lib/ghidra-installation-inspection.mjs";
|
||||||
import { cleanupOwnedProcessGroup } from "../dist/process/ProcessOwnership.js";
|
import { cleanupOwnedProcessGroup } from "../dist/process/ProcessOwnership.js";
|
||||||
import {
|
import {
|
||||||
ProviderProcessSupervisor,
|
ProviderProcessSupervisor,
|
||||||
@@ -27,7 +27,7 @@ import { createVerifierRun, completeVerifierRun } from "./lib/verifier-run.mjs";
|
|||||||
|
|
||||||
const execute = promisify(execFile);
|
const execute = promisify(execFile);
|
||||||
const run = createVerifierRun();
|
const run = createVerifierRun();
|
||||||
const installation = inspectGhidraInstallation({
|
const installation = await inspectVerifierGhidraInstallation({
|
||||||
environment: process.env,
|
environment: process.env,
|
||||||
installDir: process.env.GHIDRA_INSTALL_DIR,
|
installDir: process.env.GHIDRA_INSTALL_DIR,
|
||||||
javaHome: process.env.JAVA_HOME,
|
javaHome: process.env.JAVA_HOME,
|
||||||
|
|||||||
@@ -68,13 +68,13 @@ for (const [role, command] of Object.entries(commands)) {
|
|||||||
}
|
}
|
||||||
let installation;
|
let installation;
|
||||||
if (!compilerOnly) {
|
if (!compilerOnly) {
|
||||||
const { inspectGhidraInstallation } =
|
const { inspectVerifierGhidraInstallation } =
|
||||||
await import("../dist/ghidra/GhidraInstallation.js");
|
await import("./lib/ghidra-installation-inspection.mjs");
|
||||||
assert.ok(
|
assert.ok(
|
||||||
process.env.GHIDRA_INSTALL_DIR,
|
process.env.GHIDRA_INSTALL_DIR,
|
||||||
"Ghidra switch lane prerequisite missing: GHIDRA_INSTALL_DIR (bring your own)",
|
"Ghidra switch lane prerequisite missing: GHIDRA_INSTALL_DIR (bring your own)",
|
||||||
);
|
);
|
||||||
installation = inspectGhidraInstallation({
|
installation = await inspectVerifierGhidraInstallation({
|
||||||
environment: process.env,
|
environment: process.env,
|
||||||
installDir: process.env.GHIDRA_INSTALL_DIR,
|
installDir: process.env.GHIDRA_INSTALL_DIR,
|
||||||
...(process.env.JAVA_HOME === undefined
|
...(process.env.JAVA_HOME === undefined
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import {
|
|||||||
parseGhidraInventoryInput,
|
parseGhidraInventoryInput,
|
||||||
parseGhidraInventoryResult,
|
parseGhidraInventoryResult,
|
||||||
} from "../dist/ghidra/GhidraInventoryValues.js";
|
} from "../dist/ghidra/GhidraInventoryValues.js";
|
||||||
import { inspectGhidraInstallation } from "../dist/ghidra/GhidraInstallation.js";
|
import { inspectVerifierGhidraInstallation } from "./lib/ghidra-installation-inspection.mjs";
|
||||||
import { SUPPORTED_GHIDRA_VERSION } from "../dist/ghidra/GhidraReleasePolicy.js";
|
import { SUPPORTED_GHIDRA_VERSION } from "../dist/ghidra/GhidraReleasePolicy.js";
|
||||||
import { GhidraHeadlessLauncher } from "../dist/ghidra/GhidraLauncher.js";
|
import { GhidraHeadlessLauncher } from "../dist/ghidra/GhidraLauncher.js";
|
||||||
import { GHIDRA_PROVIDER_IDENTITY } from "../dist/ghidra/GhidraProviderCapabilities.js";
|
import { GHIDRA_PROVIDER_IDENTITY } from "../dist/ghidra/GhidraProviderCapabilities.js";
|
||||||
@@ -59,7 +59,7 @@ if (installDir === undefined || !isAbsolute(installDir))
|
|||||||
throw new Error(
|
throw new Error(
|
||||||
"Set GHIDRA_INSTALL_DIR to the absolute root of Ghidra 12.1.4.",
|
"Set GHIDRA_INSTALL_DIR to the absolute root of Ghidra 12.1.4.",
|
||||||
);
|
);
|
||||||
const installation = inspectGhidraInstallation({
|
const installation = await inspectVerifierGhidraInstallation({
|
||||||
environment: process.env,
|
environment: process.env,
|
||||||
installDir,
|
installDir,
|
||||||
...(process.env.JAVA_HOME === undefined
|
...(process.env.JAVA_HOME === undefined
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { promisify } from "node:util";
|
|||||||
import { parseBinaryTarget } from "../dist/application/BinaryTargetResolver.js";
|
import { parseBinaryTarget } from "../dist/application/BinaryTargetResolver.js";
|
||||||
import { resolveGhidraAnalysisProfile } from "../dist/ghidra/GhidraAnalysisProfile.js";
|
import { resolveGhidraAnalysisProfile } from "../dist/ghidra/GhidraAnalysisProfile.js";
|
||||||
import { GhidraClient } from "../dist/ghidra/GhidraClient.js";
|
import { GhidraClient } from "../dist/ghidra/GhidraClient.js";
|
||||||
import { inspectGhidraInstallation } from "../dist/ghidra/GhidraInstallation.js";
|
import { inspectVerifierGhidraInstallation } from "./lib/ghidra-installation-inspection.mjs";
|
||||||
import { SUPPORTED_GHIDRA_VERSION } from "../dist/ghidra/GhidraReleasePolicy.js";
|
import { SUPPORTED_GHIDRA_VERSION } from "../dist/ghidra/GhidraReleasePolicy.js";
|
||||||
import { GhidraHeadlessLauncher } from "../dist/ghidra/GhidraLauncher.js";
|
import { GhidraHeadlessLauncher } from "../dist/ghidra/GhidraLauncher.js";
|
||||||
import { GHIDRA_PROVIDER_IDENTITY } from "../dist/ghidra/GhidraProviderCapabilities.js";
|
import { GHIDRA_PROVIDER_IDENTITY } from "../dist/ghidra/GhidraProviderCapabilities.js";
|
||||||
@@ -49,7 +49,7 @@ if (installDir === undefined || !isAbsolute(installDir))
|
|||||||
throw new Error(
|
throw new Error(
|
||||||
"Set GHIDRA_INSTALL_DIR to the absolute root of an extracted Ghidra 12.1.4 release.",
|
"Set GHIDRA_INSTALL_DIR to the absolute root of an extracted Ghidra 12.1.4 release.",
|
||||||
);
|
);
|
||||||
const installation = inspectGhidraInstallation({
|
const installation = await inspectVerifierGhidraInstallation({
|
||||||
environment: process.env,
|
environment: process.env,
|
||||||
installDir,
|
installDir,
|
||||||
...(process.env.JAVA_HOME === undefined
|
...(process.env.JAVA_HOME === undefined
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import { spawnSync } from "node:child_process";
|
import { spawnSync } from "node:child_process";
|
||||||
import { createHash } from "node:crypto";
|
import { createHash } from "node:crypto";
|
||||||
import { readFile, writeFile } from "node:fs/promises";
|
import { readFile, readdir, writeFile } from "node:fs/promises";
|
||||||
import { join } from "node:path";
|
import { join } from "node:path";
|
||||||
|
|
||||||
import { expect, it } from "vitest";
|
import { expect, it } from "vitest";
|
||||||
|
import ts from "typescript6";
|
||||||
|
import type { inspectVerifierGhidraInstallation } from "../../../scripts/lib/ghidra-installation-inspection.mjs";
|
||||||
|
|
||||||
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
||||||
|
|
||||||
@@ -84,3 +86,178 @@ it("hashes and closes a clean verifier input", async () => {
|
|||||||
.digest("hex"),
|
.digest("hex"),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("Ghidra verifier probes await the shared owned inspection contract", async () => {
|
||||||
|
const names = (await readdir("scripts")).filter(
|
||||||
|
(name) => name.startsWith("verify-real-ghidra") && name.endsWith(".mjs"),
|
||||||
|
);
|
||||||
|
const paths = [
|
||||||
|
...names.map((name) => `scripts/${name}`),
|
||||||
|
"scripts/lib/real-ghidra-namespace-annotations.mjs",
|
||||||
|
];
|
||||||
|
const probes: string[] = [];
|
||||||
|
const violations: { path: string; reason: string }[] = [];
|
||||||
|
for (const path of paths) {
|
||||||
|
const source = ts.createSourceFile(
|
||||||
|
path,
|
||||||
|
await readFile(path, "utf8"),
|
||||||
|
ts.ScriptTarget.Latest,
|
||||||
|
true,
|
||||||
|
ts.ScriptKind.JS,
|
||||||
|
);
|
||||||
|
const visit = (node: ts.Node): void => {
|
||||||
|
if (
|
||||||
|
ts.isCallExpression(node) &&
|
||||||
|
ts.isIdentifier(node.expression) &&
|
||||||
|
[
|
||||||
|
"inspectGhidraInstallation",
|
||||||
|
"inspectVerifierGhidraInstallation",
|
||||||
|
].includes(node.expression.text)
|
||||||
|
) {
|
||||||
|
probes.push(path);
|
||||||
|
if (!ts.isAwaitExpression(node.parent))
|
||||||
|
violations.push({
|
||||||
|
path,
|
||||||
|
reason: "installation result was not awaited",
|
||||||
|
});
|
||||||
|
if (node.expression.text !== "inspectVerifierGhidraInstallation")
|
||||||
|
violations.push({
|
||||||
|
path,
|
||||||
|
reason: "inspection has no shared cleanup owner",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
ts.forEachChild(node, visit);
|
||||||
|
};
|
||||||
|
visit(source);
|
||||||
|
}
|
||||||
|
expect(probes.length).toBeGreaterThan(0);
|
||||||
|
expect(violations).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each(["late-close", "retry-close", "retained-close", "opaque-cancellation"])(
|
||||||
|
"owns Ghidra verifier prerequisite finalization: %s",
|
||||||
|
async (mode) => {
|
||||||
|
const root = await createTestTempDirectory("rea-verifier-ghidra-owner-");
|
||||||
|
const options = {
|
||||||
|
environment: {},
|
||||||
|
installDir: root,
|
||||||
|
platform: process.platform,
|
||||||
|
architecture: "x64",
|
||||||
|
} satisfies Parameters<typeof inspectVerifierGhidraInstallation>[0];
|
||||||
|
const script = String.raw`
|
||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import { mkdir, open, writeFile } from 'node:fs/promises';
|
||||||
|
import { join } from 'node:path';
|
||||||
|
import { inspectVerifierGhidraInstallation } from ${JSON.stringify(new URL("../../../scripts/lib/ghidra-installation-inspection.mjs", import.meta.url).href)};
|
||||||
|
import { systemGhidraInstallationHost } from ${JSON.stringify(new URL("../../../dist/ghidra/GhidraInstallation.js", import.meta.url).href)};
|
||||||
|
import { SUPPORTED_GHIDRA_VERSION, SUPPORTED_GHIDRA_JAVA_MAJOR } from ${JSON.stringify(new URL("../../../dist/ghidra/GhidraReleasePolicy.js", import.meta.url).href)};
|
||||||
|
import { OwnedFileHandle } from ${JSON.stringify(new URL("../../../dist/filesystem/OwnedFileHandle.js", import.meta.url).href)};
|
||||||
|
import { RegularFileCleanupFailure } from ${JSON.stringify(new URL("../../../dist/filesystem/OwnedRegularFile.js", import.meta.url).href)};
|
||||||
|
import { ArtifactResourceRetryFailure } from ${JSON.stringify(new URL("../../../dist/artifacts/ArtifactResourceScope.js", import.meta.url).href)};
|
||||||
|
const root = process.argv[1], mode = process.argv[2], options = JSON.parse(process.argv[3]);
|
||||||
|
await mkdir(join(root, 'Ghidra'));
|
||||||
|
await writeFile(join(root, 'Ghidra', 'application.properties'), 'application.version=' + SUPPORTED_GHIDRA_VERSION + '\napplication.java.min=' + SUPPORTED_GHIDRA_JAVA_MAJOR + '\n');
|
||||||
|
const controller = new AbortController();
|
||||||
|
let foreign;
|
||||||
|
if (mode === 'opaque-cancellation') {
|
||||||
|
const path = join(root, 'foreign'); await writeFile(path, 'foreign');
|
||||||
|
foreign = new OwnedFileHandle(await open(path, 'r'));
|
||||||
|
controller.abort(new RegularFileCleanupFailure(path, foreign, { kind: 'completed', value: 'opaque' }, new Error('not this inspection owner')));
|
||||||
|
}
|
||||||
|
let entered, release;
|
||||||
|
const entering = new Promise(resolve => { entered = resolve; });
|
||||||
|
const finishing = new Promise(resolve => { release = resolve; });
|
||||||
|
const handles = new Set();
|
||||||
|
let attempts = 0, denied = true;
|
||||||
|
const originalClose = OwnedFileHandle.prototype.close;
|
||||||
|
OwnedFileHandle.prototype.close = async function () {
|
||||||
|
handles.add(this.handle); attempts++;
|
||||||
|
if (mode === 'late-close' && attempts === 1) { entered(); await finishing; }
|
||||||
|
if ((mode === 'retry-close' && attempts === 1) || (mode === 'retained-close' && denied)) throw new Error('controlled inspection close refusal');
|
||||||
|
return originalClose.call(this);
|
||||||
|
};
|
||||||
|
const host = {
|
||||||
|
...systemGhidraInstallationHost(),
|
||||||
|
executable: () => true,
|
||||||
|
executableHeader: () => ({ ok: true, value: undefined }),
|
||||||
|
probeJava: async () => ({ ok: true, value: { major: SUPPORTED_GHIDRA_JAVA_MAJOR, version: '21.0.11', home: '/fixture/jdk', bits: 64, runtime: 'jdk', osArch: 'amd64', osName: options.platform === 'win32' ? 'Windows' : options.platform === 'darwin' ? 'Mac OS X' : 'Linux' } }),
|
||||||
|
};
|
||||||
|
let settled = false;
|
||||||
|
const pending = inspectVerifierGhidraInstallation({ ...options, signal: controller.signal }, host).then(
|
||||||
|
value => { settled = true; return { value }; },
|
||||||
|
error => { settled = true; return { error }; },
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
if (mode === 'late-close') { await entering; assert.equal(settled, false); release(); }
|
||||||
|
const outcome = await pending;
|
||||||
|
if (mode === 'late-close') { assert.equal(outcome.value?.status, 'available'); assert.equal(handles.size, 1); }
|
||||||
|
else if (mode === 'retry-close') {
|
||||||
|
assert.ok(outcome.error); assert.equal(outcome.error.cleanupIncomplete, false);
|
||||||
|
assert.ok(attempts >= 2); assert.equal(handles.size, 1);
|
||||||
|
} else if (mode === 'retained-close') {
|
||||||
|
assert.ok(outcome.error instanceof AggregateError);
|
||||||
|
const primary = outcome.error.errors.find(error => error.cleanupIncomplete === true);
|
||||||
|
const cleanup = outcome.error.errors.find(error => error instanceof ArtifactResourceRetryFailure);
|
||||||
|
assert.ok(primary); assert.ok(cleanup); assert.ok(cleanup.cleanupLeases.length > 0);
|
||||||
|
denied = false;
|
||||||
|
for (const lease of cleanup.cleanupLeases) await lease.release();
|
||||||
|
assert.equal(primary.cleanupIncomplete, false);
|
||||||
|
assert.equal(handles.size, 1);
|
||||||
|
} else {
|
||||||
|
assert.equal(outcome.error?._tag, 'AnalysisCancelledError');
|
||||||
|
assert.equal(outcome.error.cause, controller.signal.reason);
|
||||||
|
assert.equal(handles.size, 0); assert.ok(foreign.handle.fd >= 0);
|
||||||
|
}
|
||||||
|
for (const handle of handles) assert.equal(handle.fd, -1);
|
||||||
|
console.log(JSON.stringify({ mode, verified: true }));
|
||||||
|
} finally {
|
||||||
|
denied = false; release();
|
||||||
|
await pending;
|
||||||
|
OwnedFileHandle.prototype.close = originalClose;
|
||||||
|
for (const handle of handles) await handle.close();
|
||||||
|
await foreign?.close();
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
const result = spawnSync(
|
||||||
|
process.execPath,
|
||||||
|
[
|
||||||
|
"--input-type=module",
|
||||||
|
"-e",
|
||||||
|
script,
|
||||||
|
root,
|
||||||
|
mode,
|
||||||
|
JSON.stringify(options),
|
||||||
|
],
|
||||||
|
{ encoding: "utf8", timeout: 10_000 },
|
||||||
|
);
|
||||||
|
expect(result.status, `${result.stdout}\n${result.stderr}`).toBe(0);
|
||||||
|
expect(JSON.parse(result.stdout.trim())).toEqual({ mode, verified: true });
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
it("Ghidra verifier awaits native prerequisite rejection before creating target fixtures", async () => {
|
||||||
|
const root = await createTestTempDirectory(
|
||||||
|
"rea-verifier-ghidra-prerequisite-",
|
||||||
|
);
|
||||||
|
const result = spawnSync(
|
||||||
|
process.execPath,
|
||||||
|
["scripts/verify-real-ghidra.mjs"],
|
||||||
|
{
|
||||||
|
encoding: "utf8",
|
||||||
|
timeout: 10_000,
|
||||||
|
env: {
|
||||||
|
PATH: root,
|
||||||
|
HOME: root,
|
||||||
|
USERPROFILE: root,
|
||||||
|
GHIDRA_INSTALL_DIR: join(root, "absent-installation"),
|
||||||
|
JAVA_HOME: join(root, "absent-jdk"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
expect(result.status, result.stderr).toBe(1);
|
||||||
|
expect(result.stderr).toContain(
|
||||||
|
'Ghidra installation is unavailable: {"status":"unavailable"',
|
||||||
|
);
|
||||||
|
expect(result.stderr).not.toContain("TypeError");
|
||||||
|
expect(result.stderr).not.toContain("Ghidra installation is unavailable: {}");
|
||||||
|
});
|
||||||
Reference in new issue
Block a user