Hi Bram and developers, I checked in 7.4.2321
==== Case 1 ==== How to reproduce: - Create the following file: $ cat sample1.vim edit a.txt augroup sample autocmd! autocmd BufUnload <buffer> tabfirst | 2bwipeout! augroup END edit b.txt - Run vanilla Vim with above script file $ vim -Nu NONE -S sample1.vim Expected behavior: SEGV does not occur. Actual behavior: SEGVed. ==== Case 2 ==== How to reproduce: - Create the following file: $ cat sample2.vim setlocal buftype=nowrite augroup sample autocmd! autocmd BufUnload <buffer> tabfirst | 2bwipeout augroup END normal! i1 edit a.txt call feedkeys("\<CR>") - Run vanilla Vim with above script file $ vim -Nu NONE -S sample2.vim Expected behavior: SEGV does not occur. Actual behavior: SEGVed. I know there are rare case and salicious scripts. But, It is not good to SEGV. I wrote a patch. --> `fix_autocmd_bufunload_with_bwipe.patch` check it out. I've also written test. --> `autocmd_bufunload_with_bwipe_test.patch` Unfortunately, it did not SEGV in the pre-patch binary :-/ NOTE: This issue was reported by Norio Takagi. (Thanks!) -- Best regards, Hirohito Higashi (a.k.a. h_east) -- -- You received this message from the "vim_dev" maillist. Do not top-post! Type your reply below the text you are replying to. For more information, visit http://www.vim.org/maillist.php --- You received this message because you are subscribed to the Google Groups "vim_dev" group. To unsubscribe from this group and stop receiving emails from it, send an email to vim_dev+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/d/optout.
diff --git a/src/buffer.c b/src/buffer.c index 9270c39..bef3d32 100644 --- a/src/buffer.c +++ b/src/buffer.c @@ -499,6 +499,10 @@ close_buffer( /* When the buffer is no longer in a window, trigger BufWinLeave */ if (buf->b_nwindows == 1) { +# ifdef FEAT_WINDOWS + tabpage_T *save_curtab = curtab; +# endif + buf->b_closing = TRUE; if (apply_autocmds(EVENT_BUFWINLEAVE, buf->b_fname, buf->b_fname, FALSE, buf) @@ -533,6 +537,10 @@ aucmd_abort: if (aborting()) /* autocmds may abort script processing */ return; # endif +# ifdef FEAT_WINDOWS + if (save_curtab != curtab) + return; +# endif } nwindows = buf->b_nwindows; #endif @@ -561,7 +569,9 @@ aucmd_abort: buf->b_nwindows = nwindows; #endif - buf_freeall(buf, (del_buf ? BFA_DEL : 0) + (wipe_buf ? BFA_WIPE : 0)); + if (buf_freeall(buf, (del_buf ? BFA_DEL : 0) + (wipe_buf ? BFA_WIPE : 0)) + == FAIL) + return; #ifdef FEAT_AUTOCMD /* Autocommands may have deleted the buffer. */ @@ -671,8 +681,9 @@ buf_clear_file(buf_T *buf) * BFA_DEL buffer is going to be deleted * BFA_WIPE buffer is going to be wiped out * BFA_KEEP_UNDO do not free undo information + * Return FAIL for failure, OK otherwise. */ - void + int buf_freeall(buf_T *buf, int flags) { #ifdef FEAT_AUTOCMD @@ -693,7 +704,7 @@ buf_freeall(buf_T *buf, int flags) FALSE, buf) && !bufref_valid(&bufref)) /* autocommands deleted the buffer */ - return; + return FAIL; } if ((flags & BFA_DEL) && buf->b_p_bl) { @@ -701,7 +712,7 @@ buf_freeall(buf_T *buf, int flags) FALSE, buf) && !bufref_valid(&bufref)) /* autocommands deleted the buffer */ - return; + return FAIL; } if (flags & BFA_WIPE) { @@ -709,7 +720,7 @@ buf_freeall(buf_T *buf, int flags) FALSE, buf) && !bufref_valid(&bufref)) /* autocommands deleted the buffer */ - return; + return FAIL; } buf->b_closing = FALSE; @@ -717,7 +728,7 @@ buf_freeall(buf_T *buf, int flags) /* 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 * "tabnext" BufUnload autocmd leaves a window behind without a buffer. */ - if (is_curwin && curwin != the_curwin && win_valid_any_tab(the_curwin)) + if (is_curwin && curwin != the_curwin && win_valid_any_tab(the_curwin)) { block_autocmds(); goto_tabpage_win(the_curtab, the_curwin); @@ -727,7 +738,7 @@ buf_freeall(buf_T *buf, int flags) # ifdef FEAT_EVAL if (aborting()) /* autocmds may abort script processing */ - return; + return FAIL; # endif /* @@ -737,7 +748,7 @@ buf_freeall(buf_T *buf, int flags) * Therefore only return if curbuf changed to the deleted buffer. */ if (buf == curbuf && !is_curbuf) - return; + return FAIL; #endif #ifdef FEAT_DIFF diff_buf_delete(buf); /* Can't use 'diff' for unloaded buffer. */ @@ -779,6 +790,7 @@ buf_freeall(buf_T *buf, int flags) syntax_clear(&buf->b_s); /* reset syntax info */ #endif buf->b_flags &= ~BF_READERR; /* a read error is no longer relevant */ + return OK; } /* @@ -1960,7 +1972,8 @@ buflist_new( if (buf == curbuf) { /* free all things allocated for this buffer */ - buf_freeall(buf, 0); + if (buf_freeall(buf, 0) == FAIL) + return NULL; if (buf != curbuf) /* autocommands deleted the buffer! */ return NULL; #if defined(FEAT_AUTOCMD) && defined(FEAT_EVAL) diff --git a/src/ex_cmds.c b/src/ex_cmds.c index 61ab2ab..b7f749e 100644 --- a/src/ex_cmds.c +++ b/src/ex_cmds.c @@ -4101,7 +4101,13 @@ do_ecmd( goto theend; } u_unchanged(curbuf); - buf_freeall(curbuf, BFA_KEEP_UNDO); + if (buf_freeall(curbuf, BFA_KEEP_UNDO) == FAIL) + { +#ifdef FEAT_AUTOCMD + vim_free(new_name); +#endif + goto theend; + } /* tell readfile() not to clear or reload undo info */ readfile_flags = READ_KEEP_UNDO; @@ -4397,6 +4403,11 @@ delbuf_msg(char_u *name) vim_free(name); au_new_curbuf.br_buf = NULL; au_new_curbuf.br_buf_free_count = 0; + +# ifdef FEAT_WINDOWS + if (curwin->w_buffer == NULL) + enter_buffer(curbuf); +# endif } #endif diff --git a/src/proto/buffer.pro b/src/proto/buffer.pro index 183f79a..dbe152b 100644 --- a/src/proto/buffer.pro +++ b/src/proto/buffer.pro @@ -5,7 +5,7 @@ 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); void buf_clear_file(buf_T *buf); -void buf_freeall(buf_T *buf, int flags); +int buf_freeall(buf_T *buf, int flags); void goto_buffer(exarg_T *eap, int start, int dir, int count); void handle_swap_exists(bufref_T *old_curbuf); char_u *do_bufdel(int command, char_u *arg, int addr_count, int start_bnr, int end_bnr, int forceit); diff --git a/src/window.c b/src/window.c index 4f99271..1e74928 100644 --- a/src/window.c +++ b/src/window.c @@ -2305,7 +2305,12 @@ win_close(win_T *win, int free_buf) * and then close the window and the tab page to avoid that curwin and * curtab are invalid while we are freeing memory. */ if (close_last_window_tabpage(win, free_buf, prev_curtab)) - return FAIL; + { + /* Autocommands have close curwin's buf. Restore curwin->w_buffer */ + if (win_valid(curwin) && curwin->w_buffer == NULL) + curwin->w_buffer = curbuf; + return FAIL; + } /* When closing the help window, try restoring a snapshot after closing * the window. Otherwise clear the snapshot, it's now invalid. */
diff --git a/src/testdir/test_autocmd.vim b/src/testdir/test_autocmd.vim index f05a55f..23cd78f 100644 --- a/src/testdir/test_autocmd.vim +++ b/src/testdir/test_autocmd.vim @@ -77,11 +77,47 @@ 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() + edit a.txt + + augroup test_autocmd_bufunload + autocmd! + autocmd BufUnload <buffer> tabfirst | 2bwipeout! + augroup END + + edit b.txt + call assert_true(v:true) + + autocmd! test_autocmd_bufunload + augroup! test_autocmd_bufunload + bwipe! +endfunc + +" SEGV occurs in older versions. (At least 7.4.2321 or older) +function Test_autocmd_bufunload_avoiding_SEGV_02() + setlocal buftype=nowrite + + augroup test_autocmd_bufunload + autocmd! + autocmd BufUnload <buffer> tabfirst | 2bwipeout + augroup END + + normal! i1 + call assert_fails('edit a.txt', 'E517:') + call feedkeys("\<CR>") + + autocmd! test_autocmd_bufunload + augroup! test_autocmd_bufunload + bwipe! +endfunc + func Test_win_tab_autocmd() let g:record = [] @@ -196,3 +232,4 @@ func Test_augroup_deleted() au! VimEnter endfunc +" vim: shiftwidth=2 sts=2 expandtab