fix(events): crash on WinScrolled #35995

Problem: apply_autocmds function can free both buf_T and win_T pointers

Solution: instead retain winids for WinResized and WinScrolled
autocmds and use curbuf pointer, which is consistent with other uses
of apply_autocmds function

(cherry picked from commit 0a0c349b6f)
This commit is contained in:
Kent Sibilev
2025-11-25 21:27:52 -05:00
committed by github-actions[bot]
parent 5143419e22
commit 656ff4c438
2 changed files with 69 additions and 7 deletions

View File

@@ -5719,6 +5719,21 @@ void may_trigger_win_scrolled_resized(void)
recursive = true;
// Save window info before autocmds since they can free windows
char resize_winid[NUMBUFLEN];
bufref_T resize_bufref;
if (trigger_resize) {
vim_snprintf(resize_winid, sizeof(resize_winid), "%d", first_size_win->handle);
set_bufref(&resize_bufref, first_size_win->w_buffer);
}
char scroll_winid[NUMBUFLEN];
bufref_T scroll_bufref;
if (trigger_scroll) {
vim_snprintf(scroll_winid, sizeof(scroll_winid), "%d", first_scroll_win->handle);
set_bufref(&scroll_bufref, first_scroll_win->w_buffer);
}
// If both are to be triggered do WinResized first.
if (trigger_resize) {
save_v_event_T save_v_event;
@@ -5726,10 +5741,8 @@ void may_trigger_win_scrolled_resized(void)
if (tv_dict_add_list(v_event, S_LEN("windows"), windows_list) == OK) {
tv_dict_set_keys_readonly(v_event);
char winid[NUMBUFLEN];
vim_snprintf(winid, sizeof(winid), "%d", first_size_win->handle);
apply_autocmds(EVENT_WINRESIZED, winid, winid, false, first_size_win->w_buffer);
buf_T *buf = bufref_valid(&resize_bufref) ? resize_bufref.br_buf : curbuf;
apply_autocmds(EVENT_WINRESIZED, resize_winid, resize_winid, false, buf);
}
restore_v_event(v_event, &save_v_event);
}
@@ -5743,9 +5756,8 @@ void may_trigger_win_scrolled_resized(void)
tv_dict_set_keys_readonly(v_event);
tv_dict_unref(scroll_dict);
char winid[NUMBUFLEN];
vim_snprintf(winid, sizeof(winid), "%d", first_scroll_win->handle);
apply_autocmds(EVENT_WINSCROLLED, winid, winid, false, first_scroll_win->w_buffer);
buf_T *buf = bufref_valid(&scroll_bufref) ? scroll_bufref.br_buf : curbuf;
apply_autocmds(EVENT_WINSCROLLED, scroll_winid, scroll_winid, false, buf);
restore_v_event(v_event, &save_v_event);
}

View File

@@ -349,4 +349,54 @@ describe('WinScrolled', function()
[winid_str] = { leftcol = 0, topline = -3, topfill = 0, width = 0, height = 0, skipcol = 0 },
}, eval('g:v_event'))
end)
it('does not crash when WinResized closes popup before WinScrolled #35803', function()
exec([[
set scrolloff=0
call setline(1, range(1, 100))
" Create first popup window (will be resized and closed)
let buf1 = nvim_create_buf(v:false, v:true)
call nvim_buf_set_lines(buf1, 0, -1, v:false, map(range(1, 50), 'string(v:val)'))
let popup1 = nvim_open_win(buf1, v:false, {
\ 'relative': 'editor',
\ 'width': 20,
\ 'height': 5,
\ 'col': 10,
\ 'row': 5
\ })
" Create second popup window (will be scrolled)
let buf2 = nvim_create_buf(v:false, v:true)
call nvim_buf_set_lines(buf2, 0, -1, v:false, map(range(1, 50), 'string(v:val)'))
let popup2 = nvim_open_win(buf2, v:false, {
\ 'relative': 'editor',
\ 'width': 20,
\ 'height': 5,
\ 'col': 35,
\ 'row': 5
\ })
let g:resized = 0
let g:scrolled = 0
" WinResized autocmd resizes and closes the first popup
autocmd WinResized * let g:resized += 1 | call nvim_win_set_height(popup1, 10) | call nvim_win_close(popup1, v:true)
" WinScrolled autocmd scrolls the second popup
autocmd WinScrolled * let g:scrolled += 1 | call nvim_win_call(popup2, {-> execute('normal! \<C-E>')})
]])
eq(0, eval('g:resized'))
eq(0, eval('g:scrolled'))
-- Trigger a resize on popup1, which will close it
-- This should trigger WinResized (which closes popup1) and WinScrolled (which scrolls popup2)
-- Before the fix, WinScrolled would use the freed pointer causing a crash
api.nvim_win_set_height(eval('popup1'), 8)
-- The key is that it should not crash when WinResized closes a window
-- that WinScrolled might have referenced via a stale buf_T pointer
assert_alive()
-- Verify autocmds were actually triggered
eq(1, eval('g:resized > 0'))
end)
end)