changeset 23624:f9d02c83f306 v8.2.2354

patch 8.2.2354: crash with a weird combination of autocommands Commit: https://github.com/vim/vim/commit/797e63b9f2baa1853e7063aac478d663cd02f207 Author: Bram Moolenaar <Bram@vim.org> 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)
author Bram Moolenaar <Bram@vim.org>
date Fri, 15 Jan 2021 16:30:04 +0100
parents 905ba483e284
children 234e2e303385
files src/buffer.c src/ex_cmds.c src/proto/buffer.pro src/testdir/test_autocmd.vim src/version.c
diffstat 5 files changed, 46 insertions(+), 11 deletions(-) [+]
line wrap: on
line diff
--- 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;
 }
 
 /*
--- 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
--- 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);
--- 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
--- 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,