diff --git a/src/nvim/window.c b/src/nvim/window.c index 65012f8564..9c27ff2857 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -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); } diff --git a/test/functional/autocmd/win_scrolled_resized_spec.lua b/test/functional/autocmd/win_scrolled_resized_spec.lua index 23de83e3dc..4876420552 100644 --- a/test/functional/autocmd/win_scrolled_resized_spec.lua +++ b/test/functional/autocmd/win_scrolled_resized_spec.lua @@ -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! \')}) + ]]) + 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)