patch 9.2.0252: Crash when ending Visual mode after curbuf was unloaded

Commit: 
https://github.com/vim/vim/commit/a8fdfd4fcb92b9fcbed3ad0a6769cb6bad34d965
Author: Sean Dewar <[email protected]>
Date:   Thu Mar 26 20:05:31 2026 +0000

    patch 9.2.0252: Crash when ending Visual mode after curbuf was unloaded
    
    Problem:  if close_buffer() in set_curbuf() unloads curbuf, NULL pointer
              accesses may occur from enter_buffer() calling
              end_visual_mode(), as curbuf is already abandoned and possibly
              unloaded.  Also, selection registers may not contain the
              selection with clipboard+=autoselect(plus).
    Solution: Move close_buffer()'s end_visual_mode() call to buf_freeall(), 
after
              any autocmds that may restart it, but just before freeing anything
              (Sean Dewar)
    
    related: #19728
    
    Signed-off-by: Sean Dewar <[email protected]>
    Signed-off-by: Christian Brabandt <[email protected]>

diff --git a/src/buffer.c b/src/buffer.c
index 26e3ec969..dd7285da0 100644
--- a/src/buffer.c
+++ b/src/buffer.c
@@ -739,15 +739,6 @@ aucmd_abort:
     if (buf->b_ffname == NULL)
        del_buf = TRUE;
 
-    // When closing the current buffer stop Visual mode before freeing
-    // anything.
-    if (buf == curbuf && VIsual_active
-#if defined(EXITFREE)
-           && !entered_free_all_mem
-#endif
-           )
-       end_visual_mode();
-
     // Free all things allocated for this buffer.
     // Also calls the "BufDelete" autocommands when del_buf is TRUE.
     //
@@ -944,6 +935,16 @@ buf_freeall(buf_T *buf, int flags)
     // Therefore only return if curbuf changed to the deleted buffer.
     if (buf == curbuf && !is_curbuf)
        return;
+
+    // If curbuf, stop Visual mode just before freeing, but after autocmds that
+    // may restart it.  May trigger TextYankPost, but with textlock set.
+    if (buf == curbuf && VIsual_active
+#if defined(EXITFREE)
+           && !entered_free_all_mem
+#endif
+           )
+       end_visual_mode();
+
 #ifdef FEAT_DIFF
     diff_buf_delete(buf);          // Can't use 'diff' for unloaded buffer.
 #endif
@@ -1976,7 +1977,9 @@ set_curbuf(buf_T *buf, int action)
     static void
 enter_buffer(buf_T *buf)
 {
-    // when closing the current buffer stop Visual mode
+    // Stop Visual mode before changing curbuf.  May trigger TextYankPost, but
+    // with textlock set.  Assumes curbuf and curwin->w_buffer is valid; if 
not,
+    // buf_freeall() should've done this already!
     if (VIsual_active
 #if defined(EXITFREE)
            && !entered_free_all_mem
diff --git a/src/testdir/test_visual.vim b/src/testdir/test_visual.vim
index 3957d0a2d..a2bb12588 100644
--- a/src/testdir/test_visual.vim
+++ b/src/testdir/test_visual.vim
@@ -2970,4 +2970,57 @@ func Test_getregionpos_block_linebreak_matches_getpos()
   let &columns = save_columns
   bw!
 endfunc
+
+func Test_visual_ended_in_wiped_buffer()
+  edit Xfoo
+  edit Xbar
+  setlocal bufhidden=wipe
+  augroup testing
+    autocmd BufWipeout * ++once normal! v
+  augroup END
+  " Must be the last window.
+  call assert_equal(1, winnr('$'))
+  call assert_equal(1, tabpagenr('$'))
+  " Was a member access on a NULL curbuf from Vim ending Visual mode.
+  buffer #
+  call assert_equal(0, bufexists('Xbar'))
+  call assert_equal('n', mode())
+
+  autocmd! testing
+  %bw!
+endfunc
+
+func Test_visual_ended_in_unloaded_buffer()
+  CheckFeature clipboard
+  CheckNotGui
+  set clipboard+=autoselect
+  edit Xfoo
+  edit Xbar
+  call setline(1, 'hi')
+  setlocal nomodified
+  let s:fired = 0
+  augroup testing
+    autocmd BufUnload Xbar call assert_equal('Xbar', bufname())
+          \| execute 'normal! V'
+          \| call assert_equal('V', mode())
+
+    " From Vim ending Visual mode.  Used to occur too late, after the buffer 
was
+    " unloaded, so @* didn't contain the selection.  Window also had a NULL
+    " w_buffer here!
+    autocmd TextYankPost * ++once let s:fired = 1
+          \| if has('clipboard_working') | call assert_equal("hi
", @*) | endif
+          \| call tabpagebuflist() " was a NULL member access on w_buffer
+  augroup END
+
+  buffer Xfoo
+  call assert_equal(0, bufloaded('Xbar'))
+  call assert_equal('n', mode())
+  call assert_equal(1, s:fired)
+
+  autocmd! testing
+  unlet! s:fired
+  set clipboard&
+  %bw!
+endfunc
+
 " vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/version.c b/src/version.c
index 862b5b12a..da34b93b8 100644
--- a/src/version.c
+++ b/src/version.c
@@ -734,6 +734,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    252,
 /**/
     251,
 /**/

-- 
-- 
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/E1w5rLA-007PDn-GU%40256bit.org.

Raspunde prin e-mail lui