commit eb5a7cc0ddf07a29b2c8c9c68ff50321dece845f
parent 06e4b159ed08b50779e4d4aaab3ccf7a260daeac
Author: zeertzjq <zeertzjq@outlook.com>
Date: Sat, 17 Jan 2026 05:53:55 +0800
vim-patch:9.1.2087: Crash when using :tabonly in BufUnload
Problem: Crash when using :tabonly in BufUnload.
Solution: Set curbuf when setting curwin->w_buffer. Don't wipe out a
buffer if there are no other buffers. Don't decrement
b_nwindows if it was 0 before buf_freeall() (zeertzjq).
fixes: vim/vim#19088#issuecomment-3710172769
closes: vim/vim#19186
https://github.com/vim/vim/commit/fa64f92f6ab8b8080bdba77155e7bb3530fa21f6
Diffstat:
3 files changed, 49 insertions(+), 3 deletions(-)
diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c
@@ -718,13 +718,16 @@ bool close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last, bool i
// Autocommands may have opened or closed windows for this buffer.
// Decrement the count for the close we do here.
- if (buf->b_nwindows > 0) {
+ // Don't decrement b_nwindows if the buffer wasn't displayed in any window
+ // before calling buf_freeall(),
+ if (nwindows > 0 && buf->b_nwindows > 0) {
buf->b_nwindows--;
}
// Remove the buffer from the list.
- // Do not wipe out the buffer if it is used in a window.
- if (wipe_buf && buf->b_nwindows <= 0) {
+ // Do not wipe out the buffer if it is used in a window, or if autocommands
+ // wiped out all other buffers.
+ if (wipe_buf && buf->b_nwindows <= 0 && (buf->b_prev != NULL || buf->b_next != NULL)) {
if (clear_w_buf) {
win->w_buffer = NULL;
}
diff --git a/src/nvim/window.c b/src/nvim/window.c
@@ -2765,6 +2765,9 @@ static void win_unclose_buffer(win_T *win, bufref_T *bufref, bool did_decrement)
// If the buffer was removed from the window we have to give it any buffer.
win->w_buffer = firstbuf;
firstbuf->b_nwindows++;
+ if (win == curwin) {
+ curbuf = curwin->w_buffer;
+ }
win_init_empty(win);
} else if (did_decrement && win->w_buffer == bufref->br_buf && bufref_valid(bufref)) {
// close_buffer() decremented the window count, but we're keeping the window.
diff --git a/test/old/testdir/test_autocmd.vim b/test/old/testdir/test_autocmd.vim
@@ -862,6 +862,46 @@ func Test_BufUnload_close_other()
call Run_test_BufUnload_close_other('setlocal bufhidden=wipe')
endfunc
+func Run_test_BufUnload_tabonly(first_cmd)
+ exe a:first_cmd
+ tabnew Xa
+ setlocal bufhidden=wipe
+ tabprevious
+ autocmd BufWinLeave Xa ++once tabnext
+ autocmd BufUnload Xa ++once tabonly
+ tabonly
+
+ %bwipe!
+endfunc
+
+func Test_BufUnload_tabonly()
+ " This used to dereference a NULL curbuf.
+ call Run_test_BufUnload_tabonly('setlocal bufhidden=hide')
+ " This used to dereference a NULL firstbuf.
+ call Run_test_BufUnload_tabonly('setlocal bufhidden=wipe')
+endfunc
+
+func Run_test_BufUnload_tabonly_nested(second_autocmd)
+ file Xa
+ tabnew Xb
+ setlocal bufhidden=wipe
+ tabnew Xc
+ setlocal bufhidden=wipe
+ autocmd BufUnload Xb ++once ++nested bwipe! Xa
+ exe $'autocmd BufUnload Xa ++once ++nested {a:second_autocmd}'
+ autocmd BufWinLeave Xc ++once tabnext
+ tabfirst
+ 2tabclose
+
+ %bwipe!
+endfunc
+
+func Test_BufUnload_tabonly_nested()
+ " These used to cause heap-use-after-free.
+ call Run_test_BufUnload_tabonly_nested('tabonly')
+ call Run_test_BufUnload_tabonly_nested('tabonly | tabprevious')
+endfunc
+
func s:AddAnAutocmd()
augroup vimBarTest
au BufReadCmd * echo 'hello'