# HG changeset patch # User Bram Moolenaar # Date 1610724604 -3600 # Node ID f9d02c83f30676857c192cd83b7548bb15699d69 # Parent 905ba483e28420936df3e85c078be17c0133ab16 patch 8.2.2354: crash with a weird combination of autocommands Commit: https://github.com/vim/vim/commit/797e63b9f2baa1853e7063aac478d663cd02f207 Author: Bram Moolenaar Date: Fri Jan 15 16:22:52 2021 +0100 patch 8.2.2354: crash with a weird combination of autocommands Problem: Crash with a weird combination of autocommands. Solution: Increment b_nwindows when needed. (closes https://github.com/vim/vim/issues/7674) diff --git a/src/buffer.c b/src/buffer.c --- a/src/buffer.c +++ b/src/buffer.c @@ -492,8 +492,10 @@ can_unload_buffer(buf_T *buf) * supposed to close the window but autocommands close all other windows. * * When "ignore_abort" is TRUE don't abort even when aborting() returns TRUE. + * + * Return TRUE when we got to the end and b_nwindows was decremented. */ - void + int close_buffer( win_T *win, // if not NULL, set b_last_cursor buf_T *buf, @@ -540,7 +542,7 @@ close_buffer( if (wipe_buf || unload_buf) { if (!can_unload_buffer(buf)) - return; + return FALSE; // Wiping out or unloading a terminal buffer kills the job. free_terminal(buf); @@ -571,7 +573,7 @@ close_buffer( // Disallow deleting the buffer when it is locked (already being closed or // halfway a command that relies on it). Unloading is allowed. if ((del_buf || wipe_buf) && !can_unload_buffer(buf)) - return; + return FALSE; // check no autocommands closed the window if (win != NULL && win_valid_any_tab(win)) @@ -600,7 +602,7 @@ close_buffer( // Autocommands deleted the buffer. aucmd_abort: emsg(_(e_auabort)); - return; + return FALSE; } --buf->b_locked; if (abort_if_last && one_window()) @@ -625,7 +627,7 @@ aucmd_abort: #ifdef FEAT_EVAL // autocmds may abort script processing if (!ignore_abort && aborting()) - return; + return FALSE; #endif } @@ -653,7 +655,7 @@ aucmd_abort: // Return when a window is displaying the buffer or when it's not // unloaded. if (buf->b_nwindows > 0 || !unload_buf) - return; + return FALSE; // Always remove the buffer when there is no file name. if (buf->b_ffname == NULL) @@ -683,11 +685,11 @@ aucmd_abort: // Autocommands may have deleted the buffer. if (!bufref_valid(&bufref)) - return; + return FALSE; #ifdef FEAT_EVAL // autocmds may abort script processing if (!ignore_abort && aborting()) - return; + return FALSE; #endif /* @@ -698,7 +700,7 @@ aucmd_abort: * deleted buffer. */ if (buf == curbuf && !is_curbuf) - return; + return FALSE; if (win_valid_any_tab(win) && win->w_buffer == buf) win->w_buffer = NULL; // make sure we don't use the buffer now @@ -755,6 +757,7 @@ aucmd_abort: buf->b_p_bl = FALSE; } // NOTE: at this point "curbuf" may be invalid! + return TRUE; } /* diff --git a/src/ex_cmds.c b/src/ex_cmds.c --- a/src/ex_cmds.c +++ b/src/ex_cmds.c @@ -2742,6 +2742,8 @@ do_ecmd( else { win_T *the_curwin = curwin; + int did_decrement; + buf_T *was_curbuf = curbuf; // Set the w_closing flag to avoid that autocommands close the // window. And set b_locked for the same reason. @@ -2754,7 +2756,7 @@ do_ecmd( // Close the link to the current buffer. This will set // oldwin->w_buffer to NULL. u_sync(FALSE); - close_buffer(oldwin, curbuf, + did_decrement = close_buffer(oldwin, curbuf, (flags & ECMD_HIDE) ? 0 : DOBUF_UNLOAD, FALSE, FALSE); the_curwin->w_closing = FALSE; @@ -2776,7 +2778,15 @@ do_ecmd( goto theend; } if (buf == curbuf) // already in new buffer + { + // close_buffer() has decremented the window count, + // increment it again here and restore w_buffer. + if (did_decrement && buf_valid(was_curbuf)) + ++was_curbuf->b_nwindows; + if (win_valid_any_tab(oldwin) && oldwin->w_buffer == NULL) + oldwin->w_buffer = was_curbuf; auto_buf = TRUE; + } else { #ifdef FEAT_SYN_HL diff --git a/src/proto/buffer.pro b/src/proto/buffer.pro --- a/src/proto/buffer.pro +++ b/src/proto/buffer.pro @@ -5,7 +5,7 @@ int open_buffer(int read_stdin, exarg_T void set_bufref(bufref_T *bufref, buf_T *buf); int bufref_valid(bufref_T *bufref); int buf_valid(buf_T *buf); -void close_buffer(win_T *win, buf_T *buf, int action, int abort_if_last, int ignore_abort); +int close_buffer(win_T *win, buf_T *buf, int action, int abort_if_last, int ignore_abort); void buf_clear_file(buf_T *buf); void buf_freeall(buf_T *buf, int flags); void free_wininfo(wininfo_T *wip); diff --git a/src/testdir/test_autocmd.vim b/src/testdir/test_autocmd.vim --- a/src/testdir/test_autocmd.vim +++ b/src/testdir/test_autocmd.vim @@ -500,6 +500,26 @@ func Test_autocmd_bufwipe_in_SessLoadPos endfor endfunc +" Using :blast and :ball for many events caused a crash, because b_nwindows was +" not incremented correctly. +func Test_autocmd_blast_badd() + let content =<< trim [CODE] + au BufNew,BufAdd,BufWinEnter,BufEnter,BufLeave,BufWinLeave,BufUnload,VimEnter foo* blast + edit foo1 + au BufNew,BufAdd,BufWinEnter,BufEnter,BufLeave,BufWinLeave,BufUnload,VimEnter foo* ball + edit foo2 + call writefile(['OK'], 'Xerrors') + qall + [CODE] + + call writefile(content, 'XblastBall') + call system(GetVimCommand() .. ' --clean -S XblastBall') + call assert_match('OK', readfile('Xerrors')->join()) + + call delete('XblastBall') + call delete('Xerrors') +endfunc + " SEGV occurs in older versions. func Test_autocmd_bufwipe_in_SessLoadPost2() tabnew diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -751,6 +751,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 2354, +/**/ 2353, /**/ 2352,