# HG changeset patch # User Bram Moolenaar # Date 1615990503 -3600 # Node ID 03fc95628eb033b7e736ee47f59a226e6edc3760 # Parent 4d64b2bee1222515b5b058c24a3aa9e6fe8db29b patch 8.2.2614: Vim9: function is deleted while executing Commit: https://github.com/vim/vim/commit/c970e4225b68ff8650ff23df09e04246c147fca3 Author: Bram Moolenaar Date: Wed Mar 17 15:03:04 2021 +0100 patch 8.2.2614: Vim9: function is deleted while executing Problem: Vim9: function is deleted while executing. Solution: increment the call count, when more than zero do not delete the function but mark it as dead. (closes #7977) diff --git a/src/testdir/test_vim9_script.vim b/src/testdir/test_vim9_script.vim --- a/src/testdir/test_vim9_script.vim +++ b/src/testdir/test_vim9_script.vim @@ -1561,6 +1561,35 @@ def Test_script_reload_change_type() delete('Xreload.vim') enddef +" Define CallFunc so that the test can be compiled +command CallFunc echo 'nop' + +def Test_script_reload_from_function() + var lines =<< trim END + vim9script + + if exists('g:loaded') + finish + endif + g:loaded = 1 + delcommand CallFunc + command CallFunc Func() + def Func() + so /tmp/test.vim + g:didTheFunc = 1 + enddef + END + writefile(lines, 'XreloadFunc.vim') + source XreloadFunc.vim + CallFunc + assert_equal(1, g:didTheFunc) + + delete('XreloadFunc.vim') + delcommand CallFunc + unlet g:loaded + unlet g:didTheFunc +enddef + def Test_script_var_shadows_function() var lines =<< trim END vim9script diff --git a/src/userfunc.c b/src/userfunc.c --- a/src/userfunc.c +++ b/src/userfunc.c @@ -1359,9 +1359,12 @@ func_remove(ufunc_T *fp) // function, so we can find the index when defining the function again. // Do remove it when it's a copy. if (fp->uf_def_status == UF_COMPILED && (fp->uf_flags & FC_COPY) == 0) + { fp->uf_flags |= FC_DEAD; - else - hash_remove(&func_hashtab, hi); + return FALSE; + } + hash_remove(&func_hashtab, hi); + fp->uf_flags |= FC_DELETED; return TRUE; } return FALSE; @@ -2134,11 +2137,23 @@ delete_script_functions(int sid) 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; + + if (fp->uf_calls > 0) + { + // Function is executing, don't free it but do remove + // it from the hashtable. + if (func_remove(fp)) + fp->uf_refcount--; + } + else + { + 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; } @@ -4251,7 +4266,6 @@ ex_delfunction(exarg_T *eap) // do remove it from the hashtable. if (func_remove(fp)) fp->uf_refcount--; - fp->uf_flags |= FC_DELETED; } else func_clear_free(fp, FALSE); diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -751,6 +751,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 2614, +/**/ 2613, /**/ 2612, diff --git a/src/vim9execute.c b/src/vim9execute.c --- a/src/vim9execute.c +++ b/src/vim9execute.c @@ -323,6 +323,8 @@ call_dfunc(int cdf_idx, partial_T *pt, i else ectx->ec_outer = NULL; + ++ufunc->uf_calls; + // Set execution state to the start of the called function. ectx->ec_dfunc_idx = cdf_idx; ectx->ec_instr = INSTRUCTIONS(dfunc); @@ -556,6 +558,9 @@ func_return(ectx_T *ectx) } } #endif + // TODO: when is it safe to delete the function when it is no longer used? + --dfunc->df_ufunc->uf_calls; + // execution context goes one level up entry = estack_pop(); if (entry != NULL) @@ -1334,7 +1339,7 @@ call_def_function( ++ectx.ec_stack.ga_len; } if (ufunc->uf_va_name != NULL) - ++ectx.ec_stack.ga_len; + ++ectx.ec_stack.ga_len; // Frame pointer points to just after arguments. ectx.ec_frame_idx = ectx.ec_stack.ga_len; @@ -1407,6 +1412,9 @@ call_def_function( // Do turn errors into exceptions. suppress_errthrow = FALSE; + // Do not delete the function while executing it. + ++ufunc->uf_calls; + // When ":silent!" was used before calling then we still abort the // function. If ":silent!" is used in the function then we don't. emsg_silent_def = emsg_silent; @@ -3838,6 +3846,9 @@ failed: estack_pop(); current_sctx = save_current_sctx; + // TODO: when is it safe to delete the function if it is no longer used? + --ufunc->uf_calls; + if (*msg_list != NULL && saved_msg_list != NULL) { msglist_T **plist = saved_msg_list;