mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-22 05:59:31 +08:00
Native executables used to terminate with a signal when recursion exhausted the stack. Functions that can take part in unbounded recursion now compare the stack pointer against a per-stack guard on entry and throw Node's `RangeError: Maximum call stack size exceeded` through the ordinary exception path, so catch handlers, finally blocks, worker error events and the uncaught-error exit code behave as they do under Node. - The checked set is every function on a cycle of the call graph, where operations that can reenter compiled code indirectly (indirect calls, callbacks, dynamic and library operations) reach closure targets and class members. Non-recursive functions, including leaf callbacks, stay check-free, and the may-throw analysis accounts for the new throw. - The runtime installs the guard per context (main thread from the stack rlimit, workers from their thread attributes) and per async fiber, with a reserve for the throw, unwinding and error reporting. Worker threads now get an explicit 8 MiB stack. - Scalar call results pass through the unwind path as its dummy value, so calls in tail position keep tail-call and recursion elimination.
50 lines
1.2 KiB
TypeScript
50 lines
1.2 KiB
TypeScript
// Recursion inside async functions and generators runs on their own stacks;
|
|
// exhausting one throws the catchable RangeError there (or rejects).
|
|
function depth(n: number): number {
|
|
return n === 0 ? 0 : 1 + depth(n - 1);
|
|
}
|
|
function endless(n: number): number {
|
|
return endless(n + 1) + 1;
|
|
}
|
|
|
|
async function guarded(label: string): Promise<string> {
|
|
await null;
|
|
try {
|
|
return String(endless(0));
|
|
} catch (e) {
|
|
return `${label} ${(e as Error).name} ${(e as Error).message}`;
|
|
}
|
|
}
|
|
|
|
async function rejecting(): Promise<number> {
|
|
await null;
|
|
return endless(0);
|
|
}
|
|
|
|
async function bounded(): Promise<number> {
|
|
await null;
|
|
return depth(2000);
|
|
}
|
|
|
|
function* steps(): Generator<number> {
|
|
yield depth(1000);
|
|
try {
|
|
yield endless(0);
|
|
} catch (e) {
|
|
yield (e as Error).name.length;
|
|
}
|
|
}
|
|
|
|
console.log(await guarded("fiber"));
|
|
try {
|
|
await rejecting();
|
|
} catch (e) {
|
|
console.log("rejected", e instanceof RangeError, (e as Error).message);
|
|
}
|
|
console.log("bounded", await bounded());
|
|
const first = guarded("one");
|
|
const second = guarded("two");
|
|
console.log(await second, "/", await first);
|
|
console.log([...steps()].join(","));
|
|
console.log("main", depth(3000));
|