diff options
| author | user <user@clank> | 2026-10-09 12:23:50 +0200 |
|---|---|---|
| committer | user <user@clank> | 2026-10-09 12:23:50 +0200 |
| commit | 2cb6dce416c814298f24087fdaafb0c119a584b6 (patch) | |
| tree | 1228aa51fb9a47b6f5881725819296b988a707b3 /include | |
| parent | skill: which pane am I, and how not to land in somebody else's tab (diff) | |
| download | slosh-2cb6dce416c814298f24087fdaafb0c119a584b6.tar.gz slosh-2cb6dce416c814298f24087fdaafb0c119a584b6.tar.xz slosh-2cb6dce416c814298f24087fdaafb0c119a584b6.zip | |
panes: the focus a closed tab left behind
A tab's focus outlived the tree it pointed into. close_leaf's last-pane
branch freed the leaf and removed the tab but never cleared `t->focus`,
and tab_remove only shifts the slots *after* the one it removes -- so with
the last tab gone, `a->tabs[0]` kept the stale tab_t while `ntabs` was 0
and `cur` was 0. `cur(a)` is `&a->tabs[a->cur]` with no bounds check, and
`a->quit` is a request rather than a barrier: the loop keeps serving
commands, and the next one to ask for the focused pane was handed freed
memory.
From outside it looked like a random crash, because what the freed chunk
held depended on what had been allocated since. Two from one afternoon: a
pane_t recycled into a `panes` JSON reply, whose `term` pointer was then
the ASCII of `k":true,` and faulted as a general protection fault; and the
same node read back zeroed, which left pane_clear_bell testing a flag at
NULL+0x5d. Typing `exit` in the last pane is the whole recipe.
close_leaf drops the focus with the tree
tab_remove blanks the slot the shift vacated
app_compose tests ->pane as well as ->focus, the way the search
beside it already did
app_apply_layout clears restore_focus up front -- it is a node pointer,
and an apply that fails before consuming it leaves it
naming a tree the next apply frees
Two more the same reading turned up, both in close_leaf: an emptied split
set the focus to the node just freed, and a leaf whose parent disagreed
with that parent's child list underflowed `p->nkids - at - 1` into a
memmove of nearly SIZE_MAX.
FOCUS_CHECK -- behind -DSLOSH_FOCUS_CHECK, compiled out otherwise -- is
the invariant all of this turns on: every tab's focus is a leaf of that
tab's own tree. It is what found this one.
Committed with --no-verify: the files are deliberately partly staged, and
the staged blobs were checked against clang-format directly.
Diffstat (limited to 'include')
| -rw-r--r-- | include/app_internal.h | 7 |
1 files changed, 7 insertions, 0 deletions
diff --git a/include/app_internal.h b/include/app_internal.h index 1466c89..40d169a 100644 --- a/include/app_internal.h +++ b/include/app_internal.h @@ -459,6 +459,13 @@ static inline tab_t *cur(app_t *a) { return &a->tabs[a->cur]; } typedef void (*leaf_fn)(node_t *, void *); +#ifdef SLOSH_FOCUS_CHECK +void focus_check(app_t *a, const char *where); +#define FOCUS_CHECK(a, where) focus_check((a), (where)) +#else +#define FOCUS_CHECK(a, where) ((void)0) +#endif + /* Formerly static in app.c; shared across the app_*.c units since the * split. Grouped by the unit that defines them. */ |
