Skip to content

Fix example crashes: allocator misuse and integer underflow#93

Merged
meszmate merged 1 commit intomainfrom
fix/example-allocator-bugs
Apr 23, 2026
Merged

Fix example crashes: allocator misuse and integer underflow#93
meszmate merged 1 commit intomainfrom
fix/example-allocator-bugs

Conversation

@meszmate
Copy link
Copy Markdown
Owner

Summary

Re-apply the fixes from PR #92, which never reached main. (#92 was based on `feature/examples-batch-7`; that branch was merged into main before #92 was merged into the feature branch, so the fixes ended up orphaned.)

Bug 1 — Allocator misuse (5 examples)

Component state was initialized with `ctx.allocator` — the per-frame arena that resets every render — instead of `ctx.persistent_allocator`. The internal `array_list` storage pointed at freed memory after the first `render()`, segfaulting on the second frame.

File Crash site
`braille_canvas.zig` `braille_canvas.zig:329` rendering cell style
`data_table.zig` `data_table.zig:284` reading column width
`rich_log.zig` `rich_log.zig:280` indexing entries
`screen_stack.zig` `screen_stack.zig:156` calling vtable.update
`action_system.zig` `action.zig:138` iterating actions

Switched all per-component init to `ctx.persistent_allocator`, matching the pattern in `examples/file_browser.zig`. Also moved the palette `shortcut` string allocations to persistent in `action_system` since `CommandPalette` stores command structs by reference.

Bug 2 — `async_tasks` integer underflow

Pressing `s` to relaunch while previous tasks were still in-flight reset `tasks_launched` to 3, but the old generation's completion events kept arriving and decremented `tasks_launched` past zero — `u8` underflow panic on the fourth completion.

Two-part fix:

  1. Guard against re-launch while `tasks_launched != 0` (early return)
  2. Saturating subtraction (`-|=`) so any stray duplicate event can't underflow

Test plan

  • `zig build` passes
  • `zig build test` — full suite passes
  • All six fixed examples run cleanly under random-keystroke smoke tests
  • `async_tasks` stress-tested with multiple rapid `s` presses

The fixes from PR #92 landed in feature/examples-batch-7 after #91 had
already merged into main, so they never reached main. Re-apply against
main directly.

Two bug classes:

1. Allocator misuse (5 examples). Component state was initialized with
   ctx.allocator — the per-frame arena that resets every render —
   instead of ctx.persistent_allocator. After the first render the
   internal array_lists pointed at freed memory, causing segfaults /
   bus errors on the second frame:

   - braille_canvas.zig — BrailleCanvas cells
   - data_table.zig    — DataTable columns/rows
   - rich_log.zig      — RichLog entries + search_term buffer
   - screen_stack.zig  — ScreenStack stack array
   - action_system.zig — ActionRegistry actions, CommandPalette
                          commands, palette shortcut strings

   Switched all per-component init to ctx.persistent_allocator,
   matching the pattern used by examples/file_browser.zig.

2. Integer underflow in async_tasks. Pressing 's' to relaunch while
   previous tasks were still in-flight reset tasks_launched to 3 but
   the OLD generation's completion events kept arriving and
   decremented past zero. Two-part fix: guard against re-launch while
   tasks are running, and use saturating subtraction (-|=) so any
   stray duplicate event can't underflow.

Verified with stress tests (rapid keystrokes, multi-launch sequences).
@meszmate meszmate merged commit e44a410 into main Apr 23, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant