# HG changeset patch # User Bram Moolenaar # Date 1662663604 -7200 # Node ID d1c04b4dc60d5b2899905b90312662bc2a73ba59 # Parent 27820ff1eaf4214116e4166dbd6c2a7d618f4a2f patch 9.0.0419: the :defer command does not check the function arguments Commit: https://github.com/vim/vim/commit/169003289fb4b2ad18fd7f5807e0d05efff0be85 Author: Bram Moolenaar 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. diff --git a/src/proto/vim9instr.pro b/src/proto/vim9instr.pro --- 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); diff --git a/src/testdir/test_user_func.vim b/src/testdir/test_user_func.vim --- 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 diff --git a/src/userfunc.c b/src/userfunc.c --- 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 { diff --git a/src/version.c b/src/version.c --- 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, diff --git a/src/vim9cmds.c b/src/vim9cmds.c --- 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) diff --git a/src/vim9instr.c b/src/vim9instr.c --- 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