← board

Decide: should a stack overflow raise EStackOverflow by itself, or stay a hand-written hook?

__pxxSigSPPtr + __pxxSigPCPtr now make a stack overflow catchable, but only for a program that writes the hook itself:

procedure OnSegv;
begin
  PPtrUInt(__pxxSigSPPtr)^ := (PtrUInt(@spare[High(spare)]) - 256) and not PtrUInt(15);
  PPtrUInt(__pxxSigPCPtr)^ := PtrUInt(@Raiser);
end;

FPC raises EStackOverflow (runtime error 202) with no user code at all. The fork is whether pxx should too, and it is a design call, not a bug.

The fork

(a) Leave it a hook — the compiler supplies the two intrinsics and nothing else. Costs nothing at startup, invents no policy, and keeps the "no silent behaviour we did not ask for" line. But it means the default experience of a runaway recursion stays a bare SIGSEGV, and every program that wants FPC's behaviour re-implements the same eight lines (including getting the alignment and the headroom right).

(b) Install it by default — the signal runtime registers its own SIGSEGV hook that distinguishes a stack overflow from an ordinary wild pointer (the fault address is within a page or so of the faulting SP, which the ucontext now gives us) and raises. Then try ... except on E: EStackOverflow just works. Questions this drags in: where the spare stack comes from (a second BSS block? the existing BSS_SIG_ALTSTK, which the handler is already using?), how much, and whether it is per-thread — a cloned thread's overflow is the interesting case and the one a fixed BSS block gets wrong.

(c) Behind a flag--fpc-stack-errors, exactly the shape --fpc-mem-errors already has for turning a SIGSEGV into runtime error 216. This is the precedent-following answer: same family of behaviour, same opt-in, same reason (FPC parity that costs something is a flag, not a default).

Recommendation

(c), and reuse --fpc-mem-errors' machinery rather than adding a second flag — a stack overflow is a memory fault, the stub already decodes SIGSEGV, and runtime error 202 vs 216 is one comparison of the fault address against the saved SP (both of which the stub has in hand once UContextSPOffset exists). The "raise a catchable Pascal exception" half is a bigger question that overlaps decide-int-div-zero-behavior-unification — where a builtin exception CLASS should live — and should be answered together with it, not separately.

Blocked on nothing; the intrinsics that would implement any of the three landed with bug-a-stack-overflow-fault-to-raise-loops-forever-without-an-sp-reset.

DEFERRED (user, 2026-08-21) — parked to rainy-day, with the direction recorded

"much work for little wins, we have other things on our mind"

Not rejected, and not undecided-in-the-dark: the direction is recorded below so whoever picks this up does not restart the analysis.

The user's leaning, if it is ever done

(b) install it by default, with (c)'s FPC numbering under strict-fpc mode. I.e. our own sane behaviour out of the box, and FPC's exact runtime-error convention behind the mimic flag — not a second user-facing flag of its own.

Why it is deferred, in the user's own terms

"We seek LANGUAGE compliance, not error-handling compliance." FPC parity on how a program dies is worth much less than parity on what the language means, and this ticket is entirely the former. That principle is worth more than this ticket: it applies to the whole --fpc-*-errors family and it is the reason none of them should default to FPC's convention.

And the mechanical objection, which is the good one:

"having a stack overflow exception handler would ironically use the stack"

That is the crux and it does not go away. The raise has to land on the MAIN stack past the guard page, so it needs headroom the faulting program has by definition run out of. Today's hook makes the program supply its own spare array precisely for this. A fixed BSS block gets the per-thread case wrong, and a cloned thread's overflow is the interesting one.

What this ticket got wrong, corrected before parking

Its recommendation deferred the catchable half because "where a builtin exception CLASS should live … should be answered together with decide-int-div-zero-behavior-unification." That is stale. The question has a working answer and four shipped instances: a PXX*Hook slot in builtinheap.pas, defaulting to nil = message + Halt(n), which sysutils' initialization upgrades to a catchable raise —

PXXDivZeroHook -> EDivByZero      PXXOverflowHook   -> EIntOverflow
PXXRangeErrorHook -> ERangeError  PXXIoErrorHook    -> EInOutError

with a fifth (PXXNilRefHook -> EAccessViolation) specified in [[feature-a-emitted-nil-checks]]. So the exception-class home needs no design round — it needs PXXStackOverflowHook and an EStackOverflow, which does not exist in sysutils today.

The design was never the blocker. The headroom is.

The cheap half, separable and NOT parked with the rest

Reporting 202 instead of 216 for a stack overflow is one comparison of si_addr against the saved SP — both already in hand inside the --fpc-mem-errors stub, which already decodes SIGSEGV. It needs no headroom, no new flag and no exception class, and the parent bug ([[bug-a-a-memory-fault-is-a-raw-sigsegv-not-runtime-error-216]]) already lists it as a known gap. If anyone touches that stub for another reason, fold it in; it does not justify a session of its own.

Also settled while parking: do not add --fpc-stack-errors. A stack overflow is a memory fault, the stub already decodes SIGSEGV, and one flag reporting FPC's numbers explains better than two split by fault kind.