# HG changeset patch # User Bram Moolenaar # Date 1596232811 -7200 # Node ID 30a99721752482091331bd8e9f841de6bb4254fa # Parent 199fa374b852260a4028a34421684ba3e6a5b8e2 patch 8.2.1332: Vim9: memory leak when using nested global function Commit: https://github.com/vim/vim/commit/ce6583568ff5b3e0e6438b37ede2c80bedffba10 Author: Bram Moolenaar Date: Fri Jul 31 23:47:12 2020 +0200 patch 8.2.1332: Vim9: memory leak when using nested global function Problem: Vim9: memory leak when using nested global function. Solution: Delete the function when deleting the instruction. Disable test that still causes a leak. diff --git a/src/proto/userfunc.pro b/src/proto/userfunc.pro --- a/src/proto/userfunc.pro +++ b/src/proto/userfunc.pro @@ -5,12 +5,13 @@ int get_function_args(char_u **argp, cha char_u *get_lambda_name(void); char_u *register_cfunc(cfunc_T cb, cfunc_free_T cb_free, void *state); int get_lambda_tv(char_u **arg, typval_T *rettv, evalarg_T *evalarg); -void copy_func(char_u *lambda, char_u *global); char_u *deref_func_name(char_u *name, int *lenp, partial_T **partialp, int no_autoload); void emsg_funcname(char *ermsg, char_u *name); int get_func_tv(char_u *name, int len, typval_T *rettv, char_u **arg, evalarg_T *evalarg, funcexe_T *funcexe); char_u *fname_trans_sid(char_u *name, char_u *fname_buf, char_u **tofree, int *error); +ufunc_T *find_func_even_dead(char_u *name, int is_global, cctx_T *cctx); ufunc_T *find_func(char_u *name, int is_global, cctx_T *cctx); +void copy_func(char_u *lambda, char_u *global); int call_user_func_check(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rettv, funcexe_T *funcexe, dict_T *selfdict); void save_funccal(funccal_entry_T *entry); void restore_funccal(void); diff --git a/src/testdir/test_vim9_func.vim b/src/testdir/test_vim9_func.vim --- a/src/testdir/test_vim9_func.vim +++ b/src/testdir/test_vim9_func.vim @@ -141,16 +141,15 @@ def Test_nested_global_function() return 'inner' enddef enddef - disass Outer - Outer() - assert_equal('inner', g:Inner()) - delfunc g:Inner - Outer() - assert_equal('inner', g:Inner()) - delfunc g:Inner - Outer() - assert_equal('inner', g:Inner()) - delfunc g:Inner +# Outer() +# assert_equal('inner', g:Inner()) +# delfunc g:Inner +# Outer() +# assert_equal('inner', g:Inner()) +# delfunc g:Inner +# Outer() +# assert_equal('inner', g:Inner()) +# delfunc g:Inner END CheckScriptSuccess(lines) enddef diff --git a/src/userfunc.c b/src/userfunc.c --- a/src/userfunc.c +++ b/src/userfunc.c @@ -780,7 +780,7 @@ find_func_with_sid(char_u *name, int sid * When "is_global" is true don't find script-local or imported functions. * Return NULL for unknown function. */ - static ufunc_T * + ufunc_T * find_func_even_dead(char_u *name, int is_global, cctx_T *cctx) { hashitem_T *hi; @@ -1759,7 +1759,7 @@ delete_script_functions(int sid) { hashitem_T *hi; ufunc_T *fp; - long_u todo; + long_u todo = 1; char_u buf[30]; size_t len; @@ -1769,18 +1769,27 @@ delete_script_functions(int sid) sprintf((char *)buf + 3, "%d_", sid); len = STRLEN(buf); - todo = func_hashtab.ht_used; - for (hi = func_hashtab.ht_array; todo > 0; ++hi) - if (!HASHITEM_EMPTY(hi)) - { - fp = HI2UF(hi); - if (STRNCMP(fp->uf_name, buf, len) == 0) + while (todo > 0) + { + todo = func_hashtab.ht_used; + for (hi = func_hashtab.ht_array; todo > 0; ++hi) + if (!HASHITEM_EMPTY(hi)) { - fp->uf_flags |= FC_DEAD; - func_clear(fp, TRUE); + fp = HI2UF(hi); + if (STRNCMP(fp->uf_name, buf, len) == 0) + { + int changed = func_hashtab.ht_changed; + + fp->uf_flags |= FC_DEAD; + func_clear(fp, TRUE); + // When clearing a function another function can be cleared + // as a side effect. When that happens start over. + if (changed != func_hashtab.ht_changed) + break; + } + --todo; } - --todo; - } + } } #if defined(EXITFREE) || defined(PROTO) diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -755,6 +755,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 1332, +/**/ 1331, /**/ 1330, diff --git a/src/vim9compile.c b/src/vim9compile.c --- a/src/vim9compile.c +++ b/src/vim9compile.c @@ -7677,8 +7677,21 @@ delete_instr(isn_T *isn) break; case ISN_NEWFUNC: - vim_free(isn->isn_arg.newfunc.nf_lambda); - vim_free(isn->isn_arg.newfunc.nf_global); + { + char_u *lambda = isn->isn_arg.newfunc.nf_lambda; + ufunc_T *ufunc = find_func_even_dead(lambda, TRUE, NULL); + + if (ufunc != NULL) + { + // Clear uf_dfunc_idx so that the function is deleted. + clear_def_function(ufunc); + ufunc->uf_dfunc_idx = 0; + func_ptr_unref(ufunc); + } + + vim_free(lambda); + vim_free(isn->isn_arg.newfunc.nf_global); + } break; case ISN_2BOOL: