changeset 10106:58e6dd1d8be3 v7.4.2324

commit https://github.com/vim/vim/commit/e0ab94e7123ca7855f45919114d948ef2bc1e8c3 Author: Bram Moolenaar <Bram@vim.org> Date: Sun Sep 4 19:50:54 2016 +0200 patch 7.4.2324 Problem: Crash when editing a new buffer and BufUnload autocommand wipes out the new buffer. (Norio Takagi) Solution: Don't allow wiping out this buffer. (partly by Hirohito Higashi) Move old style test13 into test_autocmd. Avoid ml_get error when editing a file.
author Christian Brabandt <cb@256bit.org>
date Sun, 04 Sep 2016 20:00:07 +0200
parents 5aca505bbcfe
children 3164c4afc969
files src/Makefile src/buffer.c src/ex_cmds.c src/ex_docmd.c src/structs.h src/testdir/Make_all.mak src/testdir/test13.in src/testdir/test13.ok src/testdir/test_autocmd.vim src/version.c src/window.c
diffstat 11 files changed, 133 insertions(+), 122 deletions(-) [+]
line wrap: on
line diff
--- a/src/Makefile
+++ b/src/Makefile
@@ -2042,7 +2042,7 @@ test1 \
 	test_utf8 \
 	test_wordcount \
 	test2 test3 test4 test5 test6 test7 test8 test9 \
-	test11 test12 test13 test14 test15 test17 test18 test19 \
+	test11 test12 test14 test15 test17 test18 test19 \
 	test20 test21 test22 test23 test24 test25 test26 test27 test28 test29 \
 	test30 test31 test32 test33 test34 test36 test37 test38 test39 \
 	test40 test41 test42 test43 test44 test45 test48 test49 \
