From eb20b2ddb5c89bb94a1a552048d63dd5dc41c4c4 Mon Sep 17 00:00:00 2001 From: agra Date: Tue, 2 Jun 2026 23:20:19 +0300 Subject: [PATCH] =?UTF-8?q?docs(issues):=20file=200073=20=E2=80=94=20closu?= =?UTF-8?q?re=20literal=20inside=20a=20`defer`=20body=20segfaults=20loweri?= =?UTF-8?q?ng?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Minimal repro (issues/0073-...sx): a non-failable, uncalled closure literal declared inside a `defer` body crashes the compiler with a SIGSEGV in lowerLambda (src/ir/lower.zig). Isolation shows the trigger is "a closure literal lowered inside a defer body" — not failability, not whether it's called (closures and failable closures lower fine outside a defer). Pre-existing lowering bug, unrelated to the A5 error-analysis extraction; surfaced while writing an A5.2 cleanup-absorption test example. Filed per the IMPASSIBLE RULE: work paused pending a fix in another session. --- .../0073-closure-literal-in-defer-segfault.md | 89 +++++++++++++++++++ .../0073-closure-literal-in-defer-segfault.sx | 17 ++++ 2 files changed, 106 insertions(+) create mode 100644 issues/0073-closure-literal-in-defer-segfault.md create mode 100644 issues/0073-closure-literal-in-defer-segfault.sx diff --git a/issues/0073-closure-literal-in-defer-segfault.md b/issues/0073-closure-literal-in-defer-segfault.md new file mode 100644 index 0000000..acf5abd --- /dev/null +++ b/issues/0073-closure-literal-in-defer-segfault.md @@ -0,0 +1,89 @@ +# issue 0073 — closure literal inside a `defer` body segfaults the compiler + +## Symptom + +One-line: declaring a **closure literal inside a `defer` body** crashes the +compiler with a segfault during lowering. + +- **Observed:** `sx run` / `sx build` SIGSEGVs in `lowerLambda` + (`src/ir/lower.zig`) while lowering the enclosing function. With a *failable* + closure (`() -> !E { ... }`) the crash surfaces one frame out, in + `lowerCall` → `expandCallDefaults` → `scope.lookupFn` → `hash_map.get` (a + corrupted/garbage scope pointer), suggesting a stale/clobbered `self.scope` + (or builder/current-function state) while the deferred body is lowered. +- **Expected:** the program either lowers normally or produces a clean + diagnostic. A compiler segfault is never acceptable, regardless of whether the + shape is intended to be supported. + +Isolation (all on `arch-refactor`, current `HEAD`): + +| Probe | Shape | Result | +|-------|-------|--------| +| (a) | failable closure declared + `cb() catch e {}` — **no `defer`** | OK (exit 0) | +| (b) | failable closure literal inside a `defer` body | **SIGSEGV** (lowerCall/expandCallDefaults) | +| (c) | **non-failable** `() { return; }` inside a `defer` body | **SIGSEGV** (lowerLambda) | +| (d) | failable closure literal inside a plain `{ … }` block (not `defer`) | OK (exit 0) | + +So the trigger is **a closure literal lowered inside a `defer` body** — not +failability, not whether the closure is called. (a)/(d) prove closures and +failable closures lower fine outside a `defer`; (c) proves a bare non-failable +closure in a `defer` is enough to crash. + +## Reproduction + +`issues/0073-closure-literal-in-defer-segfault.sx` (minimal — non-failable, +uncalled closure in a `defer`): + +```sx +#import "modules/std.sx"; + +work :: () { + defer { cb := () { return; }; } +} + +main :: () -> s32 { + work(); + return 0; +} +``` + +Run: `./zig-out/bin/sx run issues/0073-closure-literal-in-defer-segfault.sx` +→ "Segmentation fault" with a stack through `lowerLambda` (`src/ir/lower.zig`). + +## Investigation prompt + +A `defer` body is captured at its declaration site and lowered into the +function's exit/cleanup path (drained by `lowerReturn` / block-exit), not inline. +Lowering a **lambda literal** while emitting a deferred body appears to run with +invalid lowering state — most likely `self.scope` (note probe (b)'s crash is in +`scope.lookupFn` reading a hash map at a garbage address) and/or the +builder's current-function / block context is stale or not the one +`lowerLambda` expects when it allocates the closure's trampoline + env. + +Suspected area: `src/ir/lower.zig` — `lowerLambda` (~`:8145`) and the `defer` +capture/replay path (`defer_stmt` handling + the cleanup-drain in `lowerReturn` / +block exit; grep `defer_stack` / `func_defer_base`). Check whether deferred +bodies are lowered: +1. with a scope pointer that has since been popped/freed (use-after-free → + garbage `fn_names`/`map` in `lookupFn`), or +2. in a builder state where `lowerLambda`'s assumptions (current function, + insert block) don't hold, or +3. re-entrantly / unbounded (the failable-variant trace looked like deep + recursion through `expandCallDefaults`→`lowerCall`→`lowerLambda`). + +Likely fix shape: lower deferred bodies under a valid, live scope (re-establish +or retain the declaring scope when replaying the `defer` body), or defer the +lambda's trampoline emission to a context that has the right function/block. + +Verification step: run the repro above — expect it to lower cleanly (or emit a +clean diagnostic) and NOT segfault. Then confirm a closure that is actually used +inside the defer (`defer { cb := () { ... }; cb(); }`) also works, and re-run +`bash tests/run_examples.sh` (357/0) to confirm no regression. + +## Provenance + +Found while writing an A5.2 (architecture stream) test-first scaffolding example +for the ERR E1.7 "cleanup-absorption stops at nested closures" behavior — the +closure-boundary probe (a closure literal inside a `defer`) crashed the compiler +instead of exercising the diagnostic. The crash is a pre-existing lowering bug, +unrelated to the A5 error-analysis extraction; surfaced by the probe. diff --git a/issues/0073-closure-literal-in-defer-segfault.sx b/issues/0073-closure-literal-in-defer-segfault.sx new file mode 100644 index 0000000..efac4fb --- /dev/null +++ b/issues/0073-closure-literal-in-defer-segfault.sx @@ -0,0 +1,17 @@ +// Repro for issue 0073 — a closure literal declared inside a `defer` body +// segfaults the compiler during lowering. Minimal: the closure is non-failable, +// unused, and never called; merely declaring it in the defer body crashes. +// +// Expected: either lowers fine, or a clean diagnostic — NEVER a segfault. +// Observed: SIGSEGV in `lowerLambda` (src/ir/lower.zig) during `work`'s lowering. + +#import "modules/std.sx"; + +work :: () { + defer { cb := () { return; }; } +} + +main :: () -> s32 { + work(); + return 0; +}