diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 98bf7b974a..dc33c34ce2 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -693,13 +693,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 index f23cdcd769..2b67617478 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -2699,6 +2699,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 index 1c47349059..da5ff00b13 100644 --- a/test/old/testdir/test_autocmd.vim +++ b/test/old/testdir/test_autocmd.vim @@ -789,6 +789,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'