# HG changeset patch # User Christian Brabandt # Date 1489940105 -3600 # Node ID e08ead1d269f5edc00a3e4c6065fc0f2d78dad23 # Parent 011eb80f6e661ce54391b4e70ab5df6db9a30114 patch 8.0.0486: crash and endless loop when closing windows in autocmd commit https://github.com/vim/vim/commit/8c752bd6c4af54c0b7bac35a39acc2bf16015f85 Author: Bram Moolenaar Date: Sun Mar 19 17:09:56 2017 +0100 patch 8.0.0486: crash and endless loop when closing windows in autocmd Problem: Crash and endless loop when closing windows in a SessionLoadPost autocommand. Solution: Check for valid tabpage. (partly neovim #6308) diff --git a/src/fileio.c b/src/fileio.c --- a/src/fileio.c +++ b/src/fileio.c @@ -9033,6 +9033,11 @@ win_found: win_remove(curwin, NULL); aucmd_win_used = FALSE; last_status(FALSE); /* may need to remove last status line */ + + if (!valid_tabpage_win(curtab)) + /* no valid window in current tabpage */ + close_tabpage(curtab); + restore_snapshot(SNAP_AUCMD_IDX, FALSE); (void)win_comp_pos(); /* recompute window positions */ unblock_autocmds(); diff --git a/src/proto/window.pro b/src/proto/window.pro --- a/src/proto/window.pro +++ b/src/proto/window.pro @@ -26,6 +26,8 @@ int win_new_tabpage(int after); int may_open_tabpage(void); int make_tabpages(int maxcount); int valid_tabpage(tabpage_T *tpc); +int valid_tabpage_win(tabpage_T *tpc); +void close_tabpage(tabpage_T *tpc); tabpage_T *find_tabpage(int n); int tabpage_index(tabpage_T *ftp); void goto_tabpage(int n); 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 @@ -345,3 +345,66 @@ func Test_BufEnter() call delete('Xdir', 'd') au! BufEnter endfunc + +" Closing a window might cause an endless loop +" E814 for older Vims +function Test_autocmd_bufwipe_in_SessLoadPost() + tabnew + set noswapfile + let g:bufnr=bufnr('%') + mksession! + + let content=['set nocp noswapfile', + \ 'let v:swapchoice="e"', + \ 'augroup test_autocmd_sessionload', + \ 'autocmd!', + \ 'autocmd SessionLoadPost * 4bw!', + \ 'augroup END' + \ ] + call writefile(content, 'Xvimrc') + let a=system(v:progpath. ' -u Xvimrc --noplugins -S Session.vim') + call assert_match('E814', a) + + unlet! g:bufnr + set swapfile + for file in ['Session.vim', 'Xvimrc'] + call delete(file) + endfor +endfunc + +" SEGV occurs in older versions. +function Test_autocmd_bufwipe_in_SessLoadPost2() + tabnew + set noswapfile + let g:bufnr=bufnr('%') + mksession! + + let content = ['set nocp noswapfile', + \ 'function! DeleteInactiveBufs()', + \ ' tabfirst', + \ ' let tabblist = []', + \ ' for i in range(1, tabpagenr(''$''))', + \ ' call extend(tabblist, tabpagebuflist(i))', + \ ' endfor', + \ ' for b in range(1, bufnr(''$''))', + \ ' if bufexists(b) && buflisted(b) && (index(tabblist, b) == -1 || bufname(b) =~# ''^$'')', + \ ' exec ''bwipeout '' . b', + \ ' endif', + \ ' endfor', + \ 'call append("1", "SessionLoadPost DONE")', + \ 'endfunction', + \ 'au SessionLoadPost * call DeleteInactiveBufs()'] + call writefile(content, 'Xvimrc') + let a=system(v:progpath. ' -u Xvimrc --noplugins -S Session.vim') + " this probably only matches on unix + if has("unix") + call assert_notmatch('Caught deadly signal SEGV', a) + endif + call assert_match('SessionLoadPost DONE', a) + + unlet! g:bufnr + set swapfile + for file in ['Session.vim', 'Xvimrc'] + call delete(file) + endfor +endfunc diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -765,6 +765,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 486, +/**/ 485, /**/ 484, diff --git a/src/window.c b/src/window.c --- a/src/window.c +++ b/src/window.c @@ -2107,7 +2107,7 @@ win_equal_rec( } /* - * close all windows for buffer 'buf' + * Close all windows for buffer "buf". */ void close_windows( @@ -2131,7 +2131,10 @@ close_windows( #endif ) { - win_close(wp, FALSE); + if (win_close(wp, FALSE) == FAIL) + /* If closing the window fails give up, to avoid looping + * forever. */ + break; /* Start all over, autocommands may change the window layout. */ wp = firstwin; @@ -3759,6 +3762,58 @@ valid_tabpage(tabpage_T *tpc) } /* + * Return TRUE when "tpc" points to a valid tab page and at least one window is + * valid. + */ + int +valid_tabpage_win(tabpage_T *tpc) +{ + tabpage_T *tp; + win_T *wp; + + FOR_ALL_TABPAGES(tp) + { + if (tp == tpc) + { + FOR_ALL_WINDOWS_IN_TAB(tp, wp) + { + if (win_valid_any_tab(wp)) + return TRUE; + } + return FALSE; + } + } + /* shouldn't happen */ + return FALSE; +} + +/* + * Close tabpage "tab", assuming it has no windows in it. + * There must be another tabpage or this will crash. + */ + void +close_tabpage(tabpage_T *tab) +{ + tabpage_T *ptp; + + if (tab == first_tabpage) + { + first_tabpage = tab->tp_next; + ptp = first_tabpage; + } + else + { + for (ptp = first_tabpage; ptp != NULL && ptp->tp_next != tab; + ptp = ptp->tp_next) + ; + ptp->tp_next = tab->tp_next; + } + + goto_tabpage_tp(ptp, FALSE, FALSE); + free_tabpage(tab); +} + +/* * Find tab page "n" (first one is 1). Returns NULL when not found. */ tabpage_T *