patch 9.1.1773: Crash in BufLeave after BufUnload closes other windows

Commit: 
https://github.com/vim/vim/commit/0c70820015c7a37425c07bf30ad277ee2656d496
Author: zeertzjq <[email protected]>
Date:   Thu Sep 18 20:05:19 2025 +0000

    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 <[email protected]>
    Signed-off-by: Christian Brabandt <[email protected]>

diff --git a/src/testdir/test_autocmd.vim b/src/testdir/test_autocmd.vim
index cbcdd4036..6957c2a9b 100644
--- a/src/testdir/test_autocmd.vim
+++ b/src/testdir/test_autocmd.vim
@@ -818,27 +818,49 @@ func Test_WinClosed_switch_tab()
   %bwipe!
 endfunc
 
-" This used to trigger WinClosed twice for the same window, and the window's
-" buffer was NULL in the second autocommand.
-func Test_WinClosed_BufUnload_close_other()
-  tabnew
+" This used to trigger WinClosed/WinLeave/BufLeave twice for the same window,
+" and the window's buffer was NULL in the second autocommand.
+func Run_test_BufUnload_close_other(extra_cmd)
+  let oldtab = tabpagenr()
+  tabnew Xb1
   let g:tab = tabpagenr()
-  let g:buf = bufnr()
-  new
-  setlocal bufhidden=wipe
-  augroup test-WinClosed
-    autocmd BufUnload * ++once exe g:buf .. 'bwipe!'
-    autocmd WinClosed * call tabpagebuflist(g:tab)
+  let g:w1 = win_getid()
+  new Xb2
+  let g:w2 = win_getid()
+  let g:log = []
+  exe a:extra_cmd
+
+  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
+
   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:buf
-  autocmd! test-WinClosed
-  augroup! test-WinClosed
+  unlet g:w1
+  unlet g:w2
+  unlet g:log
+  autocmd! test-BufUnload-close-other
+  augroup! test-BufUnload-close-other
   %bwipe!
 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()
   augroup vimBarTest
     au BufReadCmd * echo 'hello'
diff --git a/src/version.c b/src/version.c
index 4864220d3..2e0c77177 100644
--- a/src/version.c
+++ b/src/version.c
@@ -724,6 +724,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    1773,
 /**/
     1772,
 /**/
diff --git a/src/window.c b/src/window.c
index 723195a0f..f071a0e7d 100644
--- a/src/window.c
+++ b/src/window.c
@@ -2605,10 +2605,12 @@ close_last_window_tabpage(
      * 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
      * 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.
+     * 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
     // to the other tab page.
@@ -2906,6 +2908,7 @@ win_close(win_T *win, int free_buf)
        win_comp_pos();
        win_fix_scroll(FALSE);
     }
+
     if (close_curwin)
     {
        // 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);
     }
 
+    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;
 #ifdef MESSAGE_QUEUE
     if (!did_decrement)

-- 
-- 
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 [email protected].
To view this discussion visit 
https://groups.google.com/d/msgid/vim_dev/E1uzL1z-00EoB7-FT%40256bit.org.

Raspunde prin e-mail lui