changeset 30166:d1c04b4dc60d v9.0.0419

patch 9.0.0419: the :defer command does not check the function arguments Commit: https://github.com/vim/vim/commit/169003289fb4b2ad18fd7f5807e0d05efff0be85 Author: Bram Moolenaar <Bram@vim.org> Date: Thu Sep 8 19:51:45 2022 +0100 patch 9.0.0419: the :defer command does not check the function arguments Problem: The :defer command does not check the function argument count and types. Solution: Check the function arguments when adding a deferred function.
author Bram Moolenaar <Bram@vim.org>
date Thu, 08 Sep 2022 21:00:04 +0200
parents 27820ff1eaf4
children 3a316f08a3c0
files src/proto/vim9instr.pro src/testdir/test_user_func.vim src/userfunc.c src/version.c src/vim9cmds.c src/vim9instr.c
diffstat 6 files changed, 242 insertions(+), 111 deletions(-) [+]
line wrap: on
line diff
--- a/src/proto/vim9instr.pro
+++ b/src/proto/vim9instr.pro
@@ -46,11 +46,14 @@ int generate_JUMP(cctx_T *cctx, jumpwhen
 int generate_JUMP_IF_ARG_SET(cctx_T *cctx, int arg_off);
 int generate_FOR(cctx_T *cctx, int loop_idx);
 int generate_TRYCONT(cctx_T *cctx, int levels, int where);
+int check_internal_func_args(cctx_T *cctx, int func_idx, int argcount, int method_call, type2_T **argtypes, type2_T *shuffled_argtypes);
 int generate_BCALL(cctx_T *cctx, int func_idx, int argcount, int method_call);
 int generate_LISTAPPEND(cctx_T *cctx);
 int generate_BLOBAPPEND(cctx_T *cctx);
+int check_args_on_stack(cctx_T *cctx, ufunc_T *ufunc, int argcount);
 int generate_CALL(cctx_T *cctx, ufunc_T *ufunc, int pushed_argcount);
 int generate_UCALL(cctx_T *cctx, char_u *name, int argcount);
+int check_func_args_from_type(cctx_T *cctx, type_T *type, int argcount, int at_top, char_u *name);
 int generate_PCALL(cctx_T *cctx, int argcount, char_u *name, type_T *type, int at_top);
 int generate_DEFER(cctx_T *cctx, int var_idx, int argcount);
 int generate_STRINGMEMBER(cctx_T *cctx, char_u *name, size_t len);
--- a/src/testdir/test_user_func.vim
+++ b/src/testdir/test_user_func.vim
@@ -5,6 +5,7 @@
 
 source check.vim
 source shared.vim
+import './vim9.vim' as v9
 
 func Table(title, ...)
   let ret = a:title
@@ -619,7 +620,7 @@ func Test_defer_quitall()
       DeferLevelOne()
   END
   call writefile(lines, 'XdeferQuitall', 'D')
-  let res = system(GetVimCommandClean() .. ' -X -S XdeferQuitall')
+  let res = system(GetVimCommand() .. ' -X -S XdeferQuitall')
   call assert_equal(0, v:shell_error)
   call assert_false(filereadable('XQuitallOne'))
   call assert_false(filereadable('XQuitallTwo'))
@@ -641,7 +642,7 @@ func Test_defer_quitall_in_expr_func()
       call Test_defer_in_funcref()
   END
   call writefile(lines, 'XdeferQuitallExpr', 'D')
-  let res = system(GetVimCommandClean() .. ' -X -S XdeferQuitallExpr')
+  let res = system(GetVimCommand() .. ' -X -S XdeferQuitallExpr')
   call assert_equal(0, v:shell_error)
   call assert_false(filereadable('Xentry0'))
   call assert_false(filereadable('Xentry1'))
@@ -695,5 +696,60 @@ def Test_defer_in_funcref()
   assert_false(filereadable('Xentry2'))
 enddef
 
+func Test_defer_wrong_arguments()
+  call assert_fails('defer delete()', 'E119:')
+  call assert_fails('defer FuncIndex(1)', 'E119:')
+  call assert_fails('defer delete(1, 2, 3)', 'E118:')
+  call assert_fails('defer FuncIndex(1, 2, 3)', 'E118:')
+
+  let lines =<< trim END
+      def DeferFunc0()
+        defer delete()
+      enddef
+      defcompile
+  END
+  call v9.CheckScriptFailure(lines, 'E119:')
+  let lines =<< trim END
+      def DeferFunc3()
+        defer delete(1, 2, 3)
+      enddef
+      defcompile
+  END
+  call v9.CheckScriptFailure(lines, 'E118:')
+  let lines =<< trim END
+      def DeferFunc2()
+        defer delete(1, 2)
+      enddef
+      defcompile
+  END
+  call v9.CheckScriptFailure(lines, 'E1013: Argument 1: type mismatch, expected string but got number')
+
+  def g:FuncOneArg(arg: string)
+    echo arg
+  enddef
+
+  let lines =<< trim END
+      def DeferUserFunc0()
+        defer g:FuncOneArg()
+      enddef
+      defcompile
+  END
+  call v9.CheckScriptFailure(lines, 'E119:')
+  let lines =<< trim END
+      def DeferUserFunc2()
+        defer g:FuncOneArg(1, 2)
+      enddef
+      defcompile
+  END
+  call v9.CheckScriptFailure(lines, 'E118:')
+  let lines =<< trim END
+      def DeferUserFunc1()
+        defer g:FuncOneArg(1)
+      enddef
+      defcompile
+  END
+  call v9.CheckScriptFailure(lines, 'E1013: Argument 1: type mismatch, expected string but got number')
+endfunc
+
 
 " vim: shiftwidth=2 sts=2 expandtab
--- a/src/userfunc.c
+++ b/src/userfunc.c
@@ -5608,6 +5608,7 @@ ex_call_inner(
 ex_defer_inner(
 	char_u	    *name,
 	char_u	    **arg,
+	type_T	    *type,
 	partial_T   *partial,
 	evalarg_T   *evalarg)
 {
@@ -5640,6 +5641,44 @@ ex_defer_inner(
     r = get_func_arguments(arg, evalarg, FALSE,
 					    argvars + partial_argc, &argcount);
     argcount += partial_argc;
+
+    if (r == OK)
+    {
+	if (type != NULL)
+	{
+	    // Check that the arguments are OK for the types of the funcref.
+	    r = check_argument_types(type, argvars, argcount, NULL, name);
+	}
+	else if (builtin_function(name, -1))
+	{
+	    int idx = find_internal_func(name);
+
+	    if (idx < 0)
+	    {
+		emsg_funcname(e_unknown_function_str, name);
+		r = FAIL;
+	    }
+	    else if (check_internal_func(idx, argcount) == -1)
+		r = FAIL;
+	}
+	else
+	{
+	    ufunc_T *ufunc = find_func(name, FALSE);
+
+	    // we tolerate an unknown function here, it might be defined later
+	    if (ufunc != NULL)
+	    {
+		int error = check_user_func_argcount(ufunc, argcount);
+
+		if (error != FCERR_UNKNOWN)
+		{
+		    user_func_error(error, name, NULL);
+		    r = FAIL;
+		}
+	    }
+	}
+    }
+
     if (r == FAIL)
     {
 	while (--argcount >= 0)
@@ -5839,7 +5878,7 @@ ex_call(exarg_T *eap)
     if (eap->cmdidx == CMD_defer)
     {
 	arg = startarg;
-	failed = ex_defer_inner(name, &arg, partial, &evalarg) == FAIL;
+	failed = ex_defer_inner(name, &arg, type, partial, &evalarg) == FAIL;
     }
     else
     {
--- a/src/version.c
+++ b/src/version.c
@@ -704,6 +704,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    419,
+/**/
     418,
 /**/
     417,
--- a/src/vim9cmds.c
+++ b/src/vim9cmds.c
@@ -1685,33 +1685,12 @@ compile_eval(char_u *arg, cctx_T *cctx)
 }
 
 /*
- * Get the local variable index for deferred function calls.
- * Reserve it when not done already.
- * Returns zero for failure.
- */
-    int
-get_defer_var_idx(cctx_T *cctx)
-{
-    dfunc_T	*dfunc = ((dfunc_T *)def_functions.ga_data)
-					       + cctx->ctx_ufunc->uf_dfunc_idx;
-    if (dfunc->df_defer_var_idx == 0)
-    {
-	lvar_T *lvar = reserve_local(cctx, (char_u *)"@defer@", 7,
-							    TRUE, &t_list_any);
-	if (lvar == NULL)
-	    return 0;
-	dfunc->df_defer_var_idx = lvar->lv_idx + 1;
-    }
-    return dfunc->df_defer_var_idx;
-}
-
-/*
  * Compile "defer func(arg)".
  */
     char_u *
 compile_defer(char_u *arg_start, cctx_T *cctx)
 {
-    char_u	*p;
+    char_u	*paren;
     char_u	*arg = arg_start;
     int		argcount = 0;
     int		defer_var_idx;
@@ -1720,13 +1699,13 @@ compile_defer(char_u *arg_start, cctx_T 
 
     // Get a funcref for the function name.
     // TODO: better way to find the "(".
-    p = vim_strchr(arg, '(');
-    if (p == NULL)
+    paren = vim_strchr(arg, '(');
+    if (paren == NULL)
     {
 	semsg(_(e_missing_parenthesis_str), arg);
 	return NULL;
     }
-    *p = NUL;
+    *paren = NUL;
     func_idx = find_internal_func(arg);
     if (func_idx >= 0)
 	// TODO: better type
@@ -1734,7 +1713,7 @@ compile_defer(char_u *arg_start, cctx_T 
 							   &t_func_any, FALSE);
     else if (compile_expr0(&arg, cctx) == FAIL)
 	return NULL;
-    *p = '(';
+    *paren = '(';
 
     // check for function type
     type = get_type_on_stack(cctx, 0);
@@ -1745,11 +1724,22 @@ compile_defer(char_u *arg_start, cctx_T 
     }
 
     // compile the arguments
-    arg = skipwhite(p + 1);
+    arg = skipwhite(paren + 1);
     if (compile_arguments(&arg, cctx, &argcount, CA_NOT_SPECIAL) == FAIL)
 	return NULL;
 
-    // TODO: check argument count with "type"
+    if (func_idx >= 0)
+    {
+	type2_T	*argtypes = NULL;
+	type2_T	shuffled_argtypes[MAX_FUNC_ARGS];
+
+	if (check_internal_func_args(cctx, func_idx, argcount, FALSE,
+					 &argtypes, shuffled_argtypes) == FAIL)
+	    return NULL;
+    }
+    else if (check_func_args_from_type(cctx, type, argcount, TRUE,
+							    arg_start) == FAIL)
+	return NULL;
 
     defer_var_idx = get_defer_var_idx(cctx);
     if (defer_var_idx == 0)
--- a/src/vim9instr.c
+++ b/src/vim9instr.c
@@ -1329,6 +1329,65 @@ generate_TRYCONT(cctx_T *cctx, int level
     return OK;
 }
 
+/*
+ * Check "argount" arguments and their types on the type stack.
+ * Give an error and return FAIL if something is wrong.
+ * When "method_call" is NULL no code is generated.
+ */
+    int
+check_internal_func_args(
+	cctx_T	*cctx,
+	int	func_idx,
+	int	argcount,
+	int	method_call,
+	type2_T **argtypes,
+	type2_T *shuffled_argtypes)
+{
+    garray_T	*stack = &cctx->ctx_type_stack;
+    int		argoff = check_internal_func(func_idx, argcount);
+
+    if (argoff < 0)
+	return FAIL;
+
+    if (method_call && argoff > 1)
+    {
+	isn_T	*isn = generate_instr(cctx, ISN_SHUFFLE);
+
+	if (isn  == NULL)
+	    return FAIL;
+	isn->isn_arg.shuffle.shfl_item = argcount;
+	isn->isn_arg.shuffle.shfl_up = argoff - 1;
+    }
+
+    if (argcount > 0)
+    {
+	type2_T	*typep = ((type2_T *)stack->ga_data) + stack->ga_len - argcount;
+
+	// Check the types of the arguments.
+	if (method_call && argoff > 1)
+	{
+	    int i;
+
+	    for (i = 0; i < argcount; ++i)
+		shuffled_argtypes[i] = (i < argoff - 1)
+				    ? typep[i + 1]
+				    : (i == argoff - 1) ? typep[0] : typep[i];
+	    *argtypes = shuffled_argtypes;
+	}
+	else
+	{
+	    int i;
+
+	    for (i = 0; i < argcount; ++i)
+		shuffled_argtypes[i] = typep[i];
+	    *argtypes = shuffled_argtypes;
+	}
+	if (internal_func_check_arg_types(*argtypes, func_idx, argcount,
+								 cctx) == FAIL)
+	    return FAIL;
+    }
+    return OK;
+}
 
 /*
  * Generate an ISN_BCALL instruction.
@@ -1340,8 +1399,6 @@ generate_BCALL(cctx_T *cctx, int func_id
 {
     isn_T	*isn;
     garray_T	*stack = &cctx->ctx_type_stack;
-    int		argoff;
-    type2_T	*typep;
     type2_T	*argtypes = NULL;
     type2_T	shuffled_argtypes[MAX_FUNC_ARGS];
     type2_T	*maptype = NULL;
@@ -1349,46 +1406,13 @@ generate_BCALL(cctx_T *cctx, int func_id
     type_T	*decl_type;
 
     RETURN_OK_IF_SKIP(cctx);
-    argoff = check_internal_func(func_idx, argcount);
-    if (argoff < 0)
+
+    if (check_internal_func_args(cctx, func_idx, argcount, method_call,
+					 &argtypes, shuffled_argtypes) == FAIL)
 	return FAIL;
 
-    if (method_call && argoff > 1)
-    {
-	if ((isn = generate_instr(cctx, ISN_SHUFFLE)) == NULL)
-	    return FAIL;
-	isn->isn_arg.shuffle.shfl_item = argcount;
-	isn->isn_arg.shuffle.shfl_up = argoff - 1;
-    }
-
-    if (argcount > 0)
-    {
-	// Check the types of the arguments.
-	typep = ((type2_T *)stack->ga_data) + stack->ga_len - argcount;
-	if (method_call && argoff > 1)
-	{
-	    int i;
-
-	    for (i = 0; i < argcount; ++i)
-		shuffled_argtypes[i] = (i < argoff - 1)
-			    ? typep[i + 1]
-				  : (i == argoff - 1) ? typep[0] : typep[i];
-	    argtypes = shuffled_argtypes;
-	}
-	else
-	{
-	    int i;
-
-	    for (i = 0; i < argcount; ++i)
-		shuffled_argtypes[i] = typep[i];
-	    argtypes = shuffled_argtypes;
-	}
-	if (internal_func_check_arg_types(argtypes, func_idx, argcount,
-								 cctx) == FAIL)
-	    return FAIL;
-	if (internal_func_is_map(func_idx))
-	    maptype = argtypes;
-    }
+    if (internal_func_is_map(func_idx))
+	maptype = argtypes;
 
     if ((isn = generate_instr(cctx, ISN_BCALL)) == NULL)
 	return FAIL;
@@ -1578,6 +1602,61 @@ generate_UCALL(cctx_T *cctx, char_u *nam
 }
 
 /*
+ * Check the arguments of function "type" against the types on the stack.
+ * Returns OK or FAIL;
+ */
+    int
+check_func_args_from_type(
+	cctx_T	*cctx,
+	type_T	*type,
+	int	argcount,
+	int	at_top,
+	char_u	*name)
+{
+    if (type->tt_argcount != -1)
+    {
+	int	    varargs = (type->tt_flags & TTFLAG_VARARGS) ? 1 : 0;
+
+	if (argcount < type->tt_min_argcount - varargs)
+	{
+	    emsg_funcname(e_not_enough_arguments_for_function_str, name);
+	    return FAIL;
+	}
+	if (!varargs && argcount > type->tt_argcount)
+	{
+	    emsg_funcname(e_too_many_arguments_for_function_str, name);
+	    return FAIL;
+	}
+	if (type->tt_args != NULL)
+	{
+	    int i;
+
+	    for (i = 0; i < argcount; ++i)
+	    {
+		int	offset = -argcount + i - (at_top ? 0 : 1);
+		type_T	*actual = get_type_on_stack(cctx, -1 - offset);
+		type_T	*expected;
+
+		if (varargs && i >= type->tt_argcount - 1)
+		    expected = type->tt_args[type->tt_argcount - 1]->tt_member;
+		else if (i >= type->tt_min_argcount
+					     && actual->tt_type == VAR_SPECIAL)
+		    expected = &t_any;
+		else
+		    expected = type->tt_args[i];
+		if (need_type(actual, expected, offset, i + 1,
+						    cctx, TRUE, FALSE) == FAIL)
+		{
+		    arg_type_mismatch(expected, actual, i + 1);
+		    return FAIL;
+		}
+	    }
+	}
+    }
+
+    return OK;
+}
+/*
  * Generate an ISN_PCALL instruction.
  * "type" is the type of the FuncRef.
  */
@@ -1598,47 +1677,9 @@ generate_PCALL(
 	ret_type = &t_any;
     else if (type->tt_type == VAR_FUNC || type->tt_type == VAR_PARTIAL)
     {
-	if (type->tt_argcount != -1)
-	{
-	    int	    varargs = (type->tt_flags & TTFLAG_VARARGS) ? 1 : 0;
-
-	    if (argcount < type->tt_min_argcount - varargs)
-	    {
-		emsg_funcname(e_not_enough_arguments_for_function_str, name);
-		return FAIL;
-	    }
-	    if (!varargs && argcount > type->tt_argcount)
-	    {
-		emsg_funcname(e_too_many_arguments_for_function_str, name);
-		return FAIL;
-	    }
-	    if (type->tt_args != NULL)
-	    {
-		int i;
+	if (check_func_args_from_type(cctx, type, argcount, at_top, name) == FAIL)
+	    return FAIL;
 
-		for (i = 0; i < argcount; ++i)
-		{
-		    int	    offset = -argcount + i - (at_top ? 0 : 1);
-		    type_T *actual = get_type_on_stack(cctx, -1 - offset);
-		    type_T *expected;
-
-		    if (varargs && i >= type->tt_argcount - 1)
-			expected = type->tt_args[
-					     type->tt_argcount - 1]->tt_member;
-		    else if (i >= type->tt_min_argcount
-					     && actual->tt_type == VAR_SPECIAL)
-			expected = &t_any;
-		    else
-			expected = type->tt_args[i];
-		    if (need_type(actual, expected, offset, i + 1,
-						    cctx, TRUE, FALSE) == FAIL)
-		    {
-			arg_type_mismatch(expected, actual, i + 1);
-			return FAIL;
-		    }
-		}
-	    }
-	}
 	ret_type = type->tt_member;
 	if (ret_type == &t_unknown)
 	    // return type not known yet, use a runtime check