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.
63 lines
1.2 KiB
TypeScript
63 lines
1.2 KiB
TypeScript
// Finally blocks run while a stack-overflow RangeError unwinds, and a catch
|
|
// further up sees the original error.
|
|
let finallyRuns = 0;
|
|
let deepest = 0;
|
|
|
|
function descend(level: number): number {
|
|
deepest = Math.max(deepest, level);
|
|
try {
|
|
return descend(level + 1) + 1;
|
|
} finally {
|
|
finallyRuns++;
|
|
}
|
|
}
|
|
|
|
try {
|
|
descend(0);
|
|
} catch (e) {
|
|
console.log((e as Error).name, (e as Error).message);
|
|
}
|
|
console.log("finally ran for every frame", finallyRuns === deepest + 1);
|
|
|
|
const log: string[] = [];
|
|
function outer(): void {
|
|
try {
|
|
inner(0);
|
|
} finally {
|
|
log.push("outer finally");
|
|
}
|
|
}
|
|
function inner(n: number): void {
|
|
try {
|
|
inner(n + 1);
|
|
} finally {
|
|
if (n === 0) log.push("inner finally at the top");
|
|
}
|
|
}
|
|
try {
|
|
outer();
|
|
} catch (e) {
|
|
log.push("caught " + (e instanceof RangeError));
|
|
}
|
|
console.log(log.join(" | "));
|
|
|
|
class Ledger {
|
|
entries = 0;
|
|
open = 0;
|
|
record(n: number): number {
|
|
this.open++;
|
|
try {
|
|
this.entries++;
|
|
return this.record(n + 1);
|
|
} finally {
|
|
this.open--;
|
|
}
|
|
}
|
|
}
|
|
const ledger = new Ledger();
|
|
try {
|
|
ledger.record(0);
|
|
} catch (e) {
|
|
console.log("ledger", (e as Error).name, ledger.open, ledger.entries > 100);
|
|
}
|