neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

commit 786c5fbdec79eed83b25856806d3613a69199dcf
parent 4544cec168e0197ba694177e163a36b49d013c37
Author: zeertzjq <zeertzjq@outlook.com>
Date:   Fri, 16 Jan 2026 12:31:28 +0800

vim-patch:9.1.0671: Problem:  crash with WinNewPre autocommand

Problem:  crash with WinNewPre autocommand, because window
          structures are not yet safe to use
Solution: Don't trigger WinNewPre on :tabnew

https://github.com/vim/vim/commit/fb3f9699362f8d51c3b48fcaea1eb2ed16c81454

Cherry-pick doc updates from latest Vim runtime.

Co-authored-by: Christian Brabandt <cb@256bit.org>

Diffstat:
Mruntime/doc/autocmd.txt | 8+++++---
Msrc/nvim/window.c | 6++++--
Atest/old/testdir/crash/nullpointer | 0
Mtest/old/testdir/test_autocmd.vim | 56+++++++++++++++++++++++++++++++++++++++++---------------
Mtest/old/testdir/test_crash.vim | 6++++++
5 files changed, 56 insertions(+), 20 deletions(-)

diff --git a/runtime/doc/autocmd.txt b/runtime/doc/autocmd.txt @@ -1233,10 +1233,12 @@ WinLeave Before leaving a window. If the window to be Not used for ":qa" or ":q" when exiting Vim. Before WinClosed. *WinNewPre* -WinNewPre Before creating a new window. Triggered +WinNewPre Before creating a new window. Triggered before commands that modify window layout by - creating a split or new tab page. Not done for - the first window, when Vim has just started. + creating a split. + Not done when creating tab pages and for the + first window, as the window structure is not + initialized yet and so is generally not safe. It is not allowed to modify window layout while executing commands for the WinNewPre event. diff --git a/src/nvim/window.c b/src/nvim/window.c @@ -4362,6 +4362,10 @@ void free_tabpage(tabpage_T *tp) /// /// It will edit the current buffer, like after :split. /// +/// Does not trigger WinNewPre, since the window structures +/// are not completely setup yet and could cause dereferencing +/// NULL pointers +/// /// @param after Put new tabpage after tabpage "after", or after the current /// tabpage in case of 0. /// @param filename Will be passed to apply_autocmds(). @@ -4391,8 +4395,6 @@ int win_new_tabpage(int after, char *filename) curtab = newtp; - trigger_winnewpre(); - // Create a new empty window. if (win_alloc_firstwin(old_curtab->tp_curwin) == OK) { // Make the new Tab page the new topframe. diff --git a/test/old/testdir/crash/nullpointer b/test/old/testdir/crash/nullpointer Binary files differ. diff --git a/test/old/testdir/test_autocmd.vim b/test/old/testdir/test_autocmd.vim @@ -276,6 +276,7 @@ endfunc func Test_win_tab_autocmd() let g:record = [] + defer CleanUpTestAuGroup() augroup testing au WinNewPre * call add(g:record, 'WinNewPre') au WinNew * call add(g:record, 'WinNew') @@ -295,7 +296,7 @@ func Test_win_tab_autocmd() call assert_equal([ \ 'WinNewPre', 'WinLeave', 'WinNew', 'WinEnter', - \ 'WinLeave', 'TabLeave', 'WinNewPre', 'WinNew', 'WinEnter', 'TabNew', 'TabEnter', + \ 'WinLeave', 'TabLeave', 'WinNew', 'WinEnter', 'TabNew', 'TabEnter', \ 'WinLeave', 'TabLeave', 'WinClosed', 'TabClosed', 'WinEnter', 'TabEnter', \ 'WinLeave', 'WinClosed', 'WinEnter' \ ], g:record) @@ -306,7 +307,7 @@ func Test_win_tab_autocmd() bwipe somefile call assert_equal([ - \ 'WinLeave', 'TabLeave', 'WinNewPre', 'WinNew', 'WinEnter', 'TabNew', 'TabEnter', + \ 'WinLeave', 'TabLeave', 'WinNew', 'WinEnter', 'TabNew', 'TabEnter', \ 'WinLeave', 'TabLeave', 'WinEnter', 'TabEnter', \ 'WinClosed', 'TabClosed' \ ], g:record) @@ -323,9 +324,6 @@ func Test_win_tab_autocmd() \ 'WinNewPre', 'WinLeave', 'WinNew', 'WinEnter' \ ], g:record) - augroup testing - au! - augroup END unlet g:record endfunc @@ -337,17 +335,15 @@ func Test_WinNewPre() au WinNewPre * call add(g:layouts_pre, winlayout()) au WinNew * call add(g:layouts_post, winlayout()) augroup END + defer CleanUpTestAuGroup() split call assert_notequal(g:layouts_pre[0], g:layouts_post[0]) split call assert_equal(g:layouts_pre[1], g:layouts_post[0]) call assert_notequal(g:layouts_pre[1], g:layouts_post[1]) + " not triggered for tabnew tabnew - call assert_notequal(g:layouts_pre[2], g:layouts_post[1]) - call assert_notequal(g:layouts_pre[2], g:layouts_post[2]) - augroup testing - au! - augroup END + call assert_equal(2, len(g:layouts_pre)) unlet g:layouts_pre unlet g:layouts_post @@ -390,9 +386,6 @@ func Test_WinNewPre() let g:caught += 1 endtry call assert_equal(4, g:caught) - augroup testing - au! - augroup END unlet g:caught endfunc @@ -2900,7 +2893,8 @@ endfunc func Test_autocmd_nested() let g:did_nested = 0 - augroup Testing + defer CleanUpTestAuGroup() + augroup testing au WinNew * edit somefile au BufNew * let g:did_nested = 1 augroup END @@ -2910,7 +2904,7 @@ func Test_autocmd_nested() bwipe! somefile " old nested argument still works - augroup Testing + augroup testing au! au WinNew * nested edit somefile au BufNew * let g:did_nested = 1 @@ -4458,6 +4452,38 @@ func Test_BufEnter_botline() set hidden&vim endfunc +" those commands caused null pointer access, see #15464 +func Test_WinNewPre_crash() + defer CleanUpTestAuGroup() + let _cmdheight=&cmdheight + augroup testing + au! + autocmd WinNewPre * redraw + augroup END + tabnew + tabclose + augroup testing + au! + autocmd WinNewPre * wincmd t + augroup END + tabnew + tabclose + augroup testing + au! + autocmd WinNewPre * wincmd b + augroup END + tabnew + tabclose + augroup testing + au! + autocmd WinNewPre * set cmdheight+=1 + augroup END + tabnew + tabclose + let &cmdheight=_cmdheight +endfunc + + " This was using freed memory func Test_autocmd_BufWinLeave_with_vsp() new diff --git a/test/old/testdir/test_crash.vim b/test/old/testdir/test_crash.vim @@ -220,6 +220,12 @@ func Test_crash1_3() call term_sendkeys(buf, args) call TermWait(buf, 150) + let file = 'crash/nullpointer' + let cmn_args = "%s -u NONE -i NONE -n -e -s -S %s -c ':qa!'\<cr>" + let args = printf(cmn_args, vim, file) + call term_sendkeys(buf, args) + call TermWait(buf, 50) + let file = 'crash/heap_overflow3' let cmn_args = "%s -u NONE -i NONE -n -X -m -n -e -s -S %s -c ':qa!'" let args = printf(cmn_args, vim, file)