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 c14de47f1a)
This commit is contained in:
Sean Dewar
2026-01-04 13:15:08 +00:00
parent bb31e7b345
commit d9631c7678
2 changed files with 16 additions and 4 deletions

View File

@@ -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

View File

@@ -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',