mirror of
https://github.com/morluto/rea.git
synced 2026-10-19 04:19:54 -05:00
fix(ghidra): consume inspected configuration directory identity (#4077)
* fix(ghidra): consume inspected configuration directory identity Carry the parsed release coordinate through installation inspection and provider prerequisites into the launcher. Remove duplicate property reads and basename guesses, preserve unresolved identity, and canonicalize directory versions without changing observed provider versions. Keep verifier callers, lifecycle regressions, and installation guidance aligned. Extract the Ghidra-only follow-up from #2293 without changing CLI output or unrelated documentation. * docs(ghidra): clarify startup failure side-effect boundary Distinguish the launcher's directory and Java startup guard from the client's earlier private runtime allocation and target snapshot. Keep existing failure cleanup and runtime behavior unchanged. --------- Co-authored-by: Kaoru0822 <208281322+kaoru0822-kitauji@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
16 files changed
+255
-57
No files matched your search
@@ -870,6 +870,21 @@ MCP clients do not depend on an incidental shell path. Setup shows every exact
|
||||
environment entry in its plan, writes only after approval, and never downloads,
|
||||
installs, upgrades, or modifies Ghidra or Java.
|
||||
|
||||
Installation inspection derives the private configuration directory identity from
|
||||
`application.version` and `application.release.name` in
|
||||
`Ghidra/application.properties`. The directory's version spelling follows Ghidra
|
||||
(for example, `12.1.0` becomes
|
||||
`12.1`), while the reported provider version preserves the original value.
|
||||
The launcher uses that inspected coordinate; it does not reread the properties
|
||||
or guess from the extracted folder's name.
|
||||
If the identity is missing, malformed, or unsafe as a single path component,
|
||||
the launcher fails before preparing its home/cache/config/temp directories or
|
||||
starting Java. The client may already have allocated private runtime/endpoint
|
||||
resources and a target snapshot; existing startup-failure cleanup still applies.
|
||||
Restore the selected release's properties instead of renaming its installation folder.
|
||||
This coordinate selects only REA's session-private configuration directory, not
|
||||
the installation or an existing user configuration directory.
|
||||
|
||||
Each verified session uses an ephemeral temporary project and isolated
|
||||
home/cache/config/temp paths. REA passes `-readOnly`, `-deleteProject`, uses
|
||||
Ghidra's default analysis settings, and loads its packaged Java
|
||||
|
||||
@@ -110,6 +110,7 @@ if (profile.value.profile.parameters.executable_role !== role)
|
||||
const client = new GhidraClient({
|
||||
platform: installation.platform,
|
||||
launcher: new GhidraHeadlessLauncher({
|
||||
configurationDirectoryName: installation.configurationDirectoryName,
|
||||
environment: process.env,
|
||||
analyzeHeadlessPath: installation.analyzeHeadlessPath,
|
||||
...(process.env.JAVA_HOME === undefined
|
||||
|
||||
@@ -489,6 +489,7 @@ async function verifyTarget(targetPath, variant, expectedTarget = null) {
|
||||
|
||||
const client = new GhidraClient({
|
||||
launcher: new GhidraHeadlessLauncher({
|
||||
configurationDirectoryName: installation.configurationDirectoryName,
|
||||
environment: process.env,
|
||||
analyzeHeadlessPath: installation.analyzeHeadlessPath,
|
||||
...(process.env.JAVA_HOME === undefined
|
||||
|
||||
@@ -471,6 +471,85 @@ describe("Ghidra installation rejection diagnostics", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("Ghidra configuration directory identity", () => {
|
||||
it.each([
|
||||
[
|
||||
"application.version=12.1.4\napplication.release.name=PUBLIC\n",
|
||||
"ghidra_12.1.4_PUBLIC",
|
||||
],
|
||||
[
|
||||
"application.version=12.1.2-2.1\napplication.release.name=DEV\n",
|
||||
"ghidra_12.1.2-2.1_DEV",
|
||||
],
|
||||
[
|
||||
"application.version=12.1.4\napplication.release.name=pub\\\n lic\n",
|
||||
"ghidra_12.1.4_PUBLIC",
|
||||
],
|
||||
["application.version=12.1.4\n", null],
|
||||
["application.release.name=PUBLIC\n", null],
|
||||
["application.version=12.1.4\napplication.release.name=\n", null],
|
||||
["application.version=12.1.4\napplication.release.name=../outside\n", null],
|
||||
[
|
||||
"application.version=12.1.4\napplication.release.name=..\\\\outside\n",
|
||||
null,
|
||||
],
|
||||
["application.version=../12.1.4\napplication.release.name=PUBLIC\n", null],
|
||||
[
|
||||
"application.version=12.1.4+build\napplication.release.name=PUBLIC\n",
|
||||
null,
|
||||
],
|
||||
["application.version=12.1.4\napplication.release.name=\\uNOTHEX\n", null],
|
||||
])("derives a safe release coordinate from %j", (content, expected) => {
|
||||
expect(ghidraApplicationProperties(content)).toMatchObject({
|
||||
configurationDirectoryName: expected,
|
||||
});
|
||||
});
|
||||
|
||||
it.each([
|
||||
["12.1.0", "ghidra_12.1_PUBLIC"],
|
||||
["012.01.000", "ghidra_12.1_PUBLIC"],
|
||||
["12.1.0-BETA", "ghidra_12.1-BETA_PUBLIC"],
|
||||
["12.1.0-", "ghidra_12.1_PUBLIC"],
|
||||
])(
|
||||
"uses Ghidra's canonical configuration version while retaining %s",
|
||||
(raw, expected) => {
|
||||
expect(
|
||||
ghidraApplicationProperties(
|
||||
`application.version=${raw}\napplication.release.name=PUBLIC\n`,
|
||||
),
|
||||
).toMatchObject({
|
||||
providerVersion: raw,
|
||||
configurationDirectoryName: expected,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
it.each([
|
||||
[
|
||||
"application.version=12.1.4\napplication.release.name=PUBLIC\napplication.java.min=21\n",
|
||||
"ghidra_12.1.4_PUBLIC",
|
||||
],
|
||||
["application.version=12.1.4\napplication.java.min=21\n", null],
|
||||
])(
|
||||
"preserves the inspected identity independently of the installation basename",
|
||||
(content, expected) => {
|
||||
const inspection = inspectGhidraInstallation(
|
||||
{
|
||||
environment: {},
|
||||
installDir: INSTALL,
|
||||
platform: "linux",
|
||||
architecture: "x64",
|
||||
},
|
||||
host({ readText: () => content }),
|
||||
);
|
||||
expect(inspection).toMatchObject({
|
||||
status: "available",
|
||||
configurationDirectoryName: expected,
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe("Ghidra release line and JDK bounds", () => {
|
||||
it("uses Java Properties last-key, delimiter, escape, and continuation rules", () => {
|
||||
const properties = ghidraApplicationProperties(
|
||||
@@ -486,6 +565,7 @@ describe("Ghidra release line and JDK bounds", () => {
|
||||
);
|
||||
expect(properties).toEqual({
|
||||
providerVersion: "12.1.4",
|
||||
configurationDirectoryName: null,
|
||||
javaMinimum: { status: "major", major: 21 },
|
||||
javaMaximum: { status: "absent" },
|
||||
parseError: null,
|
||||
|
||||
@@ -202,6 +202,8 @@ interface GhidraInstallationObservation {
|
||||
readonly platform: NodeJS.Platform;
|
||||
readonly architecture: NodeJS.Architecture;
|
||||
readonly applicationPropertiesPath: string | null;
|
||||
/** Parsed release identity, never inferred from the installation directory name. */
|
||||
readonly configurationDirectoryName: string | null;
|
||||
readonly nativeDecompilerPath: string | null;
|
||||
readonly javaCommand: string;
|
||||
/** Observed java.vm.name; omitted when the probe cannot establish VM identity. */
|
||||
@@ -351,6 +353,8 @@ export const inspectGhidraInstallation = (
|
||||
platform,
|
||||
architecture,
|
||||
applicationPropertiesPath: coordinates.applicationPropertiesPath,
|
||||
configurationDirectoryName:
|
||||
coordinates.properties?.configurationDirectoryName ?? null,
|
||||
nativeDecompilerPath: coordinates.nativeDecompilerPath,
|
||||
javaCommand: coordinates.javaCommand,
|
||||
...(coordinates.java?.vmName === undefined
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { parseGhidraJavaProperties } from "./GhidraJavaProperties.js";
|
||||
import { ghidraConfigurationVersion } from "./GhidraReleasePolicy.js";
|
||||
|
||||
/** One Java major bound declared by `application.properties`. */
|
||||
type GhidraJavaBound =
|
||||
@@ -18,6 +19,8 @@ export type GhidraJavaRequirement =
|
||||
/** Version and Java bounds read from one `application.properties` file. */
|
||||
export interface GhidraApplicationProperties {
|
||||
readonly providerVersion: string | null;
|
||||
/** Release-owned private settings directory; null when its identity is unresolved. */
|
||||
readonly configurationDirectoryName: string | null;
|
||||
readonly javaMinimum: GhidraJavaBound;
|
||||
readonly javaMaximum: GhidraJavaBound;
|
||||
/** A malformed Java properties escape invalidates the entire file. */
|
||||
@@ -32,15 +35,36 @@ export const ghidraApplicationProperties = (
|
||||
if (!parsed.ok)
|
||||
return {
|
||||
providerVersion: null,
|
||||
configurationDirectoryName: null,
|
||||
javaMinimum: { status: "unresolved", raw: parsed.error },
|
||||
javaMaximum: { status: "unresolved", raw: parsed.error },
|
||||
parseError: parsed.error,
|
||||
};
|
||||
const providerVersion = parsed.value.get("application.version");
|
||||
// ApplicationIdentifier removes Java's ASCII whitespace and uppercases the
|
||||
// release name. Derive only a single safe component for official Ghidra builds.
|
||||
const releaseName = parsed.value
|
||||
.get("application.release.name")
|
||||
?.replaceAll(/[ \t\n\r\f\v]/gu, "")
|
||||
.toUpperCase();
|
||||
// providerVersion above remains the original evidence.
|
||||
const configurationVersion =
|
||||
providerVersion === undefined
|
||||
? undefined
|
||||
: ghidraConfigurationVersion(providerVersion);
|
||||
const configurationDirectoryName =
|
||||
providerVersion !== undefined &&
|
||||
configurationVersion !== undefined &&
|
||||
releaseName !== undefined &&
|
||||
/^[A-Za-z0-9][A-Za-z0-9._+-]*$/u.test(providerVersion) &&
|
||||
/^[A-Za-z0-9][A-Za-z0-9._-]*$/u.test(releaseName)
|
||||
? `ghidra_${configurationVersion}_${releaseName}`
|
||||
: null;
|
||||
const minimum = parsed.value.get("application.java.min");
|
||||
const maximum = parsed.value.get("application.java.max");
|
||||
return {
|
||||
providerVersion: providerVersion ?? null,
|
||||
configurationDirectoryName,
|
||||
javaMinimum:
|
||||
minimum === undefined ? { status: "absent" } : parseJavaBound(minimum),
|
||||
// Ghidra uses an explicitly empty maximum to mean no upper bound.
|
||||
|
||||
@@ -42,6 +42,7 @@ const probeLauncher = (javaHome: string, runtimeRoot: string) =>
|
||||
environment: {},
|
||||
platform: "darwin",
|
||||
analyzeHeadlessPath: join(runtimeRoot, "support", "analyzeHeadless"),
|
||||
configurationDirectoryName: "ghidra_test_PUBLIC",
|
||||
javaHome,
|
||||
bridgeScriptPath: join(runtimeRoot, "bridge", "ReaGhidraBridge.java"),
|
||||
});
|
||||
@@ -152,6 +153,7 @@ describe("Ghidra launch failure diagnostics", () => {
|
||||
environment: {},
|
||||
platform: "darwin",
|
||||
analyzeHeadlessPath: join(runtimeRoot, "support", "analyzeHeadless"),
|
||||
configurationDirectoryName: "ghidra_test_PUBLIC",
|
||||
bridgeScriptPath: join(runtimeRoot, "bridge", "ReaGhidraBridge.java"),
|
||||
});
|
||||
const launched = await launcher.launch(launchSession(runtimeRoot));
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { parseGhidraJavaProperties } from "./GhidraJavaProperties.js";
|
||||
import { GHIDRA_BRIDGE_SOURCES } from "./GhidraBridgeSources.js";
|
||||
import {
|
||||
snapshotGhidraAnalysisSeeds,
|
||||
@@ -6,7 +5,7 @@ import {
|
||||
} from "./GhidraAnalysisSeeds.js";
|
||||
import type { GhidraLanguageOverride } from "../config/ghidraLanguageOverride.js";
|
||||
import { snapshotEnvironment } from "../process/snapshotEnvironment.js";
|
||||
import { mkdir, readFile, realpath } from "node:fs/promises";
|
||||
import { mkdir, realpath } from "node:fs/promises";
|
||||
import { basename, dirname, join, posix, win32 } from "node:path";
|
||||
|
||||
import writeFileAtomic from "write-file-atomic";
|
||||
@@ -149,6 +148,8 @@ const ghidraLaunchCommandOutput = (
|
||||
export interface GhidraHeadlessLauncherOptions {
|
||||
readonly environment: Readonly<NodeJS.ProcessEnv>;
|
||||
readonly analyzeHeadlessPath: string;
|
||||
/** Release coordinate owned by installation inspection; null must not be guessed. */
|
||||
readonly configurationDirectoryName: string | null;
|
||||
readonly javaHome?: string;
|
||||
readonly javaVmName?: string;
|
||||
readonly bridgeScriptPath: string;
|
||||
@@ -200,6 +201,10 @@ export class GhidraHeadlessLauncher implements GhidraLauncher {
|
||||
let configuredMaxHeap: string | undefined;
|
||||
const launchSupport: GhidraLaunchSupportDiagnostic[] = [];
|
||||
try {
|
||||
if (this.options.configurationDirectoryName === null)
|
||||
throw new GhidraLaunchError(
|
||||
"Ghidra configuration directory identity is unresolved; restore application.version and application.release.name in the selected installation's application.properties.",
|
||||
);
|
||||
const psp = this.options.pspExtension;
|
||||
if (psp !== undefined) {
|
||||
const installedRoot = await realpath(
|
||||
@@ -228,7 +233,7 @@ export class GhidraHeadlessLauncher implements GhidraLauncher {
|
||||
await createGhidraRuntimeDirectories(
|
||||
paths,
|
||||
platform,
|
||||
await ghidraConfigReleaseDirectory(this.options.analyzeHeadlessPath),
|
||||
this.options.configurationDirectoryName,
|
||||
);
|
||||
const seedPath =
|
||||
this.options.analysisSeeds === undefined
|
||||
@@ -754,32 +759,6 @@ const ghidraRuntimePaths = (runtimeRoot: string) => ({
|
||||
* that no supported JDK was found. Create the release directory while this
|
||||
* process still owns the runtime, leaving Ghidra only a file write inside it.
|
||||
*/
|
||||
const ghidraConfigReleaseDirectory = async (
|
||||
analyzeHeadlessPath: string,
|
||||
): Promise<string> => {
|
||||
const installRoot = dirname(dirname(analyzeHeadlessPath));
|
||||
try {
|
||||
const properties = await readFile(
|
||||
join(installRoot, "Ghidra", "application.properties"),
|
||||
"latin1",
|
||||
);
|
||||
const parsed = parseGhidraJavaProperties(properties);
|
||||
if (!parsed.ok) return basename(installRoot);
|
||||
const version = parsed.value.get("application.version");
|
||||
const release = parsed.value.get("application.release.name");
|
||||
return version === undefined ||
|
||||
release === undefined ||
|
||||
release.length === 0
|
||||
? basename(installRoot)
|
||||
: `ghidra_${version}_${release}`;
|
||||
} catch (cause: unknown) {
|
||||
// An unreadable properties file leaves the release coordinate unknown; the
|
||||
// conventional extraction directory carries the same name.
|
||||
void cause;
|
||||
return basename(installRoot);
|
||||
}
|
||||
};
|
||||
|
||||
const createGhidraRuntimeDirectories = async (
|
||||
paths: ReturnType<typeof ghidraRuntimePaths>,
|
||||
platform: NodeJS.Platform,
|
||||
|
||||
@@ -46,6 +46,7 @@ it("uses selected mixed-case Windows PATH and ComSpec through direct launch boun
|
||||
environment,
|
||||
platform: "win32",
|
||||
analyzeHeadlessPath: "C:\\ghidra\\support\\analyzeHeadless.bat",
|
||||
configurationDirectoryName: "ghidra_12.1.4_PUBLIC",
|
||||
bridgeScriptPath: "C:\\rea\\bridge.py",
|
||||
});
|
||||
environment.ComSpec = "C:\\changed\\cmd.exe";
|
||||
|
||||
@@ -149,6 +149,30 @@ it.each([
|
||||
},
|
||||
);
|
||||
|
||||
it("carries the inspected release identity into the production launcher", async () => {
|
||||
const launchers: GhidraHeadlessLauncher[] = [];
|
||||
const ghidra = provider(
|
||||
{
|
||||
...installationHost(),
|
||||
readText: () =>
|
||||
"application.version=12.1.4\napplication.release.name=PUBLIC\napplication.java.min=21\n",
|
||||
},
|
||||
(options) => {
|
||||
launchers.push(options.launcher as GhidraHeadlessLauncher);
|
||||
return {
|
||||
start: () => Promise.resolve(ok(sessionInfo())),
|
||||
callTool: () => Promise.resolve(ok(null)),
|
||||
close: () => Promise.resolve(ok(null)),
|
||||
};
|
||||
},
|
||||
);
|
||||
const client = await createElfClient(ghidra);
|
||||
expect(launchers[0]?.options).toMatchObject({
|
||||
configurationDirectoryName: "ghidra_12.1.4_PUBLIC",
|
||||
});
|
||||
await client.close();
|
||||
});
|
||||
|
||||
describe("Ghidra provider", () => {
|
||||
it("commits DOS loader and real-mode language while retaining the CPU family", async () => {
|
||||
const ghidra = provider();
|
||||
|
||||
@@ -580,6 +580,8 @@ export const createGhidraProviderClient = (input: {
|
||||
launcher: new GhidraHeadlessLauncher({
|
||||
environment: input.environment,
|
||||
analyzeHeadlessPath: prerequisites.value.analyzeHeadlessPath,
|
||||
configurationDirectoryName:
|
||||
prerequisites.value.configurationDirectoryName,
|
||||
javaHome: prerequisites.value.javaHome,
|
||||
...(installation.javaVmName === undefined
|
||||
? {}
|
||||
@@ -986,6 +988,7 @@ export const createGhidraProviderClient = (input: {
|
||||
|
||||
interface GhidraClientCoordinates {
|
||||
readonly analyzeHeadlessPath: string;
|
||||
readonly configurationDirectoryName: string | null;
|
||||
readonly javaHome: string;
|
||||
readonly providerVersion: string;
|
||||
readonly profile: AnalysisProfileCommitment;
|
||||
@@ -1026,6 +1029,7 @@ const ghidraClientPrerequisites = (
|
||||
return err(new ProviderAdapterError("ghidra", "health"));
|
||||
return ok({
|
||||
analyzeHeadlessPath: installation.analyzeHeadlessPath,
|
||||
configurationDirectoryName: installation.configurationDirectoryName,
|
||||
javaHome: installation.javaHome,
|
||||
providerVersion: installation.providerVersion,
|
||||
profile,
|
||||
|
||||
@@ -8,6 +8,7 @@ interface GhidraReleaseVersion {
|
||||
readonly major: number;
|
||||
readonly minor: number;
|
||||
readonly patch: number;
|
||||
readonly suffix: string;
|
||||
}
|
||||
|
||||
/** How an observed Ghidra release relates to this adapter's bridge. */
|
||||
@@ -108,7 +109,22 @@ const parseGhidraRelease = (raw: string): GhidraReleaseVersion | undefined => {
|
||||
if (match === null || major === undefined || minor === undefined)
|
||||
return undefined;
|
||||
const patch = match[3] === undefined ? 0 : integerGroup(match[3]);
|
||||
return patch === undefined ? undefined : { major, minor, patch };
|
||||
return patch === undefined
|
||||
? undefined
|
||||
: { major, minor, patch, suffix: match[4] ?? "" };
|
||||
};
|
||||
|
||||
/**
|
||||
* The version spelling Ghidra's `ApplicationVersion.toString` uses in its
|
||||
* settings directory: numeric components normalized, a zero patch and an
|
||||
* empty tag omitted. A build-metadata (`+`) suffix has no such spelling.
|
||||
*/
|
||||
export const ghidraConfigurationVersion = (raw: string): string | undefined => {
|
||||
const version = parseGhidraRelease(raw);
|
||||
if (version === undefined || version.suffix.startsWith("+")) return undefined;
|
||||
const patch = version.patch === 0 ? "" : `.${String(version.patch)}`;
|
||||
const suffix = version.suffix === "-" ? "" : version.suffix;
|
||||
return `${String(version.major)}.${String(version.minor)}${patch}${suffix}`;
|
||||
};
|
||||
|
||||
const integerGroup = (value: string | undefined): number | undefined => {
|
||||
|
||||
@@ -154,6 +154,7 @@ describe.skipIf(process.platform === "win32")(
|
||||
const launcher = new GhidraHeadlessLauncher({
|
||||
environment: {},
|
||||
analyzeHeadlessPath: join(directory, "support/analyzeHeadless"),
|
||||
configurationDirectoryName: "ghidra_12.1.3_PUBLIC",
|
||||
javaHome: "/jdk-21",
|
||||
bridgeScriptPath: "/packaged/ReaGhidraBridge.java",
|
||||
pspExtension: extension,
|
||||
|
||||
@@ -1481,6 +1481,7 @@ describe.skipIf(process.platform === "win32")(
|
||||
environment: process.env,
|
||||
javaHome: join(root, "jdk"),
|
||||
analyzeHeadlessPath: "/unused/ghidra/support/analyzeHeadless",
|
||||
configurationDirectoryName: "ghidra_12.1.4_PUBLIC",
|
||||
bridgeScriptPath: "/unused/rea/ReaGhidraBridge.java",
|
||||
spawnProcess: async ({ runId }) => {
|
||||
const spawned = spawn(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { mkdir, writeFile } from "node:fs/promises";
|
||||
import { mkdir, stat, writeFile } from "node:fs/promises";
|
||||
import { createHash, randomUUID } from "node:crypto";
|
||||
import { join } from "node:path";
|
||||
import { describe, expect, it, onTestFinished } from "vitest";
|
||||
@@ -85,6 +85,7 @@ if (mode === "env-failure" || (probe === "-vmargs" && mode === "vm-failure")) {
|
||||
javaHome,
|
||||
...(javaVmName === null ? {} : { javaVmName }),
|
||||
analyzeHeadlessPath: join(root, "ghidra", "support", "analyzeHeadless"),
|
||||
configurationDirectoryName: "ghidra_12.1.4_PUBLIC",
|
||||
bridgeScriptPath: join(root, "ReaGhidraBridge.java"),
|
||||
spawnProcess: async (options) => {
|
||||
analysisArguments.push(options.arguments);
|
||||
@@ -110,6 +111,68 @@ if (mode === "env-failure" || (probe === "-vmargs" && mode === "vm-failure")) {
|
||||
return { session, launcher, analysisArguments, controller };
|
||||
};
|
||||
|
||||
describe.skipIf(process.platform === "win32")(
|
||||
"Ghidra inspected configuration identity",
|
||||
() => {
|
||||
it("prepares the inspected release directory without reinterpreting changed properties", async () => {
|
||||
const { session, launcher } = await fixture("quiet");
|
||||
const propertiesRoot = join(
|
||||
session.runtimeRoot,
|
||||
"..",
|
||||
"ghidra",
|
||||
"Ghidra",
|
||||
);
|
||||
await mkdir(propertiesRoot, { recursive: true });
|
||||
await writeFile(
|
||||
join(propertiesRoot, "application.properties"),
|
||||
"application.version=12.1.2\napplication.release.name=OTHER\n",
|
||||
);
|
||||
const launched = await launcher.launch(session);
|
||||
if (!launched.ok) throw launched.error;
|
||||
expect(
|
||||
(
|
||||
await stat(
|
||||
join(
|
||||
session.runtimeRoot,
|
||||
"config",
|
||||
"ghidra",
|
||||
"ghidra_12.1.4_PUBLIC",
|
||||
),
|
||||
)
|
||||
).isDirectory(),
|
||||
).toBe(true);
|
||||
await expect(
|
||||
stat(
|
||||
join(session.runtimeRoot, "config", "ghidra", "ghidra_12.1.2_OTHER"),
|
||||
),
|
||||
).rejects.toMatchObject({ code: "ENOENT" });
|
||||
});
|
||||
|
||||
it("rejects unresolved identity before creating runtime directories or starting Java", async () => {
|
||||
const { session, launcher, analysisArguments } = await fixture("quiet");
|
||||
const unresolved = new GhidraHeadlessLauncher({
|
||||
...launcher.options,
|
||||
configurationDirectoryName: null,
|
||||
});
|
||||
const launched = await unresolved.launch(session);
|
||||
expect(launched).toMatchObject({
|
||||
ok: false,
|
||||
error: {
|
||||
cause: {
|
||||
message: expect.stringContaining(
|
||||
"configuration directory identity is unresolved",
|
||||
),
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(analysisArguments).toEqual([]);
|
||||
await expect(stat(session.runtimeRoot)).rejects.toMatchObject({
|
||||
code: "ENOENT",
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
describe.skipIf(process.platform === "win32")(
|
||||
"Ghidra LaunchSupport JVM compatibility",
|
||||
() => {
|
||||
|
||||
@@ -1,11 +1,4 @@
|
||||
import {
|
||||
access,
|
||||
chmod,
|
||||
mkdir,
|
||||
readFile,
|
||||
stat,
|
||||
writeFile,
|
||||
} from "node:fs/promises";
|
||||
import { access, chmod, readFile, stat } from "node:fs/promises";
|
||||
import { join, posix, win32 } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
@@ -489,26 +482,13 @@ describe("Ghidra Windows launcher", () => {
|
||||
});
|
||||
|
||||
describe("Ghidra Windows launcher runtime", () => {
|
||||
it.skipIf(process.platform !== "win32").each([
|
||||
{
|
||||
properties:
|
||||
"application.version=12.1.4\napplication.release.name=PUBLIC\n",
|
||||
release: "ghidra_12.1.4_PUBLIC",
|
||||
},
|
||||
{ properties: undefined, release: "renamed-install" },
|
||||
{ properties: "application.version=12.1.4\n", release: "renamed-install" },
|
||||
])(
|
||||
"prepares the release directory through the Windows runtime ($release)",
|
||||
async ({ properties, release }) => {
|
||||
it
|
||||
.skipIf(process.platform !== "win32")
|
||||
.each(["ghidra_12.1.4_PUBLIC", "ghidra_12.1.2_DEV"])(
|
||||
"prepares the inspected release directory through the Windows runtime (%s)",
|
||||
async (release) => {
|
||||
const parent = await createTestTempDirectory("rea-launch-config-");
|
||||
const installRoot = join(parent, "renamed-install");
|
||||
if (properties !== undefined) {
|
||||
await mkdir(join(installRoot, "Ghidra"), { recursive: true });
|
||||
await writeFile(
|
||||
join(installRoot, "Ghidra", "application.properties"),
|
||||
properties,
|
||||
);
|
||||
}
|
||||
const runtime = await createGhidraTestRuntime(parent);
|
||||
runtimes.push(runtime);
|
||||
const spawnProcess = vi.fn(async () => {
|
||||
@@ -517,6 +497,7 @@ describe("Ghidra Windows launcher runtime", () => {
|
||||
const launcher = new GhidraHeadlessLauncher({
|
||||
environment: process.env,
|
||||
platform: "win32",
|
||||
configurationDirectoryName: release,
|
||||
analyzeHeadlessPath: join(
|
||||
installRoot,
|
||||
"support",
|
||||
@@ -571,6 +552,7 @@ describe("Ghidra Windows launcher runtime", () => {
|
||||
// Select the fixture's Node PATH and injected JVM options explicitly.
|
||||
environment: process.env,
|
||||
analyzeHeadlessPath: fixturePath,
|
||||
configurationDirectoryName: "ghidra_12.1.4_PUBLIC",
|
||||
// POSIX follows the real inspected-JVM route; Windows uses the official
|
||||
// batch script because its launcher contract differs.
|
||||
platform: "win32",
|
||||
|
||||
Reference in new issue
Block a user