Files
scriptc/tests/corpus/stack-overflow-finally.ts
Chris Tate 3bcf761f42 Throw a catchable RangeError on deep recursion
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.
2026-10-09 16:55:35 -05:00

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);
}