fix(native): preserve source attribution and compiler ownership

This commit is contained in:
morluto 2026-10-14 01:07:20 +08:00
parent 60c34b6907
commit b576b347b1
3 changed files with 45 additions and 6 deletions

View File

@ -4,7 +4,6 @@ import { mkdtemp, readFile, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { isAbsolute, join } from "node:path";
import { fileURLToPath } from "node:url";
import { promisify } from "node:util";
import {
artifactCli,
@ -12,7 +11,44 @@ import {
withArtifactMcp,
} from "./lib/artifact-e2e.mjs";
const execute = promisify(execFile);
const execute = (command, arguments_, { timeout }) =>
new Promise((resolve, reject) => {
let timedOut = false;
const child = execFile(
command,
arguments_,
{ detached: true, encoding: "utf8" },
(error, stdout, stderr) => {
clearTimeout(deadline);
if (timedOut) {
const failure = new Error(`Command timed out after ${timeout} ms`);
Object.assign(failure, {
code: "ETIMEDOUT",
killed: true,
signal: "SIGKILL",
stdout,
stderr,
});
reject(failure);
} else if (error !== null) {
reject(error);
} else {
resolve({ stdout, stderr });
}
},
);
const deadline = setTimeout(() => {
timedOut = true;
if (child.pid === undefined) return;
try {
// This script only runs on macOS. detached:true gives the owned
// compiler invocation its own process group, inherited by FPC tools.
process.kill(-child.pid, "SIGKILL");
} catch (cause) {
if (cause.code !== "ESRCH") child.kill("SIGKILL");
}
}, timeout);
});
const compiler = process.env.REA_VERIFY_FPC_COMMAND;
if (process.platform !== "darwin" || !isAbsolute(compiler ?? ""))
throw new Error(
@ -79,12 +115,11 @@ const stable = (result) =>
}));
try {
const version = (
await execute(compiler, ["-iV"], { timeout: 30_000, killSignal: "SIGKILL" })
await execute(compiler, ["-iV"], { timeout: 30_000 })
).stdout.trim();
const architecture = (
await execute(compiler, ["-iTP"], {
timeout: 30_000,
killSignal: "SIGKILL",
})
).stdout.trim();
await execute(
@ -98,7 +133,7 @@ try {
`-FU${directory}`,
source,
],
{ timeout: 120_000, killSignal: "SIGKILL" },
{ timeout: 120_000 },
);
const binary = join(directory, "calls");
const cli = await artifactCli(

View File

@ -122,7 +122,10 @@ export const nativeCodeLocationSchema = z.strictObject({
column: z.number().int().positive().nullable(),
})
.nullable()
.optional(),
.optional()
.describe(
"Compiler or debugger source attribution only; REA does not read or verify the named source-file bytes.",
),
});
/** One observed entry into a requested function or method. */

View File

@ -31,6 +31,7 @@ export const NATIVE_CALL_OBSERVATION_LIMITATIONS = [
"Registers are the raw integer argument registers at entry. For Objective-C methods the first two hold the receiver and the selector.",
"Receiver classes come from LLDB's Objective-C runtime reader without running target code; they are null for class methods and for receivers LLDB cannot classify.",
"Calls that never reach a symbol's entry, such as inlined code or objc_direct methods called directly, are not observed.",
"Source locations come from LLDB line-table metadata; REA does not read or verify the named source-file contents.",
"The target is launched directly under LLDB, not through LaunchServices; LLDB disables ASLR for it, so load addresses can equal file addresses.",
"The observation window is checked about once a second, and recording pauses the process at every entry, which slows it.",
] as const;