patch 9.1.1773: Crash in BufLeave after BufUnload closes other windows
Problem: Crash in BufLeave/WinLeave/TabLeave when closing window after
BufUnload closes all other windows in the tab page.
Solution: Avoid duplicate BufLeave/WinLeave events. Trigger TabLeave
before removing the buffer (zeertzjq).
related: #14166
related: neovim/neovim#33603
closes: #18330
Signed-off-by: zeertzjq <zeertzjq@outlook.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
This commit is contained in:
committed by
Christian Brabandt
parent
86e8e909f2
commit
0c70820015
@ -818,27 +818,49 @@ func Test_WinClosed_switch_tab()
|
|||||||
%bwipe!
|
%bwipe!
|
||||||
endfunc
|
endfunc
|
||||||
|
|
||||||
" This used to trigger WinClosed twice for the same window, and the window's
|
" This used to trigger WinClosed/WinLeave/BufLeave twice for the same window,
|
||||||
" buffer was NULL in the second autocommand.
|
" and the window's buffer was NULL in the second autocommand.
|
||||||
func Test_WinClosed_BufUnload_close_other()
|
func Run_test_BufUnload_close_other(extra_cmd)
|
||||||
tabnew
|
let oldtab = tabpagenr()
|
||||||
|
tabnew Xb1
|
||||||
let g:tab = tabpagenr()
|
let g:tab = tabpagenr()
|
||||||
let g:buf = bufnr()
|
let g:w1 = win_getid()
|
||||||
new
|
new Xb2
|
||||||
setlocal bufhidden=wipe
|
let g:w2 = win_getid()
|
||||||
augroup test-WinClosed
|
let g:log = []
|
||||||
autocmd BufUnload * ++once exe g:buf .. 'bwipe!'
|
exe a:extra_cmd
|
||||||
autocmd WinClosed * call tabpagebuflist(g:tab)
|
|
||||||
|
augroup test-BufUnload-close-other
|
||||||
|
autocmd BufUnload * ++nested ++once bwipe! Xb1
|
||||||
|
for event in ['WinClosed', 'BufLeave', 'WinLeave', 'TabLeave']
|
||||||
|
exe $'autocmd {event} * call tabpagebuflist(g:tab)'
|
||||||
|
exe $'autocmd {event} * let g:log += ["{event}:" .. expand("<afile>")]'
|
||||||
|
endfor
|
||||||
augroup END
|
augroup END
|
||||||
|
|
||||||
close
|
close
|
||||||
|
" WinClosed is triggered once for each of the 2 closed windows.
|
||||||
|
" Others are only triggered once.
|
||||||
|
call assert_equal(['BufLeave:Xb2', 'WinLeave:Xb2', $'WinClosed:{g:w2}',
|
||||||
|
\ $'WinClosed:{g:w1}', 'TabLeave:Xb2'], g:log)
|
||||||
|
call assert_equal(oldtab, tabpagenr())
|
||||||
|
call assert_equal([0, 0], win_id2tabwin(g:w1))
|
||||||
|
call assert_equal([0, 0], win_id2tabwin(g:w2))
|
||||||
|
|
||||||
unlet g:tab
|
unlet g:tab
|
||||||
unlet g:buf
|
unlet g:w1
|
||||||
autocmd! test-WinClosed
|
unlet g:w2
|
||||||
augroup! test-WinClosed
|
unlet g:log
|
||||||
|
autocmd! test-BufUnload-close-other
|
||||||
|
augroup! test-BufUnload-close-other
|
||||||
%bwipe!
|
%bwipe!
|
||||||
endfunc
|
endfunc
|
||||||
|
|
||||||
|
func Test_BufUnload_close_other()
|
||||||
|
call Run_test_BufUnload_close_other('')
|
||||||
|
call Run_test_BufUnload_close_other('setlocal bufhidden=wipe')
|
||||||
|
endfunc
|
||||||
|
|
||||||
func s:AddAnAutocmd()
|
func s:AddAnAutocmd()
|
||||||
augroup vimBarTest
|
augroup vimBarTest
|
||||||
au BufReadCmd * echo 'hello'
|
au BufReadCmd * echo 'hello'
|
||||||
|
|||||||
@ -724,6 +724,8 @@ static char *(features[]) =
|
|||||||
|
|
||||||
static int included_patches[] =
|
static int included_patches[] =
|
||||||
{ /* Add new patch number below this line */
|
{ /* Add new patch number below this line */
|
||||||
|
/**/
|
||||||
|
1773,
|
||||||
/**/
|
/**/
|
||||||
1772,
|
1772,
|
||||||
/**/
|
/**/
|
||||||
|
|||||||
14
src/window.c
14
src/window.c
@ -2605,10 +2605,12 @@ close_last_window_tabpage(
|
|||||||
* page and then close the window and the tab page. This avoids that
|
* page and then close the window and the tab page. This avoids that
|
||||||
* curwin and curtab are invalid while we are freeing memory, they may
|
* curwin and curtab are invalid while we are freeing memory, they may
|
||||||
* be used in GUI events.
|
* be used in GUI events.
|
||||||
* Don't trigger autocommands yet, they may use wrong values, so do
|
* Don't trigger *Enter autocommands yet, they may use wrong values, so do
|
||||||
* that below.
|
* that below.
|
||||||
|
* Do trigger *Leave autocommands, unless win->w_buffer is NULL, in which
|
||||||
|
* case they have already been triggered.
|
||||||
*/
|
*/
|
||||||
goto_tabpage_tp(alt_tabpage(), FALSE, TRUE);
|
goto_tabpage_tp(alt_tabpage(), FALSE, win->w_buffer != NULL);
|
||||||
|
|
||||||
// Safety check: Autocommands may have closed the window when jumping
|
// Safety check: Autocommands may have closed the window when jumping
|
||||||
// to the other tab page.
|
// to the other tab page.
|
||||||
@ -2906,6 +2908,7 @@ win_close(win_T *win, int free_buf)
|
|||||||
win_comp_pos();
|
win_comp_pos();
|
||||||
win_fix_scroll(FALSE);
|
win_fix_scroll(FALSE);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (close_curwin)
|
if (close_curwin)
|
||||||
{
|
{
|
||||||
// Pass WEE_ALLOW_PARSE_MESSAGES to decrement dont_parse_messages
|
// Pass WEE_ALLOW_PARSE_MESSAGES to decrement dont_parse_messages
|
||||||
@ -2923,6 +2926,13 @@ win_close(win_T *win, int free_buf)
|
|||||||
apply_autocmds(EVENT_BUFENTER, NULL, NULL, FALSE, curbuf);
|
apply_autocmds(EVENT_BUFENTER, NULL, NULL, FALSE, curbuf);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ONE_WINDOW && curwin->w_locked && curbuf->b_locked_split
|
||||||
|
&& first_tabpage->tp_next != NULL)
|
||||||
|
// The new curwin is the last window in the current tab page, and it is
|
||||||
|
// already being closed. Trigger TabLeave now, as after its buffer is
|
||||||
|
// removed it's no longer safe to do that.
|
||||||
|
apply_autocmds(EVENT_TABLEAVE, NULL, NULL, FALSE, curbuf);
|
||||||
|
|
||||||
--split_disallowed;
|
--split_disallowed;
|
||||||
#ifdef MESSAGE_QUEUE
|
#ifdef MESSAGE_QUEUE
|
||||||
if (!did_decrement)
|
if (!did_decrement)
|
||||||
|
|||||||
Reference in New Issue
Block a user