195 lines
5.2 KiB
JavaScript
195 lines
5.2 KiB
JavaScript
import { requireFunctionDossierOracle } from "../../dist/application/RealHopperAssertions.js";
|
|
|
|
/** Verify named positive semantics from the source-owned C fixture. */
|
|
export async function verifyRealHopperFixture({
|
|
client,
|
|
options,
|
|
document,
|
|
oracle,
|
|
normalizedResult,
|
|
}) {
|
|
const main = await resolveFixtureProcedure(
|
|
client,
|
|
options,
|
|
oracle.mainProcedure,
|
|
normalizedResult,
|
|
);
|
|
const entry = await resolveFixtureProcedure(
|
|
client,
|
|
options,
|
|
oracle.entryProcedure,
|
|
normalizedResult,
|
|
);
|
|
const branch = await resolveFixtureProcedure(
|
|
client,
|
|
options,
|
|
oracle.branchProcedure,
|
|
normalizedResult,
|
|
);
|
|
const leaf = await resolveFixtureProcedure(
|
|
client,
|
|
options,
|
|
oracle.leafProcedure,
|
|
normalizedResult,
|
|
);
|
|
const comment = "REA real-Hopper conformance verifier";
|
|
const commentSet = normalizedResult(
|
|
await client.callTool(
|
|
{
|
|
name: "set_comment",
|
|
arguments: { address: entry.address, comment, document },
|
|
},
|
|
options,
|
|
),
|
|
"set_comment fixture marker",
|
|
);
|
|
if (commentSet !== true)
|
|
throw new Error("Hopper did not read back the fixture comment marker");
|
|
|
|
const entryDossier = requireFunctionDossierOracle(
|
|
await analyzeFixtureProcedure(client, options, entry, normalizedResult),
|
|
{
|
|
procedure_address: entry.address,
|
|
callee_address: branch.address,
|
|
caller_address: main.address,
|
|
referenced_string: oracle.entryString,
|
|
comment,
|
|
require_assembly: true,
|
|
},
|
|
);
|
|
const branchDossier = requireFunctionDossierOracle(
|
|
await analyzeFixtureProcedure(client, options, branch, normalizedResult),
|
|
{
|
|
procedure_address: branch.address,
|
|
callee_address: leaf.address,
|
|
caller_address: entry.address,
|
|
require_cfg_successor: true,
|
|
require_assembly: true,
|
|
},
|
|
);
|
|
const leafDossier = requireFunctionDossierOracle(
|
|
await analyzeFixtureProcedure(client, options, leaf, normalizedResult),
|
|
{
|
|
procedure_address: leaf.address,
|
|
caller_address: branch.address,
|
|
referenced_string: oracle.leafString,
|
|
referenced_name: oracle.globalName,
|
|
require_assembly: true,
|
|
},
|
|
);
|
|
return {
|
|
procedures: { main, entry, branch, leaf },
|
|
entryComments: entryDossier.comments.returned,
|
|
entryStrings: entryDossier.referenced_strings.returned,
|
|
entryCallees: entryDossier.callees.returned,
|
|
branchCallees: branchDossier.callees.returned,
|
|
branchBlocks: branchDossier.basic_blocks.returned,
|
|
leafStrings: leafDossier.referenced_strings.returned,
|
|
leafNames: leafDossier.referenced_names.returned,
|
|
};
|
|
}
|
|
|
|
/** Reject malformed, unsafe, or error-level diagnostics from the MCP runtime. */
|
|
export function requireSafeDiagnostics(chunks) {
|
|
const lines = chunks
|
|
.join("")
|
|
.split("\n")
|
|
.map((line) => line.trim())
|
|
.filter((line) => line.length > 0);
|
|
for (const line of lines) {
|
|
let diagnostic;
|
|
try {
|
|
diagnostic = JSON.parse(line);
|
|
} catch {
|
|
throw new Error(`The MCP runtime emitted malformed stderr: ${line}`);
|
|
}
|
|
if (
|
|
diagnostic === null ||
|
|
typeof diagnostic !== "object" ||
|
|
diagnostic.application !== "rea" ||
|
|
typeof diagnostic.level !== "number" ||
|
|
diagnostic.level >= 40
|
|
)
|
|
throw new Error(`The MCP runtime emitted unsafe stderr: ${line}`);
|
|
}
|
|
return lines.length;
|
|
}
|
|
|
|
/** Bind mutations to the active document rather than list ordering. */
|
|
export async function requireCurrentDocument(
|
|
client,
|
|
options,
|
|
documents,
|
|
normalizedResult,
|
|
) {
|
|
const current = normalizedResult(
|
|
await client.callTool({ name: "current_document", arguments: {} }, options),
|
|
"current_document",
|
|
);
|
|
if (
|
|
typeof current !== "string" ||
|
|
current.length === 0 ||
|
|
!documents.includes(current)
|
|
)
|
|
throw new Error("current_document was not present in list_documents");
|
|
return current;
|
|
}
|
|
|
|
export async function resolveFixtureProcedure(
|
|
client,
|
|
options,
|
|
expectedName,
|
|
normalizedResult,
|
|
) {
|
|
const page = normalizedResult(
|
|
await client.callTool(
|
|
{
|
|
name: "search_procedures",
|
|
arguments: {
|
|
pattern: expectedName,
|
|
mode: "literal",
|
|
case_sensitive: true,
|
|
limit: 100,
|
|
},
|
|
},
|
|
options,
|
|
),
|
|
`search_procedures ${expectedName}`,
|
|
);
|
|
const matches = Array.isArray(page?.items)
|
|
? page.items.filter(
|
|
(item) =>
|
|
typeof item?.value === "string" &&
|
|
item.value_truncated === false &&
|
|
item.value.replace(/^_+/u, "") === expectedName,
|
|
)
|
|
: [];
|
|
if (
|
|
matches.length !== 1 ||
|
|
typeof matches[0]?.address !== "string" ||
|
|
!/^0x[0-9a-f]+$/iu.test(matches[0].address)
|
|
)
|
|
throw new Error(
|
|
`Expected exactly one Hopper procedure named ${expectedName}`,
|
|
);
|
|
return { address: matches[0].address, name: matches[0].value };
|
|
}
|
|
|
|
/** Request the assembly evidence required by the fixture oracle. */
|
|
export const analyzeFixtureProcedure = async (
|
|
client,
|
|
options,
|
|
procedure,
|
|
normalizedResult,
|
|
) =>
|
|
normalizedResult(
|
|
await client.callTool(
|
|
{
|
|
name: "analyze_function",
|
|
arguments: { procedure: procedure.address, include_assembly: true },
|
|
},
|
|
options,
|
|
),
|
|
`analyze_function ${procedure.name}`,
|
|
);
|