mirror of
https://github.com/neovim/neovim.git
synced 2026-08-27 17:41:48 +00:00
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
This commit is contained in:
@@ -5788,6 +5788,21 @@ void may_trigger_win_scrolled_resized(void)
|
|||||||
|
|
||||||
recursive = true;
|
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 both are to be triggered do WinResized first.
|
||||||
if (trigger_resize) {
|
if (trigger_resize) {
|
||||||
save_v_event_T save_v_event;
|
save_v_event_T save_v_event;
|
||||||
@@ -5795,10 +5810,8 @@ void may_trigger_win_scrolled_resized(void)
|
|||||||
|
|
||||||
if (tv_dict_add_list(v_event, S_LEN("windows"), windows_list) == OK) {
|
if (tv_dict_add_list(v_event, S_LEN("windows"), windows_list) == OK) {
|
||||||
tv_dict_set_keys_readonly(v_event);
|
tv_dict_set_keys_readonly(v_event);
|
||||||
|
buf_T *buf = bufref_valid(&resize_bufref) ? resize_bufref.br_buf : curbuf;
|
||||||
char winid[NUMBUFLEN];
|
apply_autocmds(EVENT_WINRESIZED, resize_winid, resize_winid, false, buf);
|
||||||
vim_snprintf(winid, sizeof(winid), "%d", first_size_win->handle);
|
|
||||||
apply_autocmds(EVENT_WINRESIZED, winid, winid, false, first_size_win->w_buffer);
|
|
||||||
}
|
}
|
||||||
restore_v_event(v_event, &save_v_event);
|
restore_v_event(v_event, &save_v_event);
|
||||||
}
|
}
|
||||||
@@ -5812,9 +5825,8 @@ void may_trigger_win_scrolled_resized(void)
|
|||||||
tv_dict_set_keys_readonly(v_event);
|
tv_dict_set_keys_readonly(v_event);
|
||||||
tv_dict_unref(scroll_dict);
|
tv_dict_unref(scroll_dict);
|
||||||
|
|
||||||
char winid[NUMBUFLEN];
|
buf_T *buf = bufref_valid(&scroll_bufref) ? scroll_bufref.br_buf : curbuf;
|
||||||
vim_snprintf(winid, sizeof(winid), "%d", first_scroll_win->handle);
|
apply_autocmds(EVENT_WINSCROLLED, scroll_winid, scroll_winid, false, buf);
|
||||||
apply_autocmds(EVENT_WINSCROLLED, winid, winid, false, first_scroll_win->w_buffer);
|
|
||||||
|
|
||||||
restore_v_event(v_event, &save_v_event);
|
restore_v_event(v_event, &save_v_event);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -349,4 +349,54 @@ describe('WinScrolled', function()
|
|||||||
[winid_str] = { leftcol = 0, topline = -3, topfill = 0, width = 0, height = 0, skipcol = 0 },
|
[winid_str] = { leftcol = 0, topline = -3, topfill = 0, width = 0, height = 0, skipcol = 0 },
|
||||||
}, eval('g:v_event'))
|
}, eval('g:v_event'))
|
||||||
end)
|
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)
|
end)
|
||||||
|
|||||||
Reference in New Issue
Block a user