From 3ccba4cdffb82e8238475e52382b20e380322e8f Mon Sep 17 00:00:00 2001 From: Sean Dewar <6256228+seandewar@users.noreply.github.com> Date: Sat, 11 Oct 2025 18:08:24 +0100 Subject: [PATCH] fix(float): crash from nasty :fclose autocmds (#36134) Problem: :fclose may crash Nvim if autocommands close floats prematurely. Alternatively, :fclose may call win_close for windows not in curtab if autocommands change curtab or move windows between tab pages via nvim_win_set_config (may not crash, but is wrong). Solution: check win_valid before calling win_close. --- src/nvim/winfloat.c | 3 +- test/functional/ui/float_spec.lua | 49 +++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/src/nvim/winfloat.c b/src/nvim/winfloat.c index 088c378c95..0dc644112b 100644 --- a/src/nvim/winfloat.c +++ b/src/nvim/winfloat.c @@ -283,7 +283,8 @@ void win_float_remove(bool bang, int count) qsort(float_win_arr.items, float_win_arr.size, sizeof(win_T *), float_zindex_cmp); } for (size_t i = 0; i < float_win_arr.size; i++) { - if (win_close(float_win_arr.items[i], false, false) == FAIL) { + win_T *wp = float_win_arr.items[i]; + if (win_valid(wp) && win_close(wp, false, false) == FAIL) { break; } if (!bang) { diff --git a/test/functional/ui/float_spec.lua b/test/functional/ui/float_spec.lua index 6420abaa17..0a2b94f743 100644 --- a/test/functional/ui/float_spec.lua +++ b/test/functional/ui/float_spec.lua @@ -971,6 +971,55 @@ describe('float window', function() api.nvim_win_set_config(win, api.nvim_win_get_config(win)) end) + it(':fclose does not crash from nasty autocommands', function() + local w1 = api.nvim_open_win(0, false, { relative = 'editor', row = 0, col = 0, width = 5, height = 5, zindex = 6 }) + local w2 = api.nvim_open_win(0, false, { relative = 'editor', row = 0, col = 0, width = 5, height = 5, zindex = 5 }) + local w3 = api.nvim_open_win(0, false, { relative = 'editor', row = 0, col = 0, width = 5, height = 5, zindex = 4 }) + local w4 = api.nvim_open_win(0, true, { relative = 'editor', row = 0, col = 0, width = 5, height = 5, zindex = 3 }) + exec_lua(function() + vim.api.nvim_create_autocmd('WinClosed', { + once = true, + callback = function() + vim.api.nvim_win_close(w2, true) + end, + }) + end) + -- We close just the first three floats with highest zindex, so w4 remains open. + -- (despite w2 being closed early by the autocommand rather than directly by :fclose) + command('3fclose') + eq(false, api.nvim_win_is_valid(w1)) + eq(false, api.nvim_win_is_valid(w2)) + eq(false, api.nvim_win_is_valid(w3)) + eq(true, api.nvim_win_is_valid(w4)) + + -- Try switching tab pages and moving windows between tab pages via nvim_win_set_config. + -- Simplest if :fclose skips windows in non-current tabpages. + local w5 = api.nvim_open_win(0, false, { relative = 'editor', row = 0, col = 0, width = 5, height = 5, zindex = 2 }) + command('autocmd WinEnter * ++once tabnew') + eq(w4, api.nvim_get_current_win()) + local tp1 = api.nvim_get_current_tabpage() + command('fclose!') + eq(false, api.nvim_win_is_valid(w4)) + eq(true, api.nvim_win_is_valid(w5)) + neq(tp1, api.nvim_get_current_tabpage()) + + local w_tp2 = api.nvim_get_current_win() + api.nvim_set_current_tabpage(tp1) + local w6 = api.nvim_open_win(0, false, { relative = 'editor', row = 0, col = 0, width = 5, height = 5, zindex = 1 }) + exec_lua(function() + vim.api.nvim_create_autocmd('WinClosed', { + once = true, + callback = function() + vim.api.nvim_win_set_config(w6, { win = w_tp2, split = 'below' }) + end, + }) + end) + command('fclose!') + eq(false, api.nvim_win_is_valid(w5)) + eq(true, api.nvim_win_is_valid(w6)) + neq(tp1, api.nvim_win_get_tabpage(w6)) + end) + local function with_ext_multigrid(multigrid, send_mouse_grid) local screen, attrs before_each(function()