terminal: document the pool reuse zeroing invariant

PageList skips zeroing pooled page buffers in release builds, relying
on the OS page allocator handing out zeroed pages and destroyNodeExt
zeroing buffers before returning them to the pool. There is a hidden
exception: std.heap.MemoryPool writes its free list node into the
first pointer-size bytes of a free-listed buffer, so a reused buffer
is not fully zero. This is only safe because the page rows array is
laid out at offset 0, a page always has at least one row, and initBuf
fully rewrites every row, overwriting the stale free list pointer.

None of that was written down or checked anywhere, so a future layout
reorder (or a zero-row page) would corrupt pages in release builds
only, in a way that depends on pool reuse patterns. This adds a
comptime assert that a Row covers at least a pointer, a runtime assert
that pages always have at least one row, and comments tying the
invariant together at the layout, initBuf, and pool reuse sites.

Also fixes stale doc comments: deinit referenced a clonePool function
that no longer exists, and Screen tests referenced increaseCapacity by
its old adjustCapacity name.
This commit is contained in:
Mitchell Hashimoto
2026-07-07 20:00:30 -07:00
parent e44f5cb0fa
commit c442eb4b30
3 changed files with 34 additions and 6 deletions

View File

@@ -10181,9 +10181,9 @@ test "Screen: cursorDown to page with insufficient capacity" {
// cursor movement functions). The bug pattern:
//
// 1. cursorDown creates a by-value copy of the pin via page_pin.down(n)
// 2. cursorChangePin is called, which may trigger adjustCapacity
// 2. cursorChangePin is called, which may trigger increaseCapacity
// if the target page's style map is full
// 3. adjustCapacity frees the old page and creates a new one
// 3. increaseCapacity frees the old page and creates a new one
// 4. The local pin copy still points to the freed page
// 5. rowAndCell() on the stale pin accesses freed memory
@@ -10206,7 +10206,7 @@ test "Screen: cursorDown to page with insufficient capacity" {
try testing.expect(start_page != new_page);
// Fill new_page's style map to capacity. When we move INTO this page
// with a style set, adjustCapacity will be triggered.
// with a style set, increaseCapacity will be triggered.
{
new_page.pauseIntegrityChecks(true);
defer new_page.pauseIntegrityChecks(false);
@@ -10235,7 +10235,7 @@ test "Screen: cursorDown to page with insufficient capacity" {
try testing.expect(&next_pin.node.data == new_page);
// This cursorDown triggers the bug: the local page_pin copy
// becomes stale after adjustCapacity, causing rowAndCell()
// becomes stale after increaseCapacity, causing rowAndCell()
// to access freed memory.
s.cursorDown(1);