]> granicus.if.org Git - vim/commitdiff
patch 9.0.0419: the :defer command does not check the function arguments v9.0.0419
authorBram Moolenaar <Bram@vim.org>
Thu, 8 Sep 2022 18:51:45 +0000 (19:51 +0100)
committerBram Moolenaar <Bram@vim.org>
Thu, 8 Sep 2022 18:51:45 +0000 (19:51 +0100)
Problem:    The :defer command does not check the function argument count and
            types.
Solution:   Check the function arguments when adding a deferred function.

src/proto/vim9instr.pro
src/testdir/test_user_func.vim
src/userfunc.c
src/version.c
src/vim9cmds.c
src/vim9instr.c

index b0f3aa476263c895c0a4db6f4a0dcdd6a27e207f..1fea52b9803b68da7307fc93d0c30a418e6d1db1 100644 (file)
@@ -46,11 +46,14 @@ int generate_JUMP(cctx_T *cctx, jumpwhen_T when, int where);
 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);
index 6b17534406934e59660d5bd98f88874757acefac..4721175e770f1e279eb5d46384d6807028c755f7 100644 (file)
@@ -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
index 7b6034aaff6dab60bfd0cfe00fa53b2bbb71e696..1412caa8e08ff1fc32ee887d5117f20760acc77b 100644 (file)
@@ -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
     {
index c3d1ad520fa2b9933ec4d2d07db48348e007c34c..81de0b7d2f1ce4ce5a2f9f4bad50a974f1c623bd 100644 (file)
@@ -703,6 +703,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    419,
 /**/
     418,
 /**/
index c294d70a85193d508d81687c6ef6e966bbe5fa87..080674d2c941a14d8694e1b3f298b0e296b935c6 100644 (file)
@@ -1684,34 +1684,13 @@ compile_eval(char_u *arg, cctx_T *cctx)
     return skipwhite(p);
 }
 
-/*
- * 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 *cctx)
 
     // 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 *cctx)
                                                           &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 *cctx)
     }
 
     // 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)
index 34d4ae33f39781e92d02730affae9071cb94b228..6a387fa51a59dabefde517208dbee6509f98aee2 100644 (file)
@@ -1329,33 +1329,31 @@ generate_TRYCONT(cctx_T *cctx, int levels, int where)
     return OK;
 }
 
-
 /*
- * Generate an ISN_BCALL instruction.
- * "method_call" is TRUE for "value->method()"
- * Return FAIL if the number of arguments is wrong.
+ * 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
-generate_BCALL(cctx_T *cctx, int func_idx, int argcount, int method_call)
+check_internal_func_args(
+       cctx_T  *cctx,
+       int     func_idx,
+       int     argcount,
+       int     method_call,
+       type2_T **argtypes,
+       type2_T *shuffled_argtypes)
 {
-    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;
-    type_T     *type;
-    type_T     *decl_type;
+    int                argoff = check_internal_func(func_idx, argcount);
 
-    RETURN_OK_IF_SKIP(cctx);
-    argoff = check_internal_func(func_idx, argcount);
     if (argoff < 0)
        return FAIL;
 
     if (method_call && argoff > 1)
     {
-       if ((isn = generate_instr(cctx, ISN_SHUFFLE)) == NULL)
+       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;
@@ -1363,17 +1361,18 @@ generate_BCALL(cctx_T *cctx, int func_idx, int argcount, int method_call)
 
     if (argcount > 0)
     {
+       type2_T *typep = ((type2_T *)stack->ga_data) + stack->ga_len - argcount;
+
        // 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;
+                                   ? typep[i + 1]
+                                   : (i == argoff - 1) ? typep[0] : typep[i];
+           *argtypes = shuffled_argtypes;
        }
        else
        {
@@ -1381,14 +1380,39 @@ generate_BCALL(cctx_T *cctx, int func_idx, int argcount, int method_call)
 
            for (i = 0; i < argcount; ++i)
                shuffled_argtypes[i] = typep[i];
-           argtypes = shuffled_argtypes;
+           *argtypes = shuffled_argtypes;
        }
-       if (internal_func_check_arg_types(argtypes, func_idx, argcount,
+       if (internal_func_check_arg_types(*argtypes, func_idx, argcount,
                                                                 cctx) == FAIL)
            return FAIL;
-       if (internal_func_is_map(func_idx))
-           maptype = argtypes;
     }
+    return OK;
+}
+
+/*
+ * Generate an ISN_BCALL instruction.
+ * "method_call" is TRUE for "value->method()"
+ * Return FAIL if the number of arguments is wrong.
+ */
+    int
+generate_BCALL(cctx_T *cctx, int func_idx, int argcount, int method_call)
+{
+    isn_T      *isn;
+    garray_T   *stack = &cctx->ctx_type_stack;
+    type2_T    *argtypes = NULL;
+    type2_T    shuffled_argtypes[MAX_FUNC_ARGS];
+    type2_T    *maptype = NULL;
+    type_T     *type;
+    type_T     *decl_type;
+
+    RETURN_OK_IF_SKIP(cctx);
+
+    if (check_internal_func_args(cctx, func_idx, argcount, method_call,
+                                        &argtypes, shuffled_argtypes) == FAIL)
+       return FAIL;
+
+    if (internal_func_is_map(func_idx))
+       maptype = argtypes;
 
     if ((isn = generate_instr(cctx, ISN_BCALL)) == NULL)
        return FAIL;
@@ -1577,6 +1601,61 @@ generate_UCALL(cctx_T *cctx, char_u *name, int argcount)
     return push_type_stack(cctx, &t_any);
 }
 
+/*
+ * 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