Node islands: read the stack from the frame address, strip ASan's swapcontext warning, fix a race in 6811

- ni_stack_limit classifies the stack by the frame address. Under ASan's
  use-after-return detection a local lives on a fake stack, so a call into
  Node from a hosted promise reaction got the wrong V8 stack limit and hit
  "Maximum call stack size exceeded" (6802, dev, sanitized).
- The ordering harness drops ASan's once-per-process makecontext/swapcontext
  warning from stderr, as the other harnesses do.
- 6811's island timers fire at 15 and 40 ms, after the program's immediate
  and its 5 and 10 ms timers, instead of racing the first loop turn.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Malte Ubl
2026-10-21 17:46:12 -07:00
co-authored by Claude Opus 5.5
parent eaefbdf44f
commit 049b34c166
3 changed files with 13 additions and 6 deletions
+3 -2
View File
@@ -336,8 +336,9 @@ static void ni_call_job(void *p) {
* runtime's guard for it: its mapping's start plus the reserve compiled
* code keeps). */
static uintptr_t ni_stack_limit(void) {
char here;
uintptr_t sp = (uintptr_t)&here;
/* The frame address, not a local's: under ASan's use-after-return
* detection locals live on a fake stack elsewhere in memory. */
uintptr_t sp = (uintptr_t)__builtin_frame_address(0);
if (ni_stack != NULL && sp > (uintptr_t)ni_stack && sp <= (uintptr_t)ni_stack + NI_STACK)
return (uintptr_t)(ni_stack + NI_GUARD) + NI_STACK / 8;
if (sp > ni_main_lo && sp <= ni_main_hi) {
@@ -1,4 +1,4 @@
exports.timers = () => {
setTimeout(() => console.log("island timer 1"), 1);
setTimeout(() => console.log("island timer 2"), 20);
setTimeout(() => console.log("island timer 1"), 15);
setTimeout(() => console.log("island timer 2"), 40);
};
+8 -2
View File
@@ -53,20 +53,26 @@ const programs = existsSync(programsDir)
.sort()
: [];
/** Linux ASan's once-per-process warning at the first context switch (the
* runtime's fibers and Node's stack): not program output. */
const ASAN_SWAP = /^==\d+==WARNING: ASan doesn't fully support makecontext\/swapcontext.*\n/gm;
/** One run's observable outcome: stdout, stderr and the exit status. */
async function outcome(cmd: string, args: string[]): Promise<string> {
try {
const { stdout, stderr } = await execFileAsync(cmd, args, {
const { stdout, stderr: raw } = await execFileAsync(cmd, args, {
cwd: repoRoot,
encoding: "utf8",
timeout: 20_000,
env: { ...process.env, NODE_NO_WARNINGS: "1" },
});
const stderr = raw.replace(ASAN_SWAP, "");
return `${stdout}${stderr === "" ? "" : `[stderr]\n${stderr}`}[exit 0]`;
} catch (err) {
const e = err as { stdout?: string; stderr?: string; code?: unknown; signal?: unknown };
if (typeof e.code !== "number") return `[failed ${String(e.signal ?? e.code)}]`;
return `${e.stdout ?? ""}${e.stderr ? `[stderr]\n${e.stderr}` : ""}[exit ${e.code}]`;
const stderr = (e.stderr ?? "").replace(ASAN_SWAP, "");
return `${e.stdout ?? ""}${stderr ? `[stderr]\n${stderr}` : ""}[exit ${e.code}]`;
}
}