docs(issues): file 0073 — closure literal inside a defer body segfaults lowering
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.
This commit is contained in:
89
issues/0073-closure-literal-in-defer-segfault.md
Normal file
89
issues/0073-closure-literal-in-defer-segfault.md
Normal file
@@ -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.
|
||||
17
issues/0073-closure-literal-in-defer-segfault.sx
Normal file
17
issues/0073-closure-literal-in-defer-segfault.sx
Normal file
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user