Patch 9.0.0419
Problem:    The :defer command does not check the function argument count and
            types.
Solution:   Check the function arguments when adding a deferred function.
Files:      src/userfunc.c, src/vim9instr.c, src/proto/vim9instr.pro,
            src/vim9cmds.c, src/testdir/test_user_func.vim


*** ../vim-9.0.0418/src/userfunc.c      2022-09-07 21:30:40.139379052 +0100
--- src/userfunc.c      2022-09-08 18:11:09.503285696 +0100
***************
*** 5608,5613 ****
--- 5608,5614 ----
  ex_defer_inner(
        char_u      *name,
        char_u      **arg,
+       type_T      *type,
        partial_T   *partial,
        evalarg_T   *evalarg)
  {
***************
*** 5640,5645 ****
--- 5641,5684 ----
      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,5845 ****
      if (eap->cmdidx == CMD_defer)
      {
        arg = startarg;
!       failed = ex_defer_inner(name, &arg, partial, &evalarg) == FAIL;
      }
      else
      {
--- 5878,5884 ----
      if (eap->cmdidx == CMD_defer)
      {
        arg = startarg;
!       failed = ex_defer_inner(name, &arg, type, partial, &evalarg) == FAIL;
      }
      else
      {
*** ../vim-9.0.0418/src/vim9instr.c     2022-09-04 12:47:15.414692249 +0100
--- src/vim9instr.c     2022-09-08 19:39:51.440072875 +0100
***************
*** 1329,1361 ****
      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;
!     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;
  
-     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)
            return FAIL;
        isn->isn_arg.shuffle.shfl_item = argcount;
        isn->isn_arg.shuffle.shfl_up = argoff - 1;
--- 1329,1359 ----
      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;
***************
*** 1363,1379 ****
  
      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
        {
--- 1361,1378 ----
  
      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
        {
***************
*** 1381,1394 ****
  
            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 ((isn = generate_instr(cctx, ISN_BCALL)) == NULL)
        return FAIL;
--- 1380,1418 ----
  
            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.
+  * "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;
***************
*** 1578,1583 ****
--- 1602,1662 ----
  }
  
  /*
+  * 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,1644 ****
        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;
  
-               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
--- 1677,1685 ----
        ret_type = &t_any;
      else if (type->tt_type == VAR_FUNC || type->tt_type == VAR_PARTIAL)
      {
!       if (check_func_args_from_type(cctx, type, argcount, at_top, name) == 
FAIL)
!           return FAIL;
  
        ret_type = type->tt_member;
        if (ret_type == &t_unknown)
            // return type not known yet, use a runtime check
*** ../vim-9.0.0418/src/proto/vim9instr.pro     2022-09-03 21:35:50.184158219 
+0100
--- src/proto/vim9instr.pro     2022-09-08 19:32:25.669422042 +0100
***************
*** 46,56 ****
--- 46,59 ----
  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);
*** ../vim-9.0.0418/src/vim9cmds.c      2022-09-04 18:08:00.327693560 +0100
--- src/vim9cmds.c      2022-09-08 19:35:10.052887004 +0100
***************
*** 1706,1738 ****
  }
  
  /*
-  * 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    *arg = arg_start;
      int               argcount = 0;
      int               defer_var_idx;
--- 1706,1717 ----
  }
  
  /*
   * Compile "defer func(arg)".
   */
      char_u *
  compile_defer(char_u *arg_start, cctx_T *cctx)
  {
!     char_u    *paren;
      char_u    *arg = arg_start;
      int               argcount = 0;
      int               defer_var_idx;
***************
*** 1741,1753 ****
  
      // Get a funcref for the function name.
      // TODO: better way to find the "(".
!     p = vim_strchr(arg, '(');
!     if (p == NULL)
      {
        semsg(_(e_missing_parenthesis_str), arg);
        return NULL;
      }
!     *p = NUL;
      func_idx = find_internal_func(arg);
      if (func_idx >= 0)
        // TODO: better type
--- 1720,1732 ----
  
      // Get a funcref for the function name.
      // TODO: better way to find the "(".
!     paren = vim_strchr(arg, '(');
!     if (paren == NULL)
      {
        semsg(_(e_missing_parenthesis_str), arg);
        return NULL;
      }
!     *paren = NUL;
      func_idx = find_internal_func(arg);
      if (func_idx >= 0)
        // TODO: better type
***************
*** 1755,1761 ****
                                                           &t_func_any, FALSE);
      else if (compile_expr0(&arg, cctx) == FAIL)
        return NULL;
!     *p = '(';
  
      // check for function type
      type = get_type_on_stack(cctx, 0);
--- 1734,1740 ----
                                                           &t_func_any, FALSE);
      else if (compile_expr0(&arg, cctx) == FAIL)
        return NULL;
!     *paren = '(';
  
      // check for function type
      type = get_type_on_stack(cctx, 0);
***************
*** 1766,1776 ****
      }
  
      // compile the arguments
!     arg = skipwhite(p + 1);
      if (compile_arguments(&arg, cctx, &argcount, CA_NOT_SPECIAL) == FAIL)
        return NULL;
  
!     // TODO: check argument count with "type"
  
      defer_var_idx = get_defer_var_idx(cctx);
      if (defer_var_idx == 0)
--- 1745,1766 ----
      }
  
      // compile the arguments
