To: vim_dev@googlegroups.com Subject: Patch 9.0.0419 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ 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 -- Bram@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 ///