vim-patch:8.2.2354: crash with a weird combination of autocommands

Problem:    Crash with a weird combination of autocommands.
Solution:   Increment b_nwindows when needed. (closes vim/vim#7674)
797e63b9f2
This commit is contained in:
Jan Edmund Lazo 2021-05-11 22:17:12 -04:00
parent 4338077632
commit f98433d82a
No known key found for this signature in database
GPG Key ID: 64915E6E9F735B15
3 changed files with 48 additions and 14 deletions

View File

@ -407,7 +407,8 @@ bool buf_valid(buf_T *buf)
/// there to be only one window with this buffer. e.g. when /// there to be only one window with this buffer. e.g. when
/// ":quit" is supposed to close the window but autocommands /// ":quit" is supposed to close the window but autocommands
/// close all other windows. /// close all other windows.
void close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last) /// @returns true when we got to the end and b_nwindows was decremented.
bool close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last)
{ {
bool unload_buf = (action != 0); bool unload_buf = (action != 0);
bool del_buf = (action == DOBUF_DEL || action == DOBUF_WIPE); bool del_buf = (action == DOBUF_DEL || action == DOBUF_WIPE);
@ -444,7 +445,7 @@ void close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last)
// halfway a command that relies on it). Unloading is allowed. // halfway a command that relies on it). Unloading is allowed.
if (buf->b_locked > 0 && (del_buf || wipe_buf)) { if (buf->b_locked > 0 && (del_buf || wipe_buf)) {
EMSG(_("E937: Attempt to delete a buffer that is in use")); EMSG(_("E937: Attempt to delete a buffer that is in use"));
return; return false;
} }
if (win != NULL // Avoid bogus clang warning. if (win != NULL // Avoid bogus clang warning.
@ -471,13 +472,13 @@ void close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last)
buf) && !bufref_valid(&bufref)) { buf) && !bufref_valid(&bufref)) {
// Autocommands deleted the buffer. // Autocommands deleted the buffer.
EMSG(_(e_auabort)); EMSG(_(e_auabort));
return; return false;
} }
buf->b_locked--; buf->b_locked--;
if (abort_if_last && last_nonfloat(win)) { if (abort_if_last && last_nonfloat(win)) {
// Autocommands made this the only window. // Autocommands made this the only window.
EMSG(_(e_auabort)); EMSG(_(e_auabort));
return; return false;
} }
// When the buffer becomes hidden, but is not unloaded, trigger // When the buffer becomes hidden, but is not unloaded, trigger
@ -488,17 +489,17 @@ void close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last)
buf) && !bufref_valid(&bufref)) { buf) && !bufref_valid(&bufref)) {
// Autocommands deleted the buffer. // Autocommands deleted the buffer.
EMSG(_(e_auabort)); EMSG(_(e_auabort));
return; return false;
} }
buf->b_locked--; buf->b_locked--;
if (abort_if_last && last_nonfloat(win)) { if (abort_if_last && last_nonfloat(win)) {
// Autocommands made this the only window. // Autocommands made this the only window.
EMSG(_(e_auabort)); EMSG(_(e_auabort));
return; return false;
} }
} }
if (aborting()) { // autocmds may abort script processing if (aborting()) { // autocmds may abort script processing
return; return false;
} }
} }
@ -525,7 +526,7 @@ void close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last)
/* Return when a window is displaying the buffer or when it's not /* Return when a window is displaying the buffer or when it's not
* unloaded. */ * unloaded. */
if (buf->b_nwindows > 0 || !unload_buf) { if (buf->b_nwindows > 0 || !unload_buf) {
return; return false;
} }
if (buf->terminal) { if (buf->terminal) {
@ -561,11 +562,11 @@ void close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last)
if (!bufref_valid(&bufref)) { if (!bufref_valid(&bufref)) {
// Autocommands may have deleted the buffer. // Autocommands may have deleted the buffer.
return; return false;
} }
if (aborting()) { if (aborting()) {
// Autocmds may abort script processing. // Autocmds may abort script processing.
return; return false;
} }
/* /*
@ -576,7 +577,7 @@ void close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last)
* deleted buffer. * deleted buffer.
*/ */
if (buf == curbuf && !is_curbuf) { if (buf == curbuf && !is_curbuf) {
return; return false;
} }
if (win != NULL // Avoid bogus clang warning. if (win != NULL // Avoid bogus clang warning.
@ -636,6 +637,8 @@ void close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last)
buf->b_p_bl = false; buf->b_p_bl = false;
} }
} }
// NOTE: at this point "curbuf" may be invalid!
return true;
} }
/// Make buffer not contain a file. /// Make buffer not contain a file.

View File

@ -2462,6 +2462,7 @@ int do_ecmd(
auto_buf = true; auto_buf = true;
} else { } else {
win_T *the_curwin = curwin; win_T *the_curwin = curwin;
buf_T *was_curbuf = curbuf;
// Set w_closing to avoid that autocommands close the window. // Set w_closing to avoid that autocommands close the window.
// Set b_locked for the same reason. // Set b_locked for the same reason.
@ -2475,9 +2476,10 @@ int do_ecmd(
// Close the link to the current buffer. This will set // Close the link to the current buffer. This will set
// oldwin->w_buffer to NULL. // oldwin->w_buffer to NULL.
u_sync(false); u_sync(false);
close_buffer(oldwin, curbuf, const bool did_decrement = close_buffer(
(flags & ECMD_HIDE) || curbuf->terminal ? 0 : DOBUF_UNLOAD, oldwin, curbuf,
false); (flags & ECMD_HIDE) || curbuf->terminal ? 0 : DOBUF_UNLOAD,
false);
// Autocommands may have closed the window. // Autocommands may have closed the window.
if (win_valid(the_curwin)) { if (win_valid(the_curwin)) {
@ -2497,6 +2499,14 @@ int do_ecmd(
goto theend; goto theend;
} }
if (buf == curbuf) { // already in new buffer if (buf == curbuf) { // already in new buffer
// close_buffer() has decremented the window count,
// increment it again here and restore w_buffer.
if (did_decrement && buf_valid(was_curbuf)) {
was_curbuf->b_nwindows++;
}
if (win_valid_any_tab(oldwin) && oldwin->w_buffer == NULL) {
oldwin->w_buffer = was_curbuf;
}
auto_buf = true; auto_buf = true;
} else { } else {
// <VN> We could instead free the synblock // <VN> We could instead free the synblock

View File

@ -452,6 +452,27 @@ func Test_autocmd_bufwipe_in_SessLoadPost()
endfor endfor
endfunc endfunc
" Using :blast and :ball for many events caused a crash, because b_nwindows was
" not incremented correctly.
func Test_autocmd_blast_badd()
let content =<< trim [CODE]
au BufNew,BufAdd,BufWinEnter,BufEnter,BufLeave,BufWinLeave,BufUnload,VimEnter foo* blast
edit foo1
au BufNew,BufAdd,BufWinEnter,BufEnter,BufLeave,BufWinLeave,BufUnload,VimEnter foo* ball
edit foo2
call writefile(['OK'], 'Xerrors')
qall
[CODE]
call writefile(content, 'XblastBall')
call system(GetVimCommand() .. ' --clean -S XblastBall')
" call assert_match('OK', readfile('Xerrors')->join())
call assert_match('OK', join(readfile('Xerrors')))
call delete('XblastBall')
call delete('Xerrors')
endfunc
" SEGV occurs in older versions. " SEGV occurs in older versions.
func Test_autocmd_bufwipe_in_SessLoadPost2() func Test_autocmd_bufwipe_in_SessLoadPost2()
tabnew tabnew