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:
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)