From 19f64a2680d32cc0ff5316e1e2961ecb33a1640c Mon Sep 17 00:00:00 2001 From: zeertzjq Date: Wed, 24 Jun 2026 08:23:32 +0800 Subject: [PATCH] vim-patch:9.2.0708: Leaks in do_autocmd in error case (#40386) Problem: Leak in do_autocmd in error case (Cheng) Solution: goto err_exit in the error case and clean up, make the double ++once an actual error closes: vim/vim#20606 https://github.com/vim/vim/commit/98f5171ef6ba9aa6aea7223e833115e544199bd4 Co-authored-by: Christian Brabandt --- src/nvim/autocmd.c | 11 ++++++----- test/old/testdir/test_autocmd.vim | 33 +++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/src/nvim/autocmd.c b/src/nvim/autocmd.c index e1f023e779..1003187e99 100644 --- a/src/nvim/autocmd.c +++ b/src/nvim/autocmd.c @@ -763,7 +763,7 @@ void do_autocmd(exarg_T *eap, char *arg_in, int forceit) char *arg = arg_in; char *envpat = NULL; char *cmd; - bool need_free = false; + bool cmd_need_free = false; bool nested = false; bool once = false; int group; @@ -832,7 +832,7 @@ void do_autocmd(exarg_T *eap, char *arg_in, int forceit) } if (invalid_flags) { - return; + goto err_exit; } // Find the start of the commands. @@ -840,9 +840,9 @@ void do_autocmd(exarg_T *eap, char *arg_in, int forceit) if (*cmd != NUL) { cmd = expand_sfile(cmd); if (cmd == NULL) { // some error - return; + goto err_exit; } - need_free = true; + cmd_need_free = true; } } @@ -879,7 +879,8 @@ void do_autocmd(exarg_T *eap, char *arg_in, int forceit) } } - if (need_free) { +err_exit: + if (cmd_need_free) { xfree(cmd); } xfree(envpat); diff --git a/test/old/testdir/test_autocmd.vim b/test/old/testdir/test_autocmd.vim index 05ee2f8b5a..558d254775 100644 --- a/test/old/testdir/test_autocmd.vim +++ b/test/old/testdir/test_autocmd.vim @@ -3126,8 +3126,41 @@ func Test_autocmd_once() close call assert_fails('au WinNew * ++once ++once echo bad', 'E983:') + call assert_false(exists('#WinNew')) endfunc +func Test_autocmd_dup_arg() + " Duplicate ++once / ++nested, or the legacy "nested" used twice, must + " error out *and* not create the autocommand. Using an environment + " variable in the pattern also exercises the error-exit path that frees + " the expanded pattern (checked by the address/leak sanitizers). + augroup XdupTest + au! + augroup END + let $XAUTODIR = 'Xfoo' + + " New behavior: duplicate ++once now aborts, the autocmd is not added + call assert_fails('au XdupTest WinNew $XAUTODIR/* ++once ++once echo bad', 'E983:') + call assert_false(exists('#XdupTest#WinNew')) + + call assert_fails('au XdupTest WinNew $XAUTODIR/* ++nested ++nested echo bad', 'E983:') + call assert_false(exists('#XdupTest#WinNew')) + + call assert_fails('au XdupTest WinNew $XAUTODIR/* nested nested echo bad', 'E983:') + call assert_false(exists('#XdupTest#WinNew')) + + " "nested" without "++" is rejected in Vim9 script (also frees the pattern) + "call assert_fails('vim9cmd au XdupTest WinNew $XAUTODIR/* nested echo bad', 'E1078:') + call assert_false(exists('#XdupTest#WinNew')) + + augroup XdupTest + au! + augroup END + augroup! XdupTest + let $XAUTODIR = '' +endfunc + + func Test_autocmd_bufreadpre() new let b:bufreadpre = 1