From d9631c7678f74f0785be87295c70a78f4038b289 Mon Sep 17 00:00:00 2001 From: Sean Dewar <6256228+seandewar@users.noreply.github.com> Date: Sun, 4 Jan 2026 13:15:08 +0000 Subject: [PATCH] fix(window): crash closing only non-float if autocmds :tabonly (#37218) Problem: null pointer member access when closing the only non-float in the current tab page if autocommands after closing all floats also close all other tab pages. (making it the last window) Solution: check last_window again after closing the floats. Also reduce the scope of "wp"; it would be bugprone to use it before it's later reassigned to the rv of win_free_mem if freed by Buf/WinLeave. (cherry picked from commit c14de47f1a12e1479221ab525d224d77f7eb5953) --- src/nvim/window.c | 15 +++++++++++---- test/functional/ui/float_spec.lua | 5 +++++ 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/nvim/window.c b/src/nvim/window.c index 7ff4a06e6d..7cfe436337 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -98,6 +98,8 @@ typedef enum { WEE_TRIGGER_LEAVE_AUTOCMDS = 0x10, } wee_flags_T; +static const char e_cannot_close_last_window[] + = N_("E444: Cannot close last window"); static const char e_cannot_split_window_when_closing_buffer[] = N_("E1159: Cannot split a window when closing the buffer"); @@ -2683,7 +2685,7 @@ int win_close(win_T *win, bool free_buf, bool force) const bool had_diffmode = win->w_p_diff; if (last_window(win)) { - emsg(_("E444: Cannot close last window")); + emsg(_(e_cannot_close_last_window)); return FAIL; } @@ -2712,6 +2714,11 @@ int win_close(win_T *win, bool free_buf, bool force) if (!win_valid_any_tab(win)) { return FAIL; // window already closed by autocommands } + // Autocommands may have closed all other tabpages; check again. + if (last_window(win)) { + emsg(_(e_cannot_close_last_window)); + return FAIL; + } } else { emsg(e_floatonly); return FAIL; @@ -2735,7 +2742,6 @@ int win_close(win_T *win, bool free_buf, bool force) clear_snapshot(curtab, SNAP_HELP_IDX); } - win_T *wp; bool other_buffer = false; if (win == curwin) { @@ -2743,7 +2749,8 @@ int win_close(win_T *win, bool free_buf, bool force) // Guess which window is going to be the new current window. // This may change because of the autocommands (sigh). - wp = win->w_floating ? win_float_find_altwin(win, NULL) : frame2win(win_altframe(win, NULL)); + win_T *wp = win->w_floating ? win_float_find_altwin(win, NULL) + : frame2win(win_altframe(win, NULL)); // Be careful: If autocommands delete the window or cause this window // to be the last one left, return now. @@ -2838,7 +2845,7 @@ int win_close(win_T *win, bool free_buf, bool force) // Free the memory used for the window and get the window that received // the screen space. int dir; - wp = win_free_mem(win, &dir, NULL); + win_T *wp = win_free_mem(win, &dir, NULL); if (help_window) { // Closing the help window moves the cursor back to the current window diff --git a/test/functional/ui/float_spec.lua b/test/functional/ui/float_spec.lua index 83dc1d2766..c8492b0f3e 100644 --- a/test/functional/ui/float_spec.lua +++ b/test/functional/ui/float_spec.lua @@ -710,6 +710,11 @@ describe('float window', function() api.nvim_set_current_win(old_win) eq('Vim:E444: Cannot close last window', pcall_err(api.nvim_win_close, old_win, false)) + -- Start with many tab pages, but make autocommands from closing floats leave us with just + -- one (where we're now the last window). + command('tabnew | autocmd WinClosed * ++once tabonly') + api.nvim_open_win(0, false, float_opts) + eq('Vim:E444: Cannot close last window', pcall_err(api.nvim_win_close, 0, true)) end) it('if called from floating window', function() eq('Vim:E444: Cannot close last window',