patch 9.1.1387: memory leak when buflist_new() fails to reuse curbuf

Problem:  buflist_new() leaks ffname and fails to reuse curbuf when
          autocommands from buf_freeall change curbuf. Plus, a new
          buffer is not allocated in this case, despite what the comment
          above claims.
Solution: Remove the condition so ffname is not leaked and so a new
          buffer is allocated like before v8.2.4791. It should not be
          possible for undo_ftplugin or buf_freeall autocommands to
          delete the buffer as they set b_locked, but to stay consistent
          with other uses of buf_freeall, guard against that anyway
          (Sean Dewar).

Note that buf is set to NULL if it was deleted to guard against the (rare)
possibility of messing up the "buf != curbuf" condition below if a new buffer
happens to be allocated at the same address.

closes: #17319

Signed-off-by: Sean Dewar <6256228+seandewar@users.noreply.github.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
This commit is contained in:
Sean Dewar
2025-05-14 20:16:52 +02:00
committed by Christian Brabandt
parent c952fd1b24
commit 0077282c82
3 changed files with 32 additions and 2 deletions

View File

@ -2220,13 +2220,14 @@ buflist_new(
buf = NULL; buf = NULL;
if ((flags & BLN_CURBUF) && curbuf_reusable()) if ((flags & BLN_CURBUF) && curbuf_reusable())
{ {
bufref_T bufref;
buf = curbuf; buf = curbuf;
set_bufref(&bufref, buf);
trigger_undo_ftplugin(buf, curwin); trigger_undo_ftplugin(buf, curwin);
// It's like this buffer is deleted. Watch out for autocommands that // It's like this buffer is deleted. Watch out for autocommands that
// change curbuf! If that happens, allocate a new buffer anyway. // change curbuf! If that happens, allocate a new buffer anyway.
buf_freeall(buf, BFA_WIPE | BFA_DEL); buf_freeall(buf, BFA_WIPE | BFA_DEL);
if (buf != curbuf) // autocommands deleted the buffer!
return NULL;
#ifdef FEAT_EVAL #ifdef FEAT_EVAL
if (aborting()) // autocmds may abort script processing if (aborting()) // autocmds may abort script processing
{ {
@ -2234,6 +2235,8 @@ buflist_new(
return NULL; return NULL;
} }
#endif #endif
if (!bufref_valid(&bufref))
buf = NULL; // buf was deleted; allocate a new buffer
} }
if (buf != curbuf || curbuf == NULL) if (buf != curbuf || curbuf == NULL)
{ {

View File

@ -5390,4 +5390,29 @@ func Test_eventignorewin_non_current()
%bw! %bw!
endfunc endfunc
func Test_reuse_curbuf_leak()
new bar
let s:bar_buf = bufnr()
augroup testing
autocmd!
autocmd BufDelete * ++once let s:triggered = 1 | execute s:bar_buf 'buffer'
augroup END
enew
let empty_buf = bufnr()
" Old curbuf should be reused, firing BufDelete. As BufDelete changes curbuf,
" reusing the buffer would fail and leak the ffname.
edit foo
call assert_equal(1, s:triggered)
" Wasn't reused because the buffer changed, but buffer "foo" is still created.
call assert_equal(1, bufexists(empty_buf))
call assert_notequal(empty_buf, bufnr())
call assert_equal('foo', bufname())
call assert_equal('bar', bufname(s:bar_buf))
unlet! s:bar_buf s:triggered
call CleanUpTestAuGroup()
%bw!
endfunc
" vim: shiftwidth=2 sts=2 expandtab " vim: shiftwidth=2 sts=2 expandtab

View File

@ -704,6 +704,8 @@ static char *(features[]) =
static int included_patches[] = static int included_patches[] =
{ /* Add new patch number below this line */ { /* Add new patch number below this line */
/**/
1387,
/**/ /**/
1386, 1386,
/**/ /**/