changeset 22908:54219df706b5 v8.2.2001

patch 8.2.2001: Vim9: :def function does not apply 'maxfuncdepth' Commit: https://github.com/vim/vim/commit/0ba48e8c2741bd65d547fe6bf1d9873f411b25b4 Author: Bram Moolenaar <Bram@vim.org> Date: Tue Nov 17 18:23:19 2020 +0100 patch 8.2.2001: Vim9: :def function does not apply 'maxfuncdepth' Problem: Vim9: :def function does not apply 'maxfuncdepth'. Solution: Use 'maxfuncdepth'. (issue https://github.com/vim/vim/issues/7313)
author Bram Moolenaar <Bram@vim.org>
date Tue, 17 Nov 2020 18:30:04 +0100
parents 6a8b703b33a3
children 3bd8aeb73f94
files src/proto/userfunc.pro src/testdir/test_vim9_func.vim src/userfunc.c src/version.c src/vim9execute.c
diffstat 5 files changed, 99 insertions(+), 8 deletions(-) [+]
line wrap: on
line diff
--- a/src/proto/userfunc.pro
+++ b/src/proto/userfunc.pro
@@ -14,6 +14,10 @@ ufunc_T *find_func(char_u *name, int is_
 int func_is_global(ufunc_T *ufunc);
 int func_name_refcount(char_u *name);
 void copy_func(char_u *lambda, char_u *global);
+int funcdepth_increment(void);
+void funcdepth_decrement(void);
+int funcdepth_get(void);
+void funcdepth_restore(int depth);
 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);
--- a/src/testdir/test_vim9_func.vim
+++ b/src/testdir/test_vim9_func.vim
@@ -49,6 +49,36 @@ def TestCompilingError()
   call delete('XTest_compile_error')
 enddef
 
+def CallRecursive(n: number): number
+  return CallRecursive(n + 1)
+enddef
+
+def CallMapRecursive(l: list<number>): number
+  return map(l, {_, v -> CallMapRecursive([v])})[0]
+enddef
+
+def Test_funcdepth_error()
+  set maxfuncdepth=10
+
+  var caught = false
+  try
+    CallRecursive(1)
+  catch /E132:/
+    caught = true
+  endtry
+  assert_true(caught)
+
+  caught = false
+  try
+    CallMapRecursive([1])
+  catch /E132:/
+    caught = true
+  endtry
+  assert_true(caught)
+
+  set maxfuncdepth&
+enddef
+
 def ReturnString(): string
   return 'string'
 enddef
--- a/src/userfunc.c
+++ b/src/userfunc.c
@@ -1373,6 +1373,50 @@ failed:
     func_clear_free(fp, TRUE);
 }
 
+static int	funcdepth = 0;
+
+/*
+ * Increment the function call depth count.
+ * Return FAIL when going over 'maxfuncdepth'.
+ * Otherwise return OK, must call funcdepth_decrement() later!
+ */
+    int
+funcdepth_increment(void)
+{
+    if (funcdepth >= p_mfd)
+    {
+	emsg(_("E132: Function call depth is higher than 'maxfuncdepth'"));
+	return FAIL;
+    }
+    ++funcdepth;
+    return OK;
+}
+
+    void
+funcdepth_decrement(void)
+{
+    --funcdepth;
+}
+
+/*
+ * Get the current function call depth.
+ */
+    int
+funcdepth_get(void)
+{
+    return funcdepth;
+}
+
+/*
+ * Restore the function call depth.  This is for cases where there is no
+ * garantee funcdepth_decrement() can be called exactly the same number of
+ * times as funcdepth_increment().
+ */
+    void
+funcdepth_restore(int depth)
+{
+    funcdepth = depth;
+}
 
 /*
  * Call a user function.
@@ -1391,7 +1435,6 @@ call_user_func(
     funccall_T	*fc;
     int		save_did_emsg;
     int		default_arg_err = FALSE;
-    static int	depth = 0;
     dictitem_T	*v;
     int		fixvar_idx = 0;	// index in fixvar[]
     int		i;
@@ -1406,15 +1449,13 @@ call_user_func(
 #endif
     ESTACK_CHECK_DECLARATION
 
-    // If depth of calling is getting too high, don't execute the function
-    if (depth >= p_mfd)
+    // If depth of calling is getting too high, don't execute the function.
+    if (funcdepth_increment() == FAIL)
     {
-	emsg(_("E132: Function call depth is higher than 'maxfuncdepth'"));
 	rettv->v_type = VAR_NUMBER;
 	rettv->vval.v_number = -1;
 	return;
     }
-    ++depth;
 
     line_breakcheck();		// check for CTRL-C hit
 
@@ -1437,7 +1478,7 @@ call_user_func(
     {
 	// Execute the function, possibly compiling it first.
 	call_def_function(fp, argcount, argvars, funcexe->partial, rettv);
-	--depth;
+	funcdepth_decrement();
 	current_funccal = fc->caller;
 	free_funccal(fc);
 	return;
@@ -1783,8 +1824,7 @@ call_user_func(
     }
 
     did_emsg |= save_did_emsg;
-    --depth;
-
+    funcdepth_decrement();
     cleanup_function_call(fc);
 }
 
--- 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 */
 /**/
+    2001,
+/**/
     2000,
 /**/
     1999,
--- a/src/vim9execute.c
+++ b/src/vim9execute.c
@@ -227,6 +227,10 @@ call_dfunc(int cdf_idx, int argcount_arg
 								       == FAIL)
 	return FAIL;
 
+    // If depth of calling is getting too high, don't execute the function.
+    if (funcdepth_increment() == FAIL)
+	return FAIL;
+
     // Move the vararg-list to below the missing optional arguments.
     if (vararg_count > 0 && arg_to_add > 0)
 	*STACK_TV_BOT(arg_to_add - 1) = *STACK_TV_BOT(-1);
@@ -503,6 +507,7 @@ func_return(ectx_T *ectx)
     ectx->ec_stack.ga_len = top + 1;
     *STACK_TV_BOT(-1) = *STACK_TV(idx);
 
+    funcdepth_decrement();
     return OK;
 }
 
@@ -835,6 +840,7 @@ call_def_function(
     cmdmod_T	save_cmdmod;
     int		restore_cmdmod = FALSE;
     int		trylevel_at_start = trylevel;
+    int		orig_funcdepth;
 
 // Get pointer to item in the stack.
 #define STACK_TV(idx) (((typval_T *)ectx.ec_stack.ga_data) + idx)
@@ -870,11 +876,19 @@ call_def_function(
 	}
     }
 
+    // If depth of calling is getting too high, don't execute the function.
+    orig_funcdepth = funcdepth_get();
+    if (funcdepth_increment() == FAIL)
+	return FAIL;
+
     CLEAR_FIELD(ectx);
     ectx.ec_dfunc_idx = ufunc->uf_dfunc_idx;
     ga_init2(&ectx.ec_stack, sizeof(typval_T), 500);
     if (ga_grow(&ectx.ec_stack, 20) == FAIL)
+    {
+	funcdepth_decrement();
 	return FAIL;
+    }
     ga_init2(&ectx.ec_trystack, sizeof(trycmd_T), 10);
     ga_init2(&ectx.ec_funcrefs, sizeof(partial_T *), 10);
 
@@ -2941,6 +2955,7 @@ failed_early:
     if (ret != OK && did_emsg == did_emsg_before)
 	semsg(_(e_unknown_error_while_executing_str),
 						   printable_func_name(ufunc));
+    funcdepth_restore(orig_funcdepth);
     return ret;
 }