patch 9.2.0777: Memory leak in add_defer() on alloc failure
Commit:
https://github.com/vim/vim/commit/e43064830d1b69fe1bac1653e070b13b1c2fef00
Author: Christian Brabandt <
c...@256bit.org>
Date: Thu Jul 2 19:54:49 2026 +0000
patch 9.2.0777: Memory leak in add_defer() on alloc failure
Problem: Memory leak in add_defer() on allocation failure (Ao Xijie)
Solution: Free saved_name when ga_grow() fails, propagate failure from
add_defer_function() to the caller, add a test for alloc
failure
related: #20668
Supported by AI.
Signed-off-by: Christian Brabandt <
c...@256bit.org>
diff --git a/src/alloc.h b/src/alloc.h
index 651e01f24..f3a27f621 100644
--- a/src/alloc.h
+++ b/src/alloc.h
@@ -45,5 +45,6 @@ typedef enum {
aid_newtabpage_tvars,
aid_blob_alloc,
aid_get_func,
+ aid_defer,
aid_last
} alloc_id_T;
diff --git a/src/testdir/test_user_func.vim b/src/testdir/test_user_func.vim
index 56b21a481..c7e58e9e1 100644
--- a/src/testdir/test_user_func.vim
+++ b/src/testdir/test_user_func.vim
@@ -1087,4 +1087,29 @@ func Test_func_return_in_try_verbose()
delfunc TryReturnOverlongString
endfunc
+" Memory allocation failure when registering a deferred function: the ga_grow()
+" in add_defer() fails, so the deferred function must not be registered and
+" "saved_name" must not leak
+func Test_defer_alloc_fail()
+ let g:defer_log = []
+ func DeferLogTarget()
+ call add(g:defer_log, 'ran')
+ endfunc
+ func CallWithDefer()
+ defer DeferLogTarget()
+ endfunc
+
+ call test_alloc_fail(GetAllocId('defer'), 0, 0)
+ call assert_fails('call CallWithDefer()', 'E342:')
+ call assert_equal([], g:defer_log)
+
+ " Without the injected failure it registers and runs at function exit.
+ call CallWithDefer()
+ call assert_equal(['ran'], g:defer_log)
+
+ delfunc DeferLogTarget
+ delfunc CallWithDefer
+ unlet g:defer_log
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/userfunc.c b/src/userfunc.c
index 66b55449e..de1a8e84e 100644
--- a/src/userfunc.c
+++ b/src/userfunc.c
@@ -6684,14 +6684,20 @@ add_defer(char_u *name, int argcount_arg, typval_T *argvars)
if (in_def_function())
{
if (add_defer_function(saved_name, argcount, argvars) == OK)
+ {
argcount = 0;
+ ret = OK;
+ }
}
else
{
if (current_funccal->fc_defer.ga_itemsize == 0)
ga_init2(¤t_funccal->fc_defer, sizeof(defer_T), 10);
- if (ga_grow(¤t_funccal->fc_defer, 1) == FAIL)
+ if (ga_grow_id(¤t_funccal->fc_defer, 1, aid_defer) == FAIL)
+ {
+ vim_free(saved_name);
goto theend;
+ }
dr = ((defer_T *)current_funccal->fc_defer.ga_data)
+ current_funccal->fc_defer.ga_len++;
dr->dr_name = saved_name;
@@ -6701,8 +6707,8 @@ add_defer(char_u *name, int argcount_arg, typval_T *argvars)
--argcount;
dr->dr_argvars[argcount] = argvars[argcount];
}
+ ret = OK;
}
- ret = OK;
theend:
while (--argcount >= 0)
diff --git a/src/version.c b/src/version.c
index 1e7eba70a..70c83e88a 100644
--- a/src/version.c
+++ b/src/version.c
@@ -759,6 +759,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
+/**/
+ 777,
/**/
776,
/**/