commit 3ccba4cdffb82e8238475e52382b20e380322e8f
parent 150490d365a37b89d88a0e66ef6ec789bb4eb1c3
Author: Sean Dewar <6256228+seandewar@users.noreply.github.com>
Date: Sat, 11 Oct 2025 18:08:24 +0100
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.
Diffstat:
2 files changed, 51 insertions(+), 1 deletion(-)
diff --git 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
@@ -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()