104 lines
3.6 KiB
TypeScript
104 lines
3.6 KiB
TypeScript
import { readFile } from "node:fs/promises";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
import { beforeAll, describe, expect, it } from "vitest";
|
|
|
|
let bridgeSource = "";
|
|
let queueSource = "";
|
|
|
|
beforeAll(async () => {
|
|
[bridgeSource, queueSource] = await Promise.all([
|
|
readFile(
|
|
fileURLToPath(
|
|
new URL("../bridge/ghidra/ReaGhidraBridge.java", import.meta.url),
|
|
),
|
|
"utf8",
|
|
),
|
|
readFile(
|
|
fileURLToPath(
|
|
new URL("../src/ghidra/GhidraRequestQueue.ts", import.meta.url),
|
|
),
|
|
"utf8",
|
|
),
|
|
]);
|
|
});
|
|
|
|
describe("Ghidra bridge truthfulness", () => {
|
|
it("owns one persistent decompiler interface for the Program lifetime", () => {
|
|
expect(bridgeSource).toContain("private DecompInterface decompiler;");
|
|
expect(bridgeSource.match(/new DecompInterface\(\)/gu)).toHaveLength(1);
|
|
expect(
|
|
bridgeSource.match(/decompiler\.openProgram\(currentProgram\)/gu),
|
|
).toHaveLength(1);
|
|
expect(bridgeSource).toContain("decompiler.dispose()");
|
|
expect(bridgeSource).toContain("DECOMPILE_TIMEOUT_SECONDS = 30");
|
|
expect(bridgeSource).toContain(
|
|
"decompileFunction(\n function,\n DECOMPILE_TIMEOUT_SECONDS",
|
|
);
|
|
});
|
|
|
|
it("projects references and CFG from public Ghidra models", () => {
|
|
expect(bridgeSource).toContain("getReferenceManager().getReferencesTo");
|
|
expect(bridgeSource).toContain(
|
|
"getReferencesFrom(instruction.getAddress())",
|
|
);
|
|
expect(bridgeSource).toContain(
|
|
"new BasicBlockModel(currentProgram, false)",
|
|
);
|
|
expect(bridgeSource).toContain("block.getDestinations(monitor)");
|
|
expect(bridgeSource).toContain(
|
|
'result.addProperty("computed", type.isComputed())',
|
|
);
|
|
expect(bridgeSource).toContain(
|
|
'result.addProperty("indirect", type.isIndirect())',
|
|
);
|
|
expect(bridgeSource).toContain(
|
|
'result.addProperty("external", reference.isExternalReference())',
|
|
);
|
|
expect(bridgeSource).toContain("reference.isEntryPointReference()");
|
|
});
|
|
|
|
it("keeps function classification and targetless-flow uncertainty explicit", () => {
|
|
expect(bridgeSource).toContain("function.isThunk()");
|
|
expect(bridgeSource).toContain("function.isExternal()");
|
|
expect(bridgeSource).toContain('"ghidra-function-manager"');
|
|
expect(bridgeSource).toContain(
|
|
"Unresolved computed or indirect flows without target addresses",
|
|
);
|
|
expect(bridgeSource).toContain(
|
|
"not original source or Hopper-equivalent text",
|
|
);
|
|
});
|
|
|
|
it("serializes a bounded request queue before crossing the socket", () => {
|
|
expect(queueSource).toContain("class GhidraRequestQueue");
|
|
expect(queueSource).toContain("if (this.#size >= this.maximum)");
|
|
expect(queueSource).toContain("if (this.#active) return");
|
|
expect(queueSource).toContain(
|
|
"remaining = entry.deadline - performance.now()",
|
|
);
|
|
});
|
|
|
|
it("binds the Windows fallback to authenticated IPv4 loopback", () => {
|
|
expect(bridgeSource).toContain(
|
|
'descriptor.transport.equals("unix-socket")',
|
|
);
|
|
expect(bridgeSource).toContain(
|
|
'descriptor.transport.equals("authenticated-loopback-tcp")',
|
|
);
|
|
expect(bridgeSource).toContain('InetAddress.getByName("127.0.0.1")');
|
|
expect(bridgeSource).toContain('endpoint.addProperty("host", "127.0.0.1")');
|
|
expect(bridgeSource).toContain("StandardCopyOption.ATOMIC_MOVE");
|
|
});
|
|
|
|
it("does not admit mutation methods into the Java dispatch", () => {
|
|
for (const method of [
|
|
'case "set_comment"',
|
|
'case "set_address_name"',
|
|
'case "set_inline_comment"',
|
|
'case "set_bookmark"',
|
|
])
|
|
expect(bridgeSource).not.toContain(method);
|
|
});
|
|
});
|