To: vim_dev@googlegroups.com Subject: Patch 8.2.2306 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ Patch 8.2.2306 Problem: Vim9: when using function reference type is not checked. Solution: When using a function reference lookup the type and check the argument types. (issue #7629) Files: src/userfunc.c, src/proto/userfunc.pro, src/eval.c, src/structs.h, src/vim9type.c, src/proto/vim9type.pro, src/vim9compile.c, src/vim9execute.c, src/evalvars.c, src/evalfunc.c, src/testdir/test_vim9_func.vim *** ../vim-8.2.2305/src/userfunc.c 2021-01-04 14:09:40.053795023 +0100 --- src/userfunc.c 2021-01-06 21:36:27.686016340 +0100 *************** *** 727,773 **** * name it contains, otherwise return "name". * If "partialp" is not NULL, and "name" is of type VAR_PARTIAL also set * "partialp". */ char_u * ! deref_func_name(char_u *name, int *lenp, partial_T **partialp, int no_autoload) { dictitem_T *v; int cc; ! char_u *s; if (partialp != NULL) *partialp = NULL; cc = name[*lenp]; name[*lenp] = NUL; ! v = find_var(name, NULL, no_autoload); name[*lenp] = cc; ! if (v != NULL && v->di_tv.v_type == VAR_FUNC) { ! if (v->di_tv.vval.v_string == NULL) { ! *lenp = 0; ! return (char_u *)""; // just in case } - s = v->di_tv.vval.v_string; - *lenp = (int)STRLEN(s); - return s; - } ! if (v != NULL && v->di_tv.v_type == VAR_PARTIAL) ! { ! partial_T *pt = v->di_tv.vval.v_partial; ! if (pt == NULL) { ! *lenp = 0; ! return (char_u *)""; // just in case } - if (partialp != NULL) - *partialp = pt; - s = partial_name(pt); - *lenp = (int)STRLEN(s); - return s; } return name; --- 727,794 ---- * name it contains, otherwise return "name". * If "partialp" is not NULL, and "name" is of type VAR_PARTIAL also set * "partialp". + * If "type" is not NULL and a Vim9 script-local variable is found look up the + * type of the variable. */ char_u * ! deref_func_name( ! char_u *name, ! int *lenp, ! partial_T **partialp, ! type_T **type, ! int no_autoload) { dictitem_T *v; int cc; ! char_u *s = NULL; ! hashtab_T *ht; if (partialp != NULL) *partialp = NULL; cc = name[*lenp]; name[*lenp] = NUL; ! v = find_var(name, &ht, no_autoload); name[*lenp] = cc; ! if (v != NULL) { ! if (v->di_tv.v_type == VAR_FUNC) { ! if (v->di_tv.vval.v_string == NULL) ! { ! *lenp = 0; ! return (char_u *)""; // just in case ! } ! s = v->di_tv.vval.v_string; ! *lenp = (int)STRLEN(s); } ! if (v->di_tv.v_type == VAR_PARTIAL) ! { ! partial_T *pt = v->di_tv.vval.v_partial; ! if (pt == NULL) ! { ! *lenp = 0; ! return (char_u *)""; // just in case ! } ! if (partialp != NULL) ! *partialp = pt; ! s = partial_name(pt); ! *lenp = (int)STRLEN(s); ! } ! ! if (s != NULL) { ! if (type != NULL && ht == get_script_local_ht()) ! { ! svar_T *sv = find_typval_in_script(&v->di_tv); ! ! if (sv != NULL) ! *type = sv->sv_type; ! } ! return s; } } return name; *************** *** 2387,2392 **** --- 2408,2421 ---- } } + if (error == FCERR_NONE && funcexe->check_type != NULL && funcexe->evaluate) + { + // Check that the argument types are OK for the types of the funcref. + if (check_argument_types(funcexe->check_type, argvars, argcount, + name) == FAIL) + error = FCERR_OTHER; + } + if (error == FCERR_NONE && funcexe->evaluate) { char_u *rfname = fname; *************** *** 2629,2635 **** int skip, // only find the end, don't evaluate int flags, funcdict_T *fdp, // return: info about dictionary used ! partial_T **partial) // return: partial of a FuncRef { char_u *name = NULL; char_u *start; --- 2658,2665 ---- int skip, // only find the end, don't evaluate int flags, funcdict_T *fdp, // return: info about dictionary used ! partial_T **partial, // return: partial of a FuncRef ! type_T **type) // return: type of funcref if not NULL { char_u *name = NULL; char_u *start; *************** *** 2733,2739 **** if (lv.ll_exp_name != NULL) { len = (int)STRLEN(lv.ll_exp_name); ! name = deref_func_name(lv.ll_exp_name, &len, partial, flags & TFN_NO_AUTOLOAD); if (name == lv.ll_exp_name) name = NULL; --- 2763,2769 ---- if (lv.ll_exp_name != NULL) { len = (int)STRLEN(lv.ll_exp_name); ! name = deref_func_name(lv.ll_exp_name, &len, partial, type, flags & TFN_NO_AUTOLOAD); if (name == lv.ll_exp_name) name = NULL; *************** *** 2741,2747 **** else if (!(flags & TFN_NO_DEREF)) { len = (int)(end - *pp); ! name = deref_func_name(*pp, &len, partial, flags & TFN_NO_AUTOLOAD); if (name == *pp) name = NULL; } --- 2771,2778 ---- else if (!(flags & TFN_NO_DEREF)) { len = (int)(end - *pp); ! name = deref_func_name(*pp, &len, partial, type, ! flags & TFN_NO_AUTOLOAD); if (name == *pp) name = NULL; } *************** *** 3064,3070 **** else { name = trans_function_name(&p, &is_global, eap->skip, ! TFN_NO_AUTOLOAD, &fudi, NULL); paren = (vim_strchr(p, '(') != NULL); if (name == NULL && (fudi.fd_dict == NULL || !paren) && !eap->skip) { --- 3095,3101 ---- else { name = trans_function_name(&p, &is_global, eap->skip, ! TFN_NO_AUTOLOAD, &fudi, NULL, NULL); paren = (vim_strchr(p, '(') != NULL); if (name == NULL && (fudi.fd_dict == NULL || !paren) && !eap->skip) { *************** *** 3479,3485 **** if (*p == '!') p = skipwhite(p + 1); p += eval_fname_script(p); ! vim_free(trans_function_name(&p, NULL, TRUE, 0, NULL, NULL)); if (*skipwhite(p) == '(') { if (nesting == MAX_FUNC_NESTING - 1) --- 3510,3517 ---- if (*p == '!') p = skipwhite(p + 1); p += eval_fname_script(p); ! vim_free(trans_function_name(&p, NULL, TRUE, 0, NULL, ! NULL, NULL)); if (*skipwhite(p) == '(') { if (nesting == MAX_FUNC_NESTING - 1) *************** *** 3616,3622 **** { hashtab_T *ht; ! v = find_var(name, &ht, FALSE); if (v != NULL && v->di_tv.v_type == VAR_FUNC) { emsg_funcname(N_("E707: Function name conflicts with variable: %s"), --- 3648,3654 ---- { hashtab_T *ht; ! v = find_var(name, &ht, TRUE); if (v != NULL && v->di_tv.v_type == VAR_FUNC) { emsg_funcname(N_("E707: Function name conflicts with variable: %s"), *************** *** 4007,4013 **** flag = TFN_INT | TFN_QUIET | TFN_NO_AUTOLOAD; if (no_deref) flag |= TFN_NO_DEREF; ! p = trans_function_name(&nm, &is_global, FALSE, flag, NULL, NULL); nm = skipwhite(nm); // Only accept "funcname", "funcname ", "funcname (..." and --- 4039,4045 ---- flag = TFN_INT | TFN_QUIET | TFN_NO_AUTOLOAD; if (no_deref) flag |= TFN_NO_DEREF; ! p = trans_function_name(&nm, &is_global, FALSE, flag, NULL, NULL, NULL); nm = skipwhite(nm); // Only accept "funcname", "funcname ", "funcname (..." and *************** *** 4027,4033 **** int is_global = FALSE; p = trans_function_name(&nm, &is_global, FALSE, ! TFN_INT|TFN_QUIET, NULL, NULL); if (p != NULL && *nm == NUL && (!check || translated_function_exists(p, is_global))) --- 4059,4065 ---- int is_global = FALSE; p = trans_function_name(&nm, &is_global, FALSE, ! TFN_INT|TFN_QUIET, NULL, NULL, NULL); if (p != NULL && *nm == NUL && (!check || translated_function_exists(p, is_global))) *************** *** 4097,4103 **** int is_global = FALSE; p = eap->arg; ! name = trans_function_name(&p, &is_global, eap->skip, 0, &fudi, NULL); vim_free(fudi.fd_newkey); if (name == NULL) { --- 4129,4136 ---- int is_global = FALSE; p = eap->arg; ! name = trans_function_name(&p, &is_global, eap->skip, 0, &fudi, ! NULL, NULL); vim_free(fudi.fd_newkey); if (name == NULL) { *************** *** 4328,4333 **** --- 4361,4367 ---- funcdict_T fudi; partial_T *partial = NULL; evalarg_T evalarg; + type_T *type = NULL; fill_evalarg_from_eap(&evalarg, eap, eap->skip); if (eap->skip) *************** *** 4343,4350 **** return; } ! tofree = trans_function_name(&arg, NULL, eap->skip, ! TFN_INT, &fudi, &partial); if (fudi.fd_newkey != NULL) { // Still need to give an error message for missing key. --- 4377,4384 ---- return; } ! tofree = trans_function_name(&arg, NULL, eap->skip, TFN_INT, ! &fudi, &partial, in_vim9script() ? &type : NULL); if (fudi.fd_newkey != NULL) { // Still need to give an error message for missing key. *************** *** 4363,4370 **** // contents. For VAR_PARTIAL get its partial, unless we already have one // from trans_function_name(). len = (int)STRLEN(tofree); ! name = deref_func_name(tofree, &len, ! partial != NULL ? NULL : &partial, FALSE); // Skip white space to allow ":call func ()". Not good, but required for // backward compatibility. --- 4397,4404 ---- // contents. For VAR_PARTIAL get its partial, unless we already have one // from trans_function_name(). len = (int)STRLEN(tofree); ! name = deref_func_name(tofree, &len, partial != NULL ? NULL : &partial, ! in_vim9script() && type == NULL ? &type : NULL, FALSE); // Skip white space to allow ":call func ()". Not good, but required for // backward compatibility. *************** *** 4416,4421 **** --- 4450,4456 ---- funcexe.evaluate = !eap->skip; funcexe.partial = partial; funcexe.selfdict = fudi.fd_dict; + funcexe.check_type = type; if (get_func_tv(name, -1, &rettv, &arg, &evalarg, &funcexe) == FAIL) { failed = TRUE; *** ../vim-8.2.2305/src/proto/userfunc.pro 2021-01-04 14:09:40.053795023 +0100 --- src/proto/userfunc.pro 2021-01-06 21:37:44.985807661 +0100 *************** *** 4,10 **** char_u *get_lambda_name(void); char_u *register_cfunc(cfunc_T cb, cfunc_free_T cb_free, void *state); int get_lambda_tv(char_u **arg, typval_T *rettv, int types_optional, evalarg_T *evalarg); ! char_u *deref_func_name(char_u *name, int *lenp, partial_T **partialp, int no_autoload); void emsg_funcname(char *ermsg, char_u *name); int get_func_tv(char_u *name, int len, typval_T *rettv, char_u **arg, evalarg_T *evalarg, funcexe_T *funcexe); char_u *fname_trans_sid(char_u *name, char_u *fname_buf, char_u **tofree, int *error); --- 4,10 ---- char_u *get_lambda_name(void); char_u *register_cfunc(cfunc_T cb, cfunc_free_T cb_free, void *state); int get_lambda_tv(char_u **arg, typval_T *rettv, int types_optional, evalarg_T *evalarg); ! char_u *deref_func_name(char_u *name, int *lenp, partial_T **partialp, type_T **type, int no_autoload); void emsg_funcname(char *ermsg, char_u *name); int get_func_tv(char_u *name, int len, typval_T *rettv, char_u **arg, evalarg_T *evalarg, funcexe_T *funcexe); char_u *fname_trans_sid(char_u *name, char_u *fname_buf, char_u **tofree, int *error); *************** *** 31,37 **** void user_func_error(int error, char_u *name); int call_func(char_u *funcname, int len, typval_T *rettv, int argcount_in, typval_T *argvars_in, funcexe_T *funcexe); char_u *printable_func_name(ufunc_T *fp); ! char_u *trans_function_name(char_u **pp, int *is_global, int skip, int flags, funcdict_T *fdp, partial_T **partial); char_u *untrans_function_name(char_u *name); void list_functions(regmatch_T *regmatch); ufunc_T *define_function(exarg_T *eap, char_u *name_arg); --- 31,37 ---- void user_func_error(int error, char_u *name); int call_func(char_u *funcname, int len, typval_T *rettv, int argcount_in, typval_T *argvars_in, funcexe_T *funcexe); char_u *printable_func_name(ufunc_T *fp); ! char_u *trans_function_name(char_u **pp, int *is_global, int skip, int flags, funcdict_T *fdp, partial_T **partial, type_T **type); char_u *untrans_function_name(char_u *name); void list_functions(regmatch_T *regmatch); ufunc_T *define_function(exarg_T *eap, char_u *name_arg); *** ../vim-8.2.2305/src/eval.c 2021-01-05 22:08:17.205806639 +0100 --- src/eval.c 2021-01-06 21:25:44.895464060 +0100 *************** *** 721,728 **** #ifdef FEAT_FOLDING /* ! * Evaluate 'foldexpr'. Returns the foldlevel, and any character preceding ! * it in "*cp". Doesn't give error messages. */ int eval_foldexpr(char_u *arg, int *cp) --- 721,730 ---- #ifdef FEAT_FOLDING /* ! * Evaluate "arg", which is 'foldexpr'. ! * Note: caller must set "curwin" to match "arg". ! * Returns the foldlevel, and any character preceding it in "*cp". Doesn't ! * give error messages. */ int eval_foldexpr(char_u *arg, int *cp) *************** *** 809,814 **** --- 811,817 ---- int len; hashtab_T *ht = NULL; int quiet = flags & GLV_QUIET; + int writing; // Clear everything in "lp". CLEAR_POINTER(lp); *************** *** 882,891 **** cc = *p; *p = NUL; ! // Only pass &ht when we would write to the variable, it prevents autoload ! // as well. ! v = find_var(lp->ll_name, (flags & GLV_READ_ONLY) ? NULL : &ht, ! flags & GLV_NO_AUTOLOAD); if (v == NULL && !quiet) semsg(_(e_undefined_variable_str), lp->ll_name); *p = cc; --- 885,894 ---- cc = *p; *p = NUL; ! // When we would write to the variable pass &ht and prevent autoload. ! writing = !(flags & GLV_READ_ONLY); ! v = find_var(lp->ll_name, writing ? &ht : NULL, ! (flags & GLV_NO_AUTOLOAD) || writing); if (v == NULL && !quiet) semsg(_(e_undefined_variable_str), lp->ll_name); *p = cc; *************** *** 1972,1984 **** int len = name_len; partial_T *partial; int ret = OK; if (!evaluate) check_vars(s, len); // If "s" is the name of a variable of type VAR_FUNC // use its contents. ! s = deref_func_name(s, &len, &partial, !evaluate); // Need to make a copy, in case evaluating the arguments makes // the name invalid. --- 1975,1989 ---- int len = name_len; partial_T *partial; int ret = OK; + type_T *type = NULL; if (!evaluate) check_vars(s, len); // If "s" is the name of a variable of type VAR_FUNC // use its contents. ! s = deref_func_name(s, &len, &partial, ! in_vim9script() ? &type : NULL, !evaluate); // Need to make a copy, in case evaluating the arguments makes // the name invalid. *************** *** 1996,2001 **** --- 2001,2007 ---- funcexe.evaluate = evaluate; funcexe.partial = partial; funcexe.basetv = basetv; + funcexe.check_type = type; ret = get_func_tv(s, len, rettv, arg, evalarg, &funcexe); } vim_free(s); *** ../vim-8.2.2305/src/structs.h 2021-01-04 12:41:49.507891351 +0100 --- src/structs.h 2021-01-06 20:23:54.283683093 +0100 *************** *** 1944,1949 **** --- 1944,1950 ---- partial_T *partial; // for extra arguments dict_T *selfdict; // Dictionary for "self" typval_T *basetv; // base for base->method() + type_T *check_type; // type from funcref or NULL } funcexe_T; /* *** ../vim-8.2.2305/src/vim9type.c 2021-01-03 13:09:48.226390595 +0100 --- src/vim9type.c 2021-01-06 21:11:59.509421778 +0100 *************** *** 528,533 **** --- 528,573 ---- } /* + * Check that the arguments of "type" match "argvars[argcount]". + * Return OK/FAIL. + */ + int + check_argument_types(type_T *type, typval_T *argvars, int argcount, char_u *name) + { + int varargs = (type->tt_flags & TTFLAG_VARARGS) ? 1 : 0; + int i; + + if (type->tt_type != VAR_FUNC && type->tt_type != VAR_PARTIAL) + return OK; // just in case + if (argcount < type->tt_min_argcount - varargs) + { + semsg(_(e_toofewarg), name); + return FAIL; + } + if (!varargs && type->tt_argcount >= 0 && argcount > type->tt_argcount) + { + semsg(_(e_toomanyarg), name); + return FAIL; + } + if (type->tt_args == NULL) + return OK; // cannot check + + + for (i = 0; i < argcount; ++i) + { + type_T *expected; + + if (varargs && i >= type->tt_argcount - 1) + expected = type->tt_args[type->tt_argcount - 1]->tt_member; + else + expected = type->tt_args[i]; + if (check_typval_type(expected, &argvars[i], i + 1) == FAIL) + return FAIL; + } + return OK; + } + + /* * Skip over a type definition and return a pointer to just after it. * When "optional" is TRUE then a leading "?" is accepted. */ *** ../vim-8.2.2305/src/proto/vim9type.pro 2020-12-26 20:09:11.282465257 +0100 --- src/proto/vim9type.pro 2021-01-06 20:48:43.780691288 +0100 *************** *** 16,21 **** --- 16,22 ---- void arg_type_mismatch(type_T *expected, type_T *actual, int argidx); int check_type(type_T *expected, type_T *actual, int give_msg, int argidx); int check_arg_type(type_T *expected, type_T *actual, int argidx); + int check_argument_types(type_T *type, typval_T *argvars, int argcount, char_u *name); char_u *skip_type(char_u *start, int optional); type_T *parse_type(char_u **arg, garray_T *type_gap, int give_error); int equal_type(type_T *type1, type_T *type2); *** ../vim-8.2.2305/src/vim9compile.c 2021-01-05 22:08:17.201806649 +0100 --- src/vim9compile.c 2021-01-06 21:12:38.253324361 +0100 *************** *** 1790,1798 **** stack->ga_len + offset]; type_T *expected; ! if (varargs && i >= type->tt_min_argcount - 1) expected = type->tt_args[ ! type->tt_min_argcount - 1]->tt_member; else expected = type->tt_args[i]; if (need_type(actual, expected, offset, --- 1790,1798 ---- stack->ga_len + offset]; type_T *expected; ! if (varargs && i >= type->tt_argcount - 1) expected = type->tt_args[ ! type->tt_argcount - 1]->tt_member; else expected = type->tt_args[i]; if (need_type(actual, expected, offset, *** ../vim-8.2.2305/src/vim9execute.c 2021-01-05 20:58:20.851037690 +0100 --- src/vim9execute.c 2021-01-06 21:37:10.413920902 +0100 *************** *** 3423,3429 **** } else fname = trans_function_name(&arg, &is_global, FALSE, ! TFN_INT | TFN_QUIET | TFN_NO_AUTOLOAD, NULL, NULL); if (fname == NULL) { semsg(_(e_invarg2), eap->arg); --- 3423,3429 ---- } else fname = trans_function_name(&arg, &is_global, FALSE, ! TFN_INT | TFN_QUIET | TFN_NO_AUTOLOAD, NULL, NULL, NULL); if (fname == NULL) { semsg(_(e_invarg2), eap->arg); *** ../vim-8.2.2305/src/evalvars.c 2021-01-04 13:37:50.251107339 +0100 --- src/evalvars.c 2021-01-06 21:23:01.983837281 +0100 *************** *** 2639,2646 **** * Find variable "name" in the list of variables. * Return a pointer to it if found, NULL if not found. * Careful: "a:0" variables don't have a name. ! * When "htp" is not NULL we are writing to the variable, set "htp" to the ! * hashtab_T used. */ dictitem_T * find_var(char_u *name, hashtab_T **htp, int no_autoload) --- 2639,2645 ---- * Find variable "name" in the list of variables. * Return a pointer to it if found, NULL if not found. * Careful: "a:0" variables don't have a name. ! * When "htp" is not NULL set "htp" to the hashtab_T used. */ dictitem_T * find_var(char_u *name, hashtab_T **htp, int no_autoload) *************** *** 2654,2665 **** *htp = ht; if (ht == NULL) return NULL; ! ret = find_var_in_ht(ht, *name, varname, no_autoload || htp != NULL); if (ret != NULL) return ret; // Search in parent scope for lambda ! ret = find_var_in_scoped_ht(name, no_autoload || htp != NULL); if (ret != NULL) return ret; --- 2653,2664 ---- *htp = ht; if (ht == NULL) return NULL; ! ret = find_var_in_ht(ht, *name, varname, no_autoload); if (ret != NULL) return ret; // Search in parent scope for lambda ! ret = find_var_in_scoped_ht(name, no_autoload); if (ret != NULL) return ret; *************** *** 2669,2676 **** ht = get_script_local_ht(); if (ht != NULL) { ! ret = find_var_in_ht(ht, *name, varname, ! no_autoload || htp != NULL); if (ret != NULL) { if (htp != NULL) --- 2668,2674 ---- ht = get_script_local_ht(); if (ht != NULL) { ! ret = find_var_in_ht(ht, *name, varname, no_autoload); if (ret != NULL) { if (htp != NULL) *** ../vim-8.2.2305/src/evalfunc.c 2021-01-03 19:51:01.392063235 +0100 --- src/evalfunc.c 2021-01-06 21:37:01.177941528 +0100 *************** *** 3497,3503 **** { name = s; trans_name = trans_function_name(&name, &is_global, FALSE, ! TFN_INT | TFN_QUIET | TFN_NO_AUTOLOAD | TFN_NO_DEREF, NULL, NULL); if (*name != NUL) s = NULL; } --- 3497,3504 ---- { name = s; trans_name = trans_function_name(&name, &is_global, FALSE, ! TFN_INT | TFN_QUIET | TFN_NO_AUTOLOAD | TFN_NO_DEREF, ! NULL, NULL, NULL); if (*name != NUL) s = NULL; } *** ../vim-8.2.2305/src/testdir/test_vim9_func.vim 2021-01-04 14:09:40.053795023 +0100 --- src/testdir/test_vim9_func.vim 2021-01-06 21:29:46.026917452 +0100 *************** *** 579,584 **** --- 579,600 ---- CheckScriptFailure(head + ["funcMap['func']('str', 123)"] + tail, 'E119:') CheckScriptFailure(head + ["funcMap['func']('str', 123, [1], 4)"] + tail, 'E118:') + + var lines =<< trim END + vim9script + var Ref: func(number): any + Ref = (j) => !j + echo Ref(false) + END + CheckScriptFailure(lines, 'E1013: Argument 1: type mismatch, expected number but got bool', 4) + + lines =<< trim END + vim9script + var Ref: func(number): any + Ref = (j) => !j + call Ref(false) + END + CheckScriptFailure(lines, 'E1013: Argument 1: type mismatch, expected number but got bool', 4) enddef def Test_call_lambda_args() *** ../vim-8.2.2305/src/version.c 2021-01-05 22:08:17.205806639 +0100 --- src/version.c 2021-01-06 21:58:11.602231840 +0100 *************** *** 752,753 **** --- 752,755 ---- { /* Add new patch number below this line */ + /**/ + 2306, /**/ -- Common sense is what tells you that the world is flat. /// Bram Moolenaar -- Bram@Moolenaar.net -- http://www.Moolenaar.net \\\ /// sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ \\\ \\\ an exciting new programming language -- http://www.Zimbu.org /// \\\ help me help AIDS victims -- http://ICCF-Holland.org ///