diff --git a/current/CHECKPOINT-METATYPE.md b/current/CHECKPOINT-METATYPE.md index 9d732086..9e58f319 100644 --- a/current/CHECKPOINT-METATYPE.md +++ b/current/CHECKPOINT-METATYPE.md @@ -60,7 +60,14 @@ Pick any (independent): never `define()`d (hard error), use-before-define. ## Known issues -None. +- **issue 0139 — by-value self-reference segfaults** (`typeSizeBytes` infinite + recursion). A by-VALUE self-referential type (`payload = List` instead of + `*List`) stack-overflows instead of emitting a loud "infinite size" diagnostic. + PRE-EXISTING (a hand-written source enum `enum { node: Bad; leaf }` crashes + identically) — a general type-system gap, not specific to declare/define. Closes + the F5 "by-VALUE self-reference rejected" item for the comptime path once fixed. + Filed `issues/0139-byvalue-self-reference-segfault.md`; session stopped here per + the CLAUDE.md IMPASSABLE rule (do not work around / roll forward after filing). ## Log - **Self-reference done.** `declare(name)` + `preregisterForwardTypes` (forward diff --git a/issues/0139-byvalue-self-reference-segfault.md b/issues/0139-byvalue-self-reference-segfault.md new file mode 100644 index 00000000..17fd0b10 --- /dev/null +++ b/issues/0139-byvalue-self-reference-segfault.md @@ -0,0 +1,59 @@ +# 0139 — by-value self-referential type segfaults (`typeSizeBytes` infinite recursion) + +**Symptom** — a type whose field/variant payload is ITSELF *by value* (not behind +a pointer) crashes the compiler with a stack-overflow segfault instead of a loud +"infinite size" diagnostic. Observed: `Segmentation fault` inside +`src/ir/types.zig:typeSizeBytes` (unbounded self-recursion through the field +loop). Expected: a clean compile error naming the offending type and suggesting +`*Self`. + +**Pre-existing / scope** — NOT specific to the comptime `declare`/`define` +metaprogramming; a hand-written SOURCE enum reproduces it identically. So the fix +belongs in the general type-system size/layout path, and the comptime +construction path (METATYPE F5) inherits the protection for free once it lands. + +**Reproduction** (source enum — no metaprogramming needed): + +```sx +#import "modules/std.sx"; + +Bad :: enum { + node: Bad; // by-VALUE self-reference → infinite size + leaf; +} + +main :: () -> i32 { + x : Bad = .leaf; + return 0; +} +``` + +Same crash via the metaprogramming path (`#import "modules/std/meta.sx"`): + +```sx +make_bad :: () -> Type { + h := declare("Bad"); + return define(h, .enum(.{ variants = .[ + EnumVariant.{ name = "node", payload = Bad }, // by-value self-ref + EnumVariant.{ name = "leaf", payload = void } ] })); +} +Bad :: make_bad(); +``` + +**Investigation prompt** — A by-value self-referential (or mutually-recursive) +aggregate has infinite size and must be rejected loudly, not recursed into +forever. The crash is in `src/ir/types.zig:typeSizeBytes` (~line 736), which +recurses into each struct/tagged-union field's type with no cycle guard; a field +typed as its own (or an enclosing) nominal type recurses unboundedly → +stack overflow. Fix: detect the cycle — walk the nominal-type dependency with a +visited set (or a recursion-depth / on-stack-nominal guard), and when a nominal +type reaches itself by value (no pointer indirection on the path), emit a +diagnostic via `self.diagnostics.addFmt(.err, span, "type '{s}' is infinitely +sized (it contains itself by value); use a pointer (`*{s}`) to break the cycle", +…)` and return a sentinel size (e.g. 0 with the error already raised, or poison) +rather than recursing. A pointer field (`*Bad`) breaks the cycle and must stay +allowed (pointers have a fixed size and don't recurse into the pointee). +Verification: run the repro above — expect the loud diagnostic + non-zero exit, +NOT a segfault. Add an `examples/11xx-diagnostics-*` (or `issues/`) pin once the +message is settled. This also closes METATYPE PLAN F5's "by-VALUE self-reference +rejected" item for the comptime path.