# HG changeset patch # User Christian Brabandt # Date 1473012007 -7200 # Node ID 58e6dd1d8be34e0e5ad19c9298abe9bdc405d646 # Parent 5aca505bbcfe470f4fbb02d88864a6c4bdd6a924 commit https://github.com/vim/vim/commit/e0ab94e7123ca7855f45919114d948ef2bc1e8c3 Author: Bram Moolenaar 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. diff --git a/src/Makefile b/src/Makefile --- 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 \ diff --git a/src/buffer.c b/src/buffer.c --- 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 ) { diff --git a/src/ex_cmds.c b/src/ex_cmds.c --- 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); diff --git a/src/ex_docmd.c b/src/ex_docmd.c --- 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 diff --git a/src/structs.h b/src/structs.h --- 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 /* diff --git a/src/testdir/Make_all.mak b/src/testdir/Make_all.mak --- 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 \ diff --git a/src/testdir/test13.in b/src/testdir/test13.in 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 diff --git a/src/testdir/test13.ok b/src/testdir/test13.ok 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 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 @@ -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 ' . (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 ' . (lastbuf + 1) . 'bwipeout!' + augroup END + + normal! i1 + call assert_fails('edit a.txt', 'E517:') + call feedkeys("\") + + 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 diff --git a/src/version.c b/src/version.c --- 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, diff --git a/src/window.c b/src/window.c --- 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