Files
rea/scripts/verify-native-pascal-calls.mjs
T
3e8a5f76d0 feat(native): retain LLDB source coordinates and verify Pascal calls (#2800)
* feat(native): retain LLDB source coordinates and verify Pascal call entries

Preserve partial line-table attribution for native locations and historical
evidence compatibility. Verify owned Free Pascal call entries through CLI/MCP.

Refs #2382, #2372, #2397

Co-Authored-By: Codex <[email protected]>

* docs: namespace native specifications by domain and feature

Use stable descriptive paths instead of numeric prefixes to avoid parallel contribution collisions.

Co-Authored-By: Codex <[email protected]>

* fix(native): bound Pascal verification and compare caller coordinates

Address review findings at their producing boundaries and preserve directly affected consumers. Source inspection and git diff --check completed. Tests and automated checks were not run at the user’s explicit request.

* fix(native): bound compiler shutdown and source-coordinate fixtures

* docs: clarify historical native source verification

* fix(native): preserve source attribution and compiler ownership

* docs(native): qualify retained source attribution

* fix(ci): clean up native Pascal verifier processes

* test(native): type the Pascal verifier helper import after merging main

Main now typechecks this boundary test, which imports the untyped
canonical-temporary-directory helper and reads an unknown catch value.
Add the helper's declaration file and narrow the caught error.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* test(native): verify partial source attribution caveat

* fix(native): retain verifier compiler cleanup ownership

* fix(native): forward verifier cancellation and measure path bytes

CLI and MCP artifact runs observe the verifier abort signal, and a
failed Pascal verifier test stops the compiler group before deleting
its directory. Repeated source locations grow by measured UTF-8 JSON
bytes; workflow tokens, CPU and peak memory stay unmeasured.

---------

Co-authored-by: Matheus Breguêz <[email protected]>
Co-authored-by: Codex <[email protected]>
Co-authored-by: Kaoru0822 <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Cursor Agent <[email protected]>
2026-11-07 23:25:20 +08:00

187 lines
6.0 KiB
JavaScript

import assert from "node:assert/strict";
import { readFile, rm } from "node:fs/promises";
import { isAbsolute, join } from "node:path";
import { fileURLToPath } from "node:url";
import { createPackageVerifierCommands } from "./lib/verify-package-commands.mjs";
import { withVerifierCleanup } from "./lib/verifier-cleanup.mjs";
import {
artifactCli,
artifactMcpResult,
withArtifactMcp,
} from "./lib/artifact-e2e.mjs";
import { createCanonicalTemporaryDirectory } from "./lib/canonical-temporary-directory.mjs";
const cancellation = new AbortController();
let shutdownSignal;
const signalExitCodes = { SIGINT: 130, SIGTERM: 143 };
const forwardCancellation = (signal) => {
if (shutdownSignal !== undefined) return;
shutdownSignal = signal;
cancellation.abort(new Error(`Verification cancelled by ${signal}`));
};
const signalHandlers = Object.fromEntries(
Object.keys(signalExitCodes).map((signal) => [
signal,
() => forwardCancellation(signal),
]),
);
for (const [signal, handler] of Object.entries(signalHandlers))
process.on(signal, handler);
const compiler = process.env.REA_VERIFY_FPC_COMMAND;
if (process.platform !== "darwin" || !isAbsolute(compiler ?? ""))
throw new Error(
"Requires macOS and an absolute caller-selected REA_VERIFY_FPC_COMMAND; build the runtime first.",
);
const source = fileURLToPath(
new URL("../tests/conformance/native/calls.pas", import.meta.url),
);
const fixtureLines = (await readFile(source, "utf8")).split(/\r?\n/u);
const callerLines = new Set(
["REA_VERIFY_CALLER_LOOP", "REA_VERIFY_CALLER_CALL"].map((marker) => {
const matches = fixtureLines.flatMap((line, index) =>
line.includes(marker) ? [index + 1] : [],
);
assert.equal(matches.length, 1, `fixture has one ${marker} marker`);
return matches[0];
}),
);
const allocatedDirectory =
await createCanonicalTemporaryDirectory("rea-pascal-calls-");
let directory = allocatedDirectory.path;
const commands = await createPackageVerifierCommands({
workspace: {
path: allocatedDirectory.allocatedPath,
close: () =>
rm(allocatedDirectory.allocatedPath, { recursive: true, force: true }),
},
});
const execute = (command, arguments_, { timeout }) =>
commands.exec(command, arguments_, {
timeout,
diagnosticBytes: 1024 * 1024,
signal: cancellation.signal,
});
const input = {
breakpoints: [{ kind: "function", name: "rea_pascal_call" }],
argument_registers: 1,
backtrace_frames: 1,
duration_ms: 30_000,
};
const inspect = (result) => {
assert.equal(result.process.outcome, "exited");
assert.equal(result.process.exit_status, 0);
assert.equal(result.process.terminated, true);
assert.equal(result.process.stdout.text, "pascal 0\npascal 1\npascal 2\n");
assert.equal(result.events.length, 3);
assert.deepEqual(
result.events.map((event) => event.registers[0].value),
["0x0", "0x1", "0x2"],
);
assert.equal(result.breakpoints[0].location_count, 1);
for (const event of result.events) {
assert.equal(event.backtrace.length, 1);
assert.equal(event.backtrace[0].source_location?.file_path, source);
assert.equal(event.backtrace[0].module_path, join(directory, "calls"));
assert.ok(
callerLines.has(event.backtrace[0].source_location.line),
"caller is the Pascal loop/call site",
);
}
for (const location of [
...result.events,
...result.breakpoints[0].locations,
]) {
assert.equal(location.source_location?.file_path, source);
assert.ok(location.source_location.line > 0);
}
};
const stable = (result) =>
result.events.map((event) => ({
file_address: event.file_address,
source_location: event.source_location,
registers: event.registers,
callers: event.backtrace.map((frame) => ({
file_address: frame.file_address,
symbol: frame.symbol,
source_location: frame.source_location,
})),
}));
try {
const result = await withVerifierCleanup(async () => {
const version = (
await execute(compiler, ["-iV"], { timeout: 30_000 })
).stdout.trim();
const architecture = (
await execute(compiler, ["-iTP"], {
timeout: 30_000,
})
).stdout.trim();
await execute(
compiler,
[
"-dDEBUG",
"-gw3",
"-O-",
"-Xs-",
`-FE${directory}`,
`-FU${directory}`,
source,
],
{ timeout: 120_000 },
);
const binary = join(directory, "calls");
const cli = await artifactCli(
"observe-native-calls",
binary,
[JSON.stringify(input)],
{ timeoutMs: 180_000, signal: cancellation.signal, commands },
);
cancellation.signal.throwIfAborted();
inspect(cli);
await withArtifactMcp(
binary,
async (client) => {
const mcp = await artifactMcpResult(
client,
"observe_native_calls",
input,
{ signal: cancellation.signal },
);
cancellation.signal.throwIfAborted();
inspect(mcp);
assert.deepEqual(stable(mcp), stable(cli));
},
{ signal: cancellation.signal, commands },
);
cancellation.signal.throwIfAborted();
return {
ok: true,
mocked: false,
compiler,
version,
architecture,
debugger: cli.debugger,
cli: true,
stdio_mcp: true,
scope:
"bounded native call entries and compiler-provided source coordinates; no typed Pascal locals or interactive stepping",
};
}, [() => commands.close()]);
cancellation.signal.throwIfAborted();
console.log(JSON.stringify(result));
} catch (cause) {
const cancelled =
cause === cancellation.signal.reason ||
(cause?.reason === "cancelled" &&
cause.cancellation?.reason === cancellation.signal.reason &&
cause.cleanupFailure === null);
if (shutdownSignal === undefined || !cancelled) throw cause;
console.error(`Verification cancelled by ${shutdownSignal}.`);
process.exitCode = signalExitCodes[shutdownSignal];
} finally {
for (const signal of Object.keys(signalExitCodes))
process.off(signal, signalHandlers[signal]);
}