test(verification): strengthen conformance and real-Hopper checks
This commit is contained in:
1 parent
bce3398dbe
commit
0d36976277
4 files changed
+62
-1
No files matched your search
@@ -3,7 +3,11 @@ import { readFile } from "node:fs/promises";
|
||||
import { dirname, join, resolve } from "node:path";
|
||||
import { promisify } from "node:util";
|
||||
|
||||
import { sha256 } from "./lib/conformance-fixtures.mjs";
|
||||
import {
|
||||
generateLargeFixture,
|
||||
sha256,
|
||||
sourceDigest,
|
||||
} from "./lib/conformance-fixtures.mjs";
|
||||
|
||||
const exec = promisify(execFile);
|
||||
const manifestPath = resolve(
|
||||
@@ -16,6 +20,23 @@ const root = dirname(manifestPath);
|
||||
|
||||
for (const fixture of manifest.fixtures) {
|
||||
const artifactPath = join(root, fixture.artifact);
|
||||
const sources = [];
|
||||
for (const source of fixture.sources ?? []) {
|
||||
const content =
|
||||
source.path === "generated/large.c"
|
||||
? generateLargeFixture()
|
||||
: await readFile(join(process.cwd(), "tests/conformance", source.path));
|
||||
if (sha256(content) !== source.sha256)
|
||||
throw new Error(
|
||||
`${fixture.name}: source hash mismatch for ${source.path}`,
|
||||
);
|
||||
sources.push({ path: source.path, content });
|
||||
}
|
||||
if (
|
||||
fixture.sourceSha256 !== undefined &&
|
||||
sourceDigest(sources) !== fixture.sourceSha256
|
||||
)
|
||||
throw new Error(`${fixture.name}: source manifest digest mismatch`);
|
||||
const artifact = await readFile(artifactPath);
|
||||
if (sha256(artifact) !== fixture.artifactSha256)
|
||||
throw new Error(`${fixture.name}: artifact hash mismatch`);
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { execFile } from "node:child_process";
|
||||
import { createHash } from "node:crypto";
|
||||
import { createReadStream } from "node:fs";
|
||||
import { readdir, realpath } from "node:fs/promises";
|
||||
import { promisify } from "node:util";
|
||||
|
||||
@@ -8,6 +10,7 @@ import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
|
||||
import { TOOL_CONTRACTS } from "../dist/contracts/toolContracts.js";
|
||||
import {
|
||||
firstProcedureAddress,
|
||||
requireDistinctTargetHashes,
|
||||
requireAddressArray,
|
||||
requireFunctionDossier,
|
||||
requirePseudocode,
|
||||
@@ -253,6 +256,11 @@ const [targetA, targetB] = await Promise.all([
|
||||
]);
|
||||
if (targetA === targetB)
|
||||
throw new Error("Real-Hopper verification requires two distinct targets");
|
||||
const [targetHashA, targetHashB] = await Promise.all([
|
||||
sha256File(targetA),
|
||||
sha256File(targetB),
|
||||
]);
|
||||
requireDistinctTargetHashes(targetHashA, targetHashB);
|
||||
const serverEnvironment = { ...process.env };
|
||||
delete serverEnvironment.HOPPER_TARGET_PATH;
|
||||
delete serverEnvironment.HOPPER_SECOND_TARGET_PATH;
|
||||
@@ -377,6 +385,7 @@ try {
|
||||
diagnosticCount,
|
||||
dynamicSession: true,
|
||||
targets: [targetA, targetB],
|
||||
targetHashes: [targetHashA, targetHashB],
|
||||
switched: true,
|
||||
secondOverview: verifiedSecondOverview,
|
||||
};
|
||||
@@ -422,3 +431,9 @@ await new Promise((resolve, reject) => {
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
async function sha256File(path) {
|
||||
const hash = createHash("sha256");
|
||||
for await (const chunk of createReadStream(path)) hash.update(chunk);
|
||||
return hash.digest("hex");
|
||||
}
|
||||
@@ -27,6 +27,23 @@ export const firstProcedureAddress = (input: unknown): string => {
|
||||
return first.address;
|
||||
};
|
||||
|
||||
/** Reject two target paths that resolve to the same binary content. */
|
||||
export const requireDistinctTargetHashes = (
|
||||
firstHash: unknown,
|
||||
secondHash: unknown,
|
||||
): void => {
|
||||
if (
|
||||
typeof firstHash !== "string" ||
|
||||
typeof secondHash !== "string" ||
|
||||
firstHash.length === 0 ||
|
||||
secondHash.length === 0
|
||||
) {
|
||||
throw new TypeError("Target hashes must be non-empty strings");
|
||||
}
|
||||
if (firstHash === secondHash)
|
||||
throw new TypeError("Real-Hopper verification requires distinct binaries");
|
||||
};
|
||||
|
||||
/** Reject empty and success-shaped embedded decompilation failures. */
|
||||
export const requirePseudocode = (
|
||||
input: unknown,
|
||||
|
||||
@@ -2,12 +2,20 @@ import { describe, expect, it } from "vitest";
|
||||
|
||||
import {
|
||||
firstProcedureAddress,
|
||||
requireDistinctTargetHashes,
|
||||
requireAddressArray,
|
||||
requireFunctionDossier,
|
||||
requirePseudocode,
|
||||
} from "../src/application/RealHopperAssertions.js";
|
||||
|
||||
describe("real Hopper semantic assertions", () => {
|
||||
it("rejects two target paths containing the same binary", () => {
|
||||
expect(() => requireDistinctTargetHashes("same", "same")).toThrow(
|
||||
/distinct binaries/u,
|
||||
);
|
||||
expect(() => requireDistinctTargetHashes("", "other")).toThrow();
|
||||
expect(() => requireDistinctTargetHashes("first", "second")).not.toThrow();
|
||||
});
|
||||
it("extracts only a real procedure address from a structured page", () => {
|
||||
expect(
|
||||
firstProcedureAddress({
|
||||
|
||||
Reference in new issue
Block a user