!     arg = skipwhite(paren + 1);
      if (compile_arguments(&arg, cctx, &argcount, CA_NOT_SPECIAL) == FAIL)
        return NULL;
  
!     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)
*** ../vim-9.0.0418/src/testdir/test_user_func.vim      2022-09-07 
17:28:05.849865176 +0100
--- src/testdir/test_user_func.vim      2022-09-08 19:43:53.927437408 +0100
***************
*** 5,10 ****
--- 5,11 ----
  
  source check.vim
  source shared.vim
+ import './vim9.vim' as v9
  
  func Table(title, ...)
    let ret = a:title
***************
*** 619,625 ****
        DeferLevelOne()
    END
    call writefile(lines, 'XdeferQuitall', 'D')
!   let res = system(GetVimCommandClean() .. ' -X -S XdeferQuitall')
    call assert_equal(0, v:shell_error)
    call assert_false(filereadable('XQuitallOne'))
    call assert_false(filereadable('XQuitallTwo'))
--- 620,626 ----
        DeferLevelOne()
    END
    call writefile(lines, 'XdeferQuitall', 'D')
!   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,647 ****
        call Test_defer_in_funcref()
    END
    call writefile(lines, 'XdeferQuitallExpr', 'D')
!   let res = system(GetVimCommandClean() .. ' -X -S XdeferQuitallExpr')
    call assert_equal(0, v:shell_error)
    call assert_false(filereadable('Xentry0'))
    call assert_false(filereadable('Xentry1'))
--- 642,648 ----
        call Test_defer_in_funcref()
    END
    call writefile(lines, 'XdeferQuitallExpr', 'D')
!   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,699 ****
--- 696,755 ----
    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
*** ../vim-9.0.0418/src/version.c       2022-09-08 16:39:16.912140162 +0100
--- src/version.c       2022-09-08 19:49:53.546558746 +0100
***************
*** 705,706 ****
--- 705,708 ----
  {   /* Add new patch number below this line */
+ /**/
+     419,
  /**/

-- 
I am also told that there is a logical proof out there somewhere
that demonstrates that there is no task which duct tape cannot handle.
                                        -- Paul Brannan

 /// Bram Moolenaar -- b...@moolenaar.net -- http://www.Moolenaar.net   \\\
///                                                                      \\\
\\\        sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ ///
 \\\            help me help AIDS victims -- http://ICCF-Holland.org    ///

-- 
-- 
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

--- 
You received this message because you are subscribed to the Google Groups 
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to vim_dev+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/vim_dev/20220908185346.E3B5A1C0B04%40moolenaar.net.

Raspunde prin e-mail lui