vim-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.

e0ab94e712
This commit is contained in:
Justin M. Keyes 2017-03-20 14:01:22 +01:00
parent ca853edb6f
commit 165ba3e636
9 changed files with 133 additions and 121 deletions

View File

@ -360,6 +360,13 @@ void close_buffer(win_T *win, buf_T *buf, int action, int abort_if_last)
wipe_buf = true;
}
// 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_valid_any_tab(win)) {
// Set b_last_cursor when closing the last window for the buffer.
// Remember the last cursor position and window options of the buffer.
@ -378,14 +385,14 @@ void close_buffer(win_T *win, buf_T *buf, int action, int abort_if_last)
/* 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)) {
// Autocommands deleted the buffer.
EMSG(_(e_auabort));
return;
}
buf->b_closing = false;
buf->b_locked--;
if (abort_if_last && one_window()) {
/* Autocommands made this the only window. */
EMSG(_(e_auabort));
@ -395,14 +402,14 @@ void close_buffer(win_T *win, buf_T *buf, int action, int abort_if_last)
/* When the buffer becomes hidden, but is not unloaded, trigger
* 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.
EMSG(_(e_auabort));
return;
}
buf->b_closing = false;
buf->b_locked--;
if (abort_if_last && one_window()) {
/* Autocommands made this the only window. */
EMSG(_(e_auabort));
@ -559,7 +566,7 @@ void buf_freeall(buf_T *buf, int flags)
tabpage_T *the_curtab = curtab;
// Make sure the buffer isn't closed by autocommands.
buf->b_closing = true;
buf->b_locked++;
bufref_T bufref;
set_bufref(&bufref, buf);
@ -584,7 +591,7 @@ void buf_freeall(buf_T *buf, int flags)
// Autocommands may delete the buffer.
return;
}
buf->b_closing = false;
buf->b_locked--;
// If the buffer was in curwin and the window has changed, go back to that
// window, if it still exists. This avoids that ":edit x" triggering a
@ -1111,7 +1118,7 @@ do_buffer (
* a window with this buffer.
*/
while (buf == curbuf
&& !(curwin->w_closing || curwin->w_buffer->b_closing)
&& !(curwin->w_closing || curwin->w_buffer->b_locked > 0)
&& (firstwin != lastwin || first_tabpage->tp_next != NULL)) {
if (win_close(curwin, FALSE) == FAIL)
break;
@ -4499,7 +4506,7 @@ void ex_buffer_all(exarg_T *eap)
: wp->w_width != Columns)
|| (had_tab > 0 && wp != firstwin)
) && firstwin != lastwin
&& !(wp->w_closing || wp->w_buffer->b_closing)
&& !(wp->w_closing || wp->w_buffer->b_locked > 0)
) {
win_close(wp, FALSE);
wpnext = firstwin; /* just in case an autocommand does

View File

@ -470,9 +470,9 @@ struct file_buffer {
int b_nwindows; /* nr of windows open on this buffer */
int b_flags; /* various BF_ flags */
bool b_closing; /* buffer is being closed, don't let
autocommands close it too. */
int b_flags; // various BF_ flags
int b_locked; // Buffer is being closed or referenced, don't
// let autocommands wipe it out.
/*
* b_ffname has the full path of the file (NULL for no name).

View File

@ -2284,8 +2284,11 @@ int do_ecmd(
} else {
win_T *the_curwin = curwin;
// Set the w_closing flag to avoid that autocommands close the window.
// Set w_closing to avoid that autocommands close the window.
// Set b_locked for the same reason.
the_curwin->w_closing = true;
buf->b_locked++;
if (curbuf == old_curbuf.br_buf) {
buf_copy_options(buf, BCO_ENTER);
}
@ -2298,6 +2301,7 @@ int do_ecmd(
false);
the_curwin->w_closing = false;
buf->b_locked--;
// autocmds may abort script processing
if (aborting() && curwin->w_buffer != NULL) {
@ -2444,11 +2448,6 @@ int do_ecmd(
/* Assume success now */
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.
*/

View File

@ -5775,7 +5775,7 @@ static void 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_nwindows == 1 && wp->w_buffer->b_locked > 0)) {
return;
}
@ -5836,7 +5836,7 @@ static void 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;
exiting = true;
@ -6131,7 +6131,7 @@ static void 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;
// if more files or windows we won't exit

View File

@ -1,63 +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
:/^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
:new|set buftype=help
:wincmd w
:1quit
:$put ='Final line'
:$w >>test.out
:qa!
ENDTEST
start of testfile
contents
contents
contents
end of testfile

View File

@ -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

View File

@ -77,11 +77,49 @@ function Test_autocmd_bufunload_with_tabnext()
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,6 +234,67 @@ 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
func Test_BufEnter()
au! BufEnter
au Bufenter * let val = val . '+'

View File

@ -117,7 +117,7 @@ static int included_patches[] = {
// 2327 NA
2326,
// 2325 NA
// 2324,
2324,
2323,
2322,
2321,

View File

@ -1727,7 +1727,7 @@ void close_windows(buf_T *buf, int keep_curwin)
for (win_T *wp = firstwin; wp != NULL && lastwin != firstwin; ) {
if (wp->w_buffer == buf && (!keep_curwin || wp != curwin)
&& !(wp->w_closing || wp->w_buffer->b_closing)) {
&& !(wp->w_closing || wp->w_buffer->b_locked > 0)) {
if (win_close(wp, false) == FAIL) {
// If closing the window fails give up, to avoid looping forever.
break;
@ -1745,8 +1745,7 @@ void close_windows(buf_T *buf, int keep_curwin)
if (tp != curtab) {
FOR_ALL_WINDOWS_IN_TAB(wp, tp) {
if (wp->w_buffer == buf
&& !(wp->w_closing || wp->w_buffer->b_closing)
) {
&& !(wp->w_closing || wp->w_buffer->b_locked > 0)) {
win_close_othertab(wp, FALSE, tp);
/* Start all over, the tab page may be closed and
@ -1882,8 +1881,9 @@ int win_close(win_T *win, int free_buf)
return FAIL;
}
if (win->w_closing || (win->w_buffer != NULL && win->w_buffer->b_closing))
return FAIL; /* window is already being closed */
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) {
EMSG(_("E813: Cannot close autocmd window"));
return FAIL;
@ -2064,7 +2064,8 @@ void win_close_othertab(win_T *win, int free_buf, tabpage_T *tp)
// 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
}