commit 6b9665a507b16182a6d5c4e45518517b3547b48e
parent f2aec4bc0579e94b0f0ed5e5524a05c71ffb393e
Author: Sean Dewar <6256228+seandewar@users.noreply.github.com>
Date: Wed, 14 May 2025 19:39:13 +0100
vim-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: vim/vim#17319
https://github.com/vim/vim/commit/0077282c8209e5a3893d74a0176ab21895c5de4e
Co-authored-by: Sean Dewar <6256228+seandewar@users.noreply.github.com>
Diffstat:
2 files changed, 31 insertions(+), 3 deletions(-)
diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c
@@ -1904,18 +1904,21 @@ buf_T *buflist_new(char *ffname_arg, char *sfname_arg, linenr_T lnum, int flags)
// buffer.)
buf = NULL;
if ((flags & BLN_CURBUF) && curbuf_reusable()) {
+ bufref_T bufref;
+
assert(curbuf != NULL);
buf = curbuf;
+ set_bufref(&bufref, buf);
// It's like this buffer is deleted. Watch out for autocommands that
// change curbuf! If that happens, allocate a new buffer anyway.
buf_freeall(buf, BFA_WIPE | BFA_DEL);
- if (buf != curbuf) { // autocommands deleted the buffer!
- return NULL;
- }
if (aborting()) { // autocmds may abort script processing
xfree(ffname);
return NULL;
}
+ if (!bufref_valid(&bufref)) {
+ buf = NULL; // buf was deleted; allocate a new buffer
+ }
}
if (buf != curbuf || curbuf == NULL) {
buf = xcalloc(1, sizeof(buf_T));
diff --git a/test/old/testdir/test_autocmd.vim b/test/old/testdir/test_autocmd.vim
@@ -4482,4 +4482,29 @@ func Test_eventignorewin_non_current()
%bw!
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