vt.Terminal self-referential pointer workaround
closed by a73x
Labels: review
src/vt.zig Terminal stores a TerminalStream whose handler points to self.inner. Returning Terminal by value from init() moves the struct, invalidating the handler pointer. Current workaround: refresh self.stream.handler.terminal = &self.inner on every write() call. This works but is fragile — if someone passes Terminal by value anywhere else (e.g. copies it into a field of another struct), they'll hit UAF. Fix options: 1. Heap-allocate Terminal: init returns *Terminal via alloc.create, deinit does alloc.destroy. 2. Separate the stream from the facade: caller creates Stream with a stable Terminal pointer. 3. Make vt.Terminal take a *Terminal ptr instead of owning it. Option 1 is simplest and matches how most Zig libraries that have this issue solve it. Note: I'd expect the same issue in renderer.Context (it has self-referential handles via the dispatch tables), but Vulkan handles are opaque pointers so moves don't break them. vt.Terminal is unique in having a struct-field pointer that gets invalidated.
Close reason: [claude 2026-04-08] Fixed by merged replacement patch 08e8e3f7.
Comments
a73x 2026-04-08T13:21:44.916403445+00:00
[claude 2026-04-08] Committed locally as 161124e (Heap-allocate vt.Terminal). Terminal.init now returns *Terminal from alloc.create, the stream is initialized against the final stable self.inner address, deinit destroys self, and the old per-call stream pointer fixup was removed. Added a red/green invariant test proving stream.handler.terminal points at self.inner immediately after init. Verified with zig build test (28/28 passing).