Skip to content

Commit a8fdfd4

Browse files
seandewarchrisbra
authored andcommitted
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 <6256228+seandewar@users.noreply.github.com> Signed-off-by: Christian Brabandt <cb@256bit.org>
1 parent 68b3585 commit a8fdfd4

3 files changed

Lines changed: 68 additions & 10 deletions

File tree

src/buffer.c

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -739,15 +739,6 @@ close_buffer(
739739
if (buf->b_ffname == NULL)
740740
del_buf = TRUE;
741741

742-
// When closing the current buffer stop Visual mode before freeing
743-
// anything.
744-
if (buf == curbuf && VIsual_active
745-
#if defined(EXITFREE)
746-
&& !entered_free_all_mem
747-
#endif
748-
)
749-
end_visual_mode();
750-
751742
// Free all things allocated for this buffer.
752743
// Also calls the "BufDelete" autocommands when del_buf is TRUE.
753744
//
@@ -944,6 +935,16 @@ buf_freeall(buf_T *buf, int flags)
944935
// Therefore only return if curbuf changed to the deleted buffer.
945936
if (buf == curbuf && !is_curbuf)
946937
return;
938+
939+
// If curbuf, stop Visual mode just before freeing, but after autocmds that
940+
// may restart it. May trigger TextYankPost, but with textlock set.
941+
if (buf == curbuf && VIsual_active
942+
#if defined(EXITFREE)
943+
&& !entered_free_all_mem
944+
#endif
945+
)
946+
end_visual_mode();
947+
947948
#ifdef FEAT_DIFF
948949
diff_buf_delete(buf); // Can't use 'diff' for unloaded buffer.
949950
#endif
@@ -1976,7 +1977,9 @@ set_curbuf(buf_T *buf, int action)
19761977
static void
19771978
enter_buffer(buf_T *buf)
19781979
{
1979-
// when closing the current buffer stop Visual mode
1980+
// Stop Visual mode before changing curbuf. May trigger TextYankPost, but
1981+
// with textlock set. Assumes curbuf and curwin->w_buffer is valid; if not,
1982+
// buf_freeall() should've done this already!
19801983
if (VIsual_active
19811984
#if defined(EXITFREE)
19821985
&& !entered_free_all_mem

src/testdir/test_visual.vim

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2970,4 +2970,57 @@ func Test_getregionpos_block_linebreak_matches_getpos()
29702970
let &columns = save_columns
29712971
bw!
29722972
endfunc
2973+
2974+
func Test_visual_ended_in_wiped_buffer()
2975+
edit Xfoo
2976+
edit Xbar
2977+
setlocal bufhidden=wipe
2978+
augroup testing
2979+
autocmd BufWipeout * ++once normal! v
2980+
augroup END
2981+
" Must be the last window.
2982+
call assert_equal(1, winnr('$'))
2983+
call assert_equal(1, tabpagenr('$'))
2984+
" Was a member access on a NULL curbuf from Vim ending Visual mode.
2985+
buffer #
2986+
call assert_equal(0, bufexists('Xbar'))
2987+
call assert_equal('n', mode())
2988+
2989+
autocmd! testing
2990+
%bw!
2991+
endfunc
2992+
2993+
func Test_visual_ended_in_unloaded_buffer()
2994+
CheckFeature clipboard
2995+
CheckNotGui
2996+
set clipboard+=autoselect
2997+
edit Xfoo
2998+
edit Xbar
2999+
call setline(1, 'hi')
3000+
setlocal nomodified
3001+
let s:fired = 0
3002+
augroup testing
3003+
autocmd BufUnload Xbar call assert_equal('Xbar', bufname())
3004+
\| execute 'normal! V'
3005+
\| call assert_equal('V', mode())
3006+
3007+
" From Vim ending Visual mode. Used to occur too late, after the buffer was
3008+
" unloaded, so @* didn't contain the selection. Window also had a NULL
3009+
" w_buffer here!
3010+
autocmd TextYankPost * ++once let s:fired = 1
3011+
\| if has('clipboard_working') | call assert_equal("hi\n", @*) | endif
3012+
\| call tabpagebuflist() " was a NULL member access on w_buffer
3013+
augroup END
3014+
3015+
buffer Xfoo
3016+
call assert_equal(0, bufloaded('Xbar'))
3017+
call assert_equal('n', mode())
3018+
call assert_equal(1, s:fired)
3019+
3020+
autocmd! testing
3021+
unlet! s:fired
3022+
set clipboard&
3023+
%bw!
3024+
endfunc
3025+
29733026
" vim: shiftwidth=2 sts=2 expandtab

src/version.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -734,6 +734,8 @@ static char *(features[]) =
734734

735735
static int included_patches[] =
736736
{ /* Add new patch number below this line */
737+
/**/
738+
252,
737739
/**/
738740
251,
739741
/**/

0 commit comments

Comments
 (0)