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 -- Br...@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 ///