Skip to content

Commit d38ba7e

Browse files
seandewarzeertzjq
authored andcommitted
fix(window): restore b_nwindows if win_close_othertab keeps window
Problem: can't accurately know if close_buffer directly (e.g: not via autocmds) decremented b_nwindows. This can cause crashes if win_close_othertab decides to keep the window after calling close_buffer (if it did not free the buffer), as b_nwindows may remain out-of-sync. Solution: change the return value of close_buffer to accurately depict whether it decremented b_nwindows. Check it in win_close_othertab to avoid a crash. Similar issues may exist in other places that call close_buffer, but I've not addressed those here (not to mention only one other place even checks its return value...)
1 parent a3dabcf commit d38ba7e

File tree

3 files changed

+36
-9
lines changed

3 files changed

+36
-9
lines changed

src/nvim/buffer.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -500,7 +500,7 @@ static bool can_unload_buffer(buf_T *buf)
500500
/// close all other windows.
501501
/// @param ignore_abort
502502
/// If true, don't abort even when aborting() returns true.
503-
/// @return true when we got to the end and b_nwindows was decremented.
503+
/// @return true if b_nwindows was decremented directly by this call (e.g: not via autocmds).
504504
bool close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last, bool ignore_abort)
505505
{
506506
bool unload_buf = (action != 0);
@@ -625,7 +625,7 @@ bool close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last, bool i
625625
// Return when a window is displaying the buffer or when it's not
626626
// unloaded.
627627
if (buf->b_nwindows > 0 || !unload_buf) {
628-
return false;
628+
return true;
629629
}
630630

631631
if (buf->terminal) {
@@ -702,7 +702,7 @@ bool close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last, bool i
702702
}
703703
// Do not wipe out the buffer if it is used in a window.
704704
if (buf->b_nwindows > 0) {
705-
return false;
705+
return true;
706706
}
707707
FOR_ALL_TAB_WINDOWS(tp, wp) {
708708
mark_forget_file(wp, buf->b_fnum);

src/nvim/window.c

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2984,6 +2984,7 @@ bool win_close_othertab(win_T *win, int free_buf, tabpage_T *tp, bool force)
29842984
FUNC_ATTR_NONNULL_ALL
29852985
{
29862986
assert(tp != curtab);
2987+
bool did_decrement = false;
29872988

29882989
// Get here with win->w_buffer == NULL when win_close() detects the tab page
29892990
// changed.
@@ -3023,9 +3024,12 @@ bool win_close_othertab(win_T *win, int free_buf, tabpage_T *tp, bool force)
30233024
}
30243025
}
30253026

3027+
bufref_T bufref;
3028+
set_bufref(&bufref, win->w_buffer);
3029+
30263030
if (win->w_buffer != NULL) {
30273031
// Close the link to the buffer.
3028-
close_buffer(win, win->w_buffer, free_buf ? DOBUF_UNLOAD : 0, false, true);
3032+
did_decrement = close_buffer(win, win->w_buffer, free_buf ? DOBUF_UNLOAD : 0, false, true);
30293033
}
30303034

30313035
// Careful: Autocommands may have closed the tab page or made it the
@@ -3089,11 +3093,17 @@ bool win_close_othertab(win_T *win, int free_buf, tabpage_T *tp, bool force)
30893093
return true;
30903094

30913095
leave_open:
3092-
// If the buffer was removed from the window we have to give it any buffer.
3093-
if (win_valid_any_tab(win) && win->w_buffer == NULL) {
3094-
win->w_buffer = firstbuf;
3095-
firstbuf->b_nwindows++;
3096-
win_init_empty(win);
3096+
if (win_valid_any_tab(win)) {
3097+
if (win->w_buffer == NULL) {
3098+
// If the buffer was removed from the window we have to give it any buffer.
3099+
win->w_buffer = firstbuf;
3100+
firstbuf->b_nwindows++;
3101+
win_init_empty(win);
3102+
} else if (did_decrement && win->w_buffer == bufref.br_buf && bufref_valid(&bufref)) {
3103+
// close_buffer decremented the window count, but we're keeping the window.
3104+
// As the window is still viewing the buffer, increment the count.
3105+
win->w_buffer->b_nwindows++;
3106+
}
30973107
}
30983108
return false;
30993109
}

test/functional/api/window_spec.lua

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1986,6 +1986,23 @@ describe('API/win', function()
19861986
pcall_err(command, 'call nvim_win_close(g:win, 0)')
19871987
)
19881988
eq(true, eval 'nvim_tabpage_is_valid(g:tp)')
1989+
1990+
exec([[
1991+
tabnew
1992+
let g:tp = nvim_get_current_tabpage()
1993+
let g:win = win_getid()
1994+
let g:buf = bufnr()
1995+
tabprevious
1996+
let s:buf2 = nvim_create_buf(0, 0)
1997+
call setbufvar(s:buf2, '&modified', 1)
1998+
call setbufvar(s:buf2, '&bufhidden', 'wipe')
1999+
autocmd! WinClosed * ++once call nvim_open_win(s:buf2, 0, #{win: g:win, relative: 'win', width: 5, height: 5, row: 5, col: 5})
2000+
]])
2001+
matches(
2002+
'E5601: Cannot close window, only floating window would remain$',
2003+
pcall_err(command, 'call nvim_buf_delete(g:buf, #{force: 1})')
2004+
)
2005+
eq(true, eval 'nvim_tabpage_is_valid(g:tp)')
19892006
end)
19902007

19912008
it('respects requested size for large splits', function()

0 commit comments

Comments
 (0)