--- a/src/buffer.c
+++ b/src/buffer.c
@@ -476,6 +476,14 @@ close_buffer(
 	unload_buf = TRUE;
 #endif
 
+    /* Disallow deleting the buffer when it is locked (already being closed or
+     * halfway a command that relies on it). Unloading is allowed. */
+    if (buf->b_locked > 0 && (del_buf || wipe_buf))
+    {
+	EMSG(_("E937: Attempt to delete a buffer that is in use"));
+	return;
+    }
+
     if (win != NULL
 #ifdef FEAT_WINDOWS
 	&& win_valid_any_tab(win) /* in case autocommands closed the window */
@@ -499,7 +507,7 @@ close_buffer(
     /* When the buffer is no longer in a window, trigger BufWinLeave */
     if (buf->b_nwindows == 1)
     {
-	buf->b_closing = TRUE;
+	++buf->b_locked;
 	if (apply_autocmds(EVENT_BUFWINLEAVE, buf->b_fname, buf->b_fname,
 								  FALSE, buf)
 		&& !bufref_valid(&bufref))
@@ -509,7 +517,7 @@ aucmd_abort:
 	    EMSG(_(e_auabort));
 	    return;
 	}
-	buf->b_closing = FALSE;
+	--buf->b_locked;
 	if (abort_if_last && one_window())
 	    /* Autocommands made this the only window. */
 	    goto aucmd_abort;
@@ -518,13 +526,13 @@ aucmd_abort:
 	 * BufHidden */
 	if (!unload_buf)
 	{
-	    buf->b_closing = TRUE;
+	    ++buf->b_locked;
 	    if (apply_autocmds(EVENT_BUFHIDDEN, buf->b_fname, buf->b_fname,
 								  FALSE, buf)
 		    && !bufref_valid(&bufref))
 		/* Autocommands deleted the buffer. */
 		goto aucmd_abort;
-	    buf->b_closing = FALSE;
+	    --buf->b_locked;
 	    if (abort_if_last && one_window())
 		/* Autocommands made this the only window. */
 		goto aucmd_abort;
@@ -685,7 +693,7 @@ buf_freeall(buf_T *buf, int flags)
 # endif
 
     /* Make sure the buffer isn't closed by autocommands. */
-    buf->b_closing = TRUE;
+    ++buf->b_locked;
     set_bufref(&bufref, buf);
     if (buf->b_ml.ml_mfp != NULL)
     {
@@ -711,7 +719,7 @@ buf_freeall(buf_T *buf, int flags)
 	    /* autocommands deleted the buffer */
 	    return;
     }
-    buf->b_closing = FALSE;
+    --buf->b_locked;
 
 # ifdef FEAT_WINDOWS
     /* If the buffer was in curwin and the window has changed, go back to that
@@ -1369,7 +1377,7 @@ do_buffer(
 	 */
 	while (buf == curbuf
 # ifdef FEAT_AUTOCMD
-		   && !(curwin->w_closing || curwin->w_buffer->b_closing)
+		   && !(curwin->w_closing || curwin->w_buffer->b_locked > 0)
 # endif
 		   && (firstwin != lastwin || first_tabpage->tp_next != NULL))
 	{
@@ -5100,7 +5108,7 @@ ex_buffer_all(exarg_T *eap)
 #endif
 		    ) && firstwin != lastwin
 #ifdef FEAT_AUTOCMD
-		    && !(wp->w_closing || wp->w_buffer->b_closing)
+		    && !(wp->w_closing || wp->w_buffer->b_locked > 0)
 #endif
 		    )
 	    {
--- a/src/ex_cmds.c
+++ b/src/ex_cmds.c
@@ -3872,8 +3872,8 @@ do_ecmd(
 	    oldbuf = TRUE;
 	    set_bufref(&bufref, buf);
 	    (void)buf_check_timestamp(buf, FALSE);
-	    /* Check if autocommands made buffer invalid or changed the current
-	     * buffer. */
+	    /* Check if autocommands made the buffer invalid or changed the
+	     * current buffer. */
 	    if (!bufref_valid(&bufref)
 #ifdef FEAT_AUTOCMD
 		    || curbuf != old_curbuf.br_buf
@@ -3938,8 +3938,9 @@ do_ecmd(
 		win_T	    *the_curwin = curwin;
 
 		/* Set the w_closing flag to avoid that autocommands close the
-		 * window. */
+		 * window.  And set b_locked for the same reason. */
 		the_curwin->w_closing = TRUE;
+		++buf->b_locked;
 
 		if (curbuf == old_curbuf.br_buf)
 #endif
@@ -3953,6 +3954,7 @@ do_ecmd(
 
 #ifdef FEAT_AUTOCMD
 		the_curwin->w_closing = FALSE;
+		--buf->b_locked;
 
 # ifdef FEAT_EVAL
 		/* autocmds may abort script processing */
@@ -4140,11 +4142,6 @@ do_ecmd(
     retval = OK;
 
     /*
-     * Reset cursor position, could be used by autocommands.
-     */
-    check_cursor();
-
-    /*
      * Check if we are editing the w_arg_idx file in the argument list.
      */
     check_arg_idx(curwin);
--- a/src/ex_docmd.c
+++ b/src/ex_docmd.c
@@ -7201,7 +7201,7 @@ ex_quit(exarg_T *eap)
     /* Refuse to quit when locked or when the buffer in the last window is
      * being closed (can only happen in autocommands). */
     if (curbuf_locked() || (wp->w_buffer->b_nwindows == 1
-						  && wp->w_buffer->b_closing))
+						&& wp->w_buffer->b_locked > 0))
 	return;
 #endif
 
@@ -7283,7 +7283,7 @@ ex_quit_all(exarg_T *eap)
     apply_autocmds(EVENT_QUITPRE, NULL, NULL, FALSE, curbuf);
     /* Refuse to quit when locked or when the buffer in the last window is
      * being closed (can only happen in autocommands). */
-    if (curbuf_locked() || (curbuf->b_nwindows == 1 && curbuf->b_closing))
+    if (curbuf_locked() || (curbuf->b_nwindows == 1 && curbuf->b_locked > 0))
 	return;
 #endif
 
@@ -7665,7 +7665,7 @@ ex_exit(exarg_T *eap)
     apply_autocmds(EVENT_QUITPRE, NULL, NULL, FALSE, curbuf);
     /* Refuse to quit when locked or when the buffer in the last window is
      * being closed (can only happen in autocommands). */
-    if (curbuf_locked() || (curbuf->b_nwindows == 1 && curbuf->b_closing))
+    if (curbuf_locked() || (curbuf->b_nwindows == 1 && curbuf->b_locked > 0))
 	return;
 #endif
 
--- a/src/structs.h
+++ b/src/structs.h
@@ -1845,8 +1845,8 @@ struct file_buffer
 
     int		b_flags;	/* various BF_ flags */
 #ifdef FEAT_AUTOCMD
-    int		b_closing;	/* buffer is being closed, don't let
-				   autocommands close it too. */
+    int		b_locked;	/* Buffer is being closed or referenced, don't
+				   let autocommands wipe it out. */
 #endif
 
     /*
--- a/src/testdir/Make_all.mak
+++ b/src/testdir/Make_all.mak
@@ -111,7 +111,6 @@ SCRIPTS_MORE1 = \
 SCRIPTS_MORE2 = \
 	test2.out \
 	test12.out \
-	test13.out \
 	test25.out \
 	test49.out \
 	test97.out \
deleted file mode 100644
--- a/src/testdir/test13.in
+++ /dev/null
@@ -1,64 +0,0 @@
-Tests for autocommands on :close command
-
-Write three files and open them, each in a window.
-Then go to next window, with autocommand that deletes the previous one.
-Do this twice, writing the file.
-
-Also test deleting the buffer on a Unload event.  If this goes wrong there
-will be the ATTENTION prompt.
-
-Also test changing buffers in a BufDel autocommand.  If this goes wrong there
-are ml_line errors and/or a Crash.
-
-STARTTEST
-:so small.vim
-:/^start of testfile/,/^end of testfile/w! Xtestje1
-:/^start of testfile/,/^end of testfile/w! Xtestje2
-:/^start of testfile/,/^end of testfile/w! Xtestje3
-:e Xtestje1
-otestje1
-:w
-:sp Xtestje2
-otestje2
-:w
-:sp Xtestje3
-otestje3
-:w
-
-:au WinLeave Xtestje2 bwipe
-
-:w! test.out
-:au WinLeave Xtestje1 bwipe Xtestje3
-:close
-:w >>test.out
-:e Xtestje1
-:bwipe Xtestje2 Xtestje3 test.out
-:au!
-:au! BufUnload Xtestje1 bwipe
-:e Xtestje3
-:w >>test.out
-:e Xtestje2
-:sp Xtestje1
-:e
-:w >>test.out
-:au!
-:only
-:e Xtestje1
-:bwipe Xtestje2 Xtestje3 test.out test13.in
-:au BufWipeout Xtestje1 buf Xtestje1
-:bwipe
-:w >>test.out
-:only
-:help
-:wincmd w
-:1quit
-:$put ='Final line'
-:$w >>test.out
-:qa!
-ENDTEST
-
-start of testfile
-	contents
-	contents
-	contents
-end of testfile
deleted file mode 100644
--- a/src/testdir/test13.ok
+++ /dev/null
@@ -1,31 +0,0 @@
-start of testfile
-testje1
-	contents
-	contents
-	contents
-end of testfile
-start of testfile
-testje1
-	contents
-	contents
-	contents
-end of testfile
-start of testfile
-testje3
-	contents
-	contents
-	contents
-end of testfile
-start of testfile
-testje2
-	contents
-	contents
-	contents
-end of testfile
-start of testfile
-testje1
-	contents
-	contents
-	contents
-end of testfile
-Final line
--- a/src/testdir/test_autocmd.vim
+++ b/src/testdir/test_autocmd.vim
@@ -77,11 +77,49 @@ function Test_autocmd_bufunload_with_tab
   quit
   call assert_equal(2, tabpagenr('$'))
 
+  autocmd! test_autocmd_bufunload_with_tabnext_group
   augroup! test_autocmd_bufunload_with_tabnext_group
   tablast
   quit
 endfunc
 
+" SEGV occurs in older versions.  (At least 7.4.2321 or older)
+function Test_autocmd_bufunload_avoiding_SEGV_01()
+  split aa.txt
+  let lastbuf = bufnr('$')
+
+  augroup test_autocmd_bufunload
+    autocmd!
+    exe 'autocmd BufUnload <buffer> ' . (lastbuf + 1) . 'bwipeout!'
+  augroup END
+
+  call assert_fails('edit bb.txt', 'E937:')
+
+  autocmd! test_autocmd_bufunload
+  augroup! test_autocmd_bufunload
+  bwipe! aa.txt
+  bwipe! bb.txt
+endfunc
+
+" SEGV occurs in older versions.  (At least 7.4.2321 or older)
+function Test_autocmd_bufunload_avoiding_SEGV_02()
+  setlocal buftype=nowrite
+  let lastbuf = bufnr('$')
+
+  augroup test_autocmd_bufunload
+    autocmd!
+    exe 'autocmd BufUnload <buffer> ' . (lastbuf + 1) . 'bwipeout!'
+  augroup END
+
+  normal! i1
+  call assert_fails('edit a.txt', 'E517:')
+  call feedkeys("\<CR>")
+
+  autocmd! test_autocmd_bufunload
+  augroup! test_autocmd_bufunload
+  bwipe! a.txt
+endfunc
+
 func Test_win_tab_autocmd()
   let g:record = []
 
@@ -196,3 +234,63 @@ func Test_augroup_deleted()
   au! VimEnter
 endfunc
 
+" Tests for autocommands on :close command.
+" This used to be in test13.
+func Test_three_windows()
+  " Write three files and open them, each in a window.
+  " Then go to next window, with autocommand that deletes the previous one.
+  " Do this twice, writing the file.
+  e! Xtestje1
+  call setline(1, 'testje1')
+  w
+  sp Xtestje2
+  call setline(1, 'testje2')
+  w
+  sp Xtestje3
+  call setline(1, 'testje3')
+  w
+  wincmd w
+  au WinLeave Xtestje2 bwipe
+  wincmd w
+  call assert_equal('Xtestje1', expand('%'))
+
+  au WinLeave Xtestje1 bwipe Xtestje3
+  close
+  call assert_equal('Xtestje1', expand('%'))
+
+  " Test deleting the buffer on a Unload event.  If this goes wrong there
+  " will be the ATTENTION prompt.
+  e Xtestje1
+  au!
+  au! BufUnload Xtestje1 bwipe
+  call assert_fails('e Xtestje3', 'E937:')
+  call assert_equal('Xtestje3', expand('%'))
+
+  e Xtestje2
+  sp Xtestje1
+  call assert_fails('e', 'E937:')
+  call assert_equal('Xtestje2', expand('%'))
+
+  " Test changing buffers in a BufWipeout autocommand.  If this goes wrong
+  " there are ml_line errors and/or a Crash.
+  au!
+  only
+  e Xanother
+  e Xtestje1
+  bwipe Xtestje2
+  bwipe Xtestje3
+  au BufWipeout Xtestje1 buf Xtestje1
+  bwipe
+  call assert_equal('Xanother', expand('%'))
+
+  only
+  help
+  wincmd w
+  1quit
+  call assert_equal('Xanother', expand('%'))
+
+  au!
+  call delete('Xtestje1')
+  call delete('Xtestje2')
+  call delete('Xtestje3')
+endfunc
--- a/src/version.c
+++ b/src/version.c
@@ -764,6 +764,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    2324,
+/**/
     2323,
 /**/
     2322,
--- a/src/window.c
+++ b/src/window.c
@@ -2127,7 +2127,7 @@ close_windows(
     {
 	if (wp->w_buffer == buf && (!keep_curwin || wp != curwin)
 #ifdef FEAT_AUTOCMD
-		&& !(wp->w_closing || wp->w_buffer->b_closing)
+		&& !(wp->w_closing || wp->w_buffer->b_locked > 0)
 #endif
 		)
 	{
@@ -2148,7 +2148,7 @@ close_windows(
 	    for (wp = tp->tp_firstwin; wp != NULL; wp = wp->w_next)
 		if (wp->w_buffer == buf
 #ifdef FEAT_AUTOCMD
-		    && !(wp->w_closing || wp->w_buffer->b_closing)
+		    && !(wp->w_closing || wp->w_buffer->b_locked > 0)
 #endif
 		    )
 		{
@@ -2287,7 +2287,8 @@ win_close(win_T *win, int free_buf)
     }
 
 #ifdef FEAT_AUTOCMD
-    if (win->w_closing || (win->w_buffer != NULL && win->w_buffer->b_closing))
+    if (win->w_closing || (win->w_buffer != NULL
+					       && win->w_buffer->b_locked > 0))
 	return FAIL; /* window is already being closed */
     if (win == aucmd_win)
     {
@@ -2503,7 +2504,8 @@ win_close_othertab(win_T *win, int free_
 #ifdef FEAT_AUTOCMD
     /* Get here with win->w_buffer == NULL when win_close() detects the tab
      * page changed. */
-    if (win->w_closing || (win->w_buffer != NULL && win->w_buffer->b_closing))
+    if (win->w_closing || (win->w_buffer != NULL
+					       && win->w_buffer->b_locked > 0))
 	return; /* window is already being closed */
 #endif