issue(0139): by-value self-referential type segfaults (typeSizeBytes recursion)
Discovered while testing metatype self-reference: a by-VALUE self-ref
(`payload = List`, not `*List`) infinite-loops typeSizeBytes → segfault
instead of a loud "infinite size" diagnostic. PRE-EXISTING — a hand-written
source enum `enum { node: Bad; leaf }` crashes identically, so it's a
general type-system gap (the comptime F5 by-value-rejection inherits the
fix). Filed per the IMPASSABLE rule; metatype checkpoint notes it.
This commit is contained in:
59
issues/0139-byvalue-self-reference-segfault.md
Normal file
59
issues/0139-byvalue-self-reference-segfault.md
Normal file
@@ -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.
|
||||
Reference in New Issue
Block a user