1
0
forked from aniani/vim

patch 8.2.2306: Vim9: when using function reference type is not checked

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)
This commit is contained in:
Bram Moolenaar
2021-01-06 21:59:39 +01:00
parent b23279d7a2
commit 32b3f82010
12 changed files with 155 additions and 55 deletions

View File

@@ -721,8 +721,10 @@ call_func_retlist(
#ifdef FEAT_FOLDING #ifdef FEAT_FOLDING
/* /*
* Evaluate 'foldexpr'. Returns the foldlevel, and any character preceding * Evaluate "arg", which is 'foldexpr'.
* it in "*cp". Doesn't give error messages. * Note: caller must set "curwin" to match "arg".
* Returns the foldlevel, and any character preceding it in "*cp". Doesn't
* give error messages.
*/ */
int int
eval_foldexpr(char_u *arg, int *cp) eval_foldexpr(char_u *arg, int *cp)
@@ -809,6 +811,7 @@ get_lval(
int len; int len;
hashtab_T *ht = NULL; hashtab_T *ht = NULL;
int quiet = flags & GLV_QUIET; int quiet = flags & GLV_QUIET;
int writing;
// Clear everything in "lp". // Clear everything in "lp".
CLEAR_POINTER(lp); CLEAR_POINTER(lp);
@@ -882,10 +885,10 @@ get_lval(
cc = *p; cc = *p;
*p = NUL; *p = NUL;
// Only pass &ht when we would write to the variable, it prevents autoload // When we would write to the variable pass &ht and prevent autoload.
// as well. writing = !(flags & GLV_READ_ONLY);
v = find_var(lp->ll_name, (flags & GLV_READ_ONLY) ? NULL : &ht, v = find_var(lp->ll_name, writing ? &ht : NULL,
flags & GLV_NO_AUTOLOAD); (flags & GLV_NO_AUTOLOAD) || writing);
if (v == NULL && !quiet) if (v == NULL && !quiet)
semsg(_(e_undefined_variable_str), lp->ll_name); semsg(_(e_undefined_variable_str), lp->ll_name);
*p = cc; *p = cc;
@@ -1972,13 +1975,15 @@ eval_func(
int len = name_len; int len = name_len;
partial_T *partial; partial_T *partial;
int ret = OK; int ret = OK;
type_T *type = NULL;
if (!evaluate) if (!evaluate)
check_vars(s, len); check_vars(s, len);
// If "s" is the name of a variable of type VAR_FUNC // If "s" is the name of a variable of type VAR_FUNC
// use its contents. // use its contents.
s = deref_func_name(s, &len, &partial, !evaluate); s = deref_func_name(s, &len, &partial,
in_vim9script() ? &type : NULL, !evaluate);
// Need to make a copy, in case evaluating the arguments makes // Need to make a copy, in case evaluating the arguments makes
// the name invalid. // the name invalid.
@@ -1996,6 +2001,7 @@ eval_func(
funcexe.evaluate = evaluate; funcexe.evaluate = evaluate;
funcexe.partial = partial; funcexe.partial = partial;
funcexe.basetv = basetv; funcexe.basetv = basetv;
funcexe.check_type = type;
ret = get_func_tv(s, len, rettv, arg, evalarg, &funcexe); ret = get_func_tv(s, len, rettv, arg, evalarg, &funcexe);
} }
vim_free(s); vim_free(s);

View File

@@ -3497,7 +3497,8 @@ common_function(typval_T *argvars, typval_T *rettv, int is_funcref)
{ {
name = s; name = s;
trans_name = trans_function_name(&name, &is_global, FALSE, trans_name = trans_function_name(&name, &is_global, FALSE,
TFN_INT | TFN_QUIET | TFN_NO_AUTOLOAD | TFN_NO_DEREF, NULL, NULL); TFN_INT | TFN_QUIET | TFN_NO_AUTOLOAD | TFN_NO_DEREF,
NULL, NULL, NULL);
if (*name != NUL) if (*name != NUL)
s = NULL; s = NULL;
} }

View File

@@ -2639,8 +2639,7 @@ check_vars(char_u *name, int len)
* Find variable "name" in the list of variables. * Find variable "name" in the list of variables.
* Return a pointer to it if found, NULL if not found. * Return a pointer to it if found, NULL if not found.
* Careful: "a:0" variables don't have a name. * Careful: "a:0" variables don't have a name.
* When "htp" is not NULL we are writing to the variable, set "htp" to the * When "htp" is not NULL set "htp" to the hashtab_T used.
* hashtab_T used.
*/ */
dictitem_T * dictitem_T *
find_var(char_u *name, hashtab_T **htp, int no_autoload) find_var(char_u *name, hashtab_T **htp, int no_autoload)
@@ -2654,12 +2653,12 @@ find_var(char_u *name, hashtab_T **htp, int no_autoload)
*htp = ht; *htp = ht;
if (ht == NULL) if (ht == NULL)
return NULL; return NULL;
ret = find_var_in_ht(ht, *name, varname, no_autoload || htp != NULL); ret = find_var_in_ht(ht, *name, varname, no_autoload);
if (ret != NULL) if (ret != NULL)
return ret; return ret;
// Search in parent scope for lambda // Search in parent scope for lambda
ret = find_var_in_scoped_ht(name, no_autoload || htp != NULL); ret = find_var_in_scoped_ht(name, no_autoload);
if (ret != NULL) if (ret != NULL)
return ret; return ret;
@@ -2669,8 +2668,7 @@ find_var(char_u *name, hashtab_T **htp, int no_autoload)
ht = get_script_local_ht(); ht = get_script_local_ht();
if (ht != NULL) if (ht != NULL)
{ {
ret = find_var_in_ht(ht, *name, varname, ret = find_var_in_ht(ht, *name, varname, no_autoload);
no_autoload || htp != NULL);
if (ret != NULL) if (ret != NULL)
{ {
if (htp != NULL) if (htp != NULL)

View File

@@ -4,7 +4,7 @@ hashtab_T *func_tbl_get(void);
char_u *get_lambda_name(void); char_u *get_lambda_name(void);
char_u *register_cfunc(cfunc_T cb, cfunc_free_T cb_free, void *state); 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); 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); 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); 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); 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); char_u *fname_trans_sid(char_u *name, char_u *fname_buf, char_u **tofree, int *error);
@@ -31,7 +31,7 @@ int call_callback(callback_T *callback, int len, typval_T *rettv, int argcount,
void user_func_error(int error, char_u *name); 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); 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 *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 *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); char_u *untrans_function_name(char_u *name);
void list_functions(regmatch_T *regmatch); void list_functions(regmatch_T *regmatch);
ufunc_T *define_function(exarg_T *eap, char_u *name_arg); ufunc_T *define_function(exarg_T *eap, char_u *name_arg);

View File

@@ -16,6 +16,7 @@ void type_mismatch(type_T *expected, type_T *actual);
void arg_type_mismatch(type_T *expected, type_T *actual, int argidx); 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_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_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); char_u *skip_type(char_u *start, int optional);
type_T *parse_type(char_u **arg, garray_T *type_gap, int give_error); type_T *parse_type(char_u **arg, garray_T *type_gap, int give_error);
int equal_type(type_T *type1, type_T *type2); int equal_type(type_T *type1, type_T *type2);

View File

@@ -1944,6 +1944,7 @@ typedef struct {
partial_T *partial; // for extra arguments partial_T *partial; // for extra arguments
dict_T *selfdict; // Dictionary for "self" dict_T *selfdict; // Dictionary for "self"
typval_T *basetv; // base for base->method() typval_T *basetv; // base for base->method()
type_T *check_type; // type from funcref or NULL
} funcexe_T; } funcexe_T;
/* /*

View File

@@ -579,6 +579,22 @@ def Test_call_funcref_wrong_args()
CheckScriptFailure(head + ["funcMap['func']('str', 123)"] + tail, 'E119:') CheckScriptFailure(head + ["funcMap['func']('str', 123)"] + tail, 'E119:')
CheckScriptFailure(head + ["funcMap['func']('str', 123, [1], 4)"] + tail, 'E118:') 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 enddef
def Test_call_lambda_args() def Test_call_lambda_args()

View File

@@ -727,47 +727,68 @@ errret:
* name it contains, otherwise return "name". * name it contains, otherwise return "name".
* If "partialp" is not NULL, and "name" is of type VAR_PARTIAL also set * If "partialp" is not NULL, and "name" is of type VAR_PARTIAL also set
* "partialp". * "partialp".
* If "type" is not NULL and a Vim9 script-local variable is found look up the
* type of the variable.
*/ */
char_u * char_u *
deref_func_name(char_u *name, int *lenp, partial_T **partialp, int no_autoload) deref_func_name(
char_u *name,
int *lenp,
partial_T **partialp,
type_T **type,
int no_autoload)
{ {
dictitem_T *v; dictitem_T *v;
int cc; int cc;
char_u *s; char_u *s = NULL;
hashtab_T *ht;
if (partialp != NULL) if (partialp != NULL)
*partialp = NULL; *partialp = NULL;
cc = name[*lenp]; cc = name[*lenp];
name[*lenp] = NUL; name[*lenp] = NUL;
v = find_var(name, NULL, no_autoload); v = find_var(name, &ht, no_autoload);
name[*lenp] = cc; name[*lenp] = cc;
if (v != NULL && v->di_tv.v_type == VAR_FUNC) if (v != NULL)
{ {
if (v->di_tv.vval.v_string == NULL) if (v->di_tv.v_type == VAR_FUNC)
{ {
*lenp = 0; if (v->di_tv.vval.v_string == NULL)
return (char_u *)""; // just in case {
*lenp = 0;
return (char_u *)""; // just in case
}
s = v->di_tv.vval.v_string;
*lenp = (int)STRLEN(s);
} }
s = v->di_tv.vval.v_string;
*lenp = (int)STRLEN(s);
return s;
}
if (v != NULL && v->di_tv.v_type == VAR_PARTIAL) if (v->di_tv.v_type == VAR_PARTIAL)
{
partial_T *pt = v->di_tv.vval.v_partial;
if (pt == NULL)
{ {
*lenp = 0; partial_T *pt = v->di_tv.vval.v_partial;
return (char_u *)""; // just in case
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;
} }
if (partialp != NULL)
*partialp = pt;
s = partial_name(pt);
*lenp = (int)STRLEN(s);
return s;
} }
return name; return name;
@@ -2387,6 +2408,14 @@ call_func(
} }
} }
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) if (error == FCERR_NONE && funcexe->evaluate)
{ {
char_u *rfname = fname; char_u *rfname = fname;
@@ -2629,7 +2658,8 @@ trans_function_name(
int skip, // only find the end, don't evaluate int skip, // only find the end, don't evaluate
int flags, int flags,
funcdict_T *fdp, // return: info about dictionary used funcdict_T *fdp, // return: info about dictionary used
partial_T **partial) // return: partial of a FuncRef partial_T **partial, // return: partial of a FuncRef
type_T **type) // return: type of funcref if not NULL
{ {
char_u *name = NULL; char_u *name = NULL;
char_u *start; char_u *start;
@@ -2733,7 +2763,7 @@ trans_function_name(
if (lv.ll_exp_name != NULL) if (lv.ll_exp_name != NULL)
{ {
len = (int)STRLEN(lv.ll_exp_name); len = (int)STRLEN(lv.ll_exp_name);
name = deref_func_name(lv.ll_exp_name, &len, partial, name = deref_func_name(lv.ll_exp_name, &len, partial, type,
flags & TFN_NO_AUTOLOAD); flags & TFN_NO_AUTOLOAD);
if (name == lv.ll_exp_name) if (name == lv.ll_exp_name)
name = NULL; name = NULL;
@@ -2741,7 +2771,8 @@ trans_function_name(
else if (!(flags & TFN_NO_DEREF)) else if (!(flags & TFN_NO_DEREF))
{ {
len = (int)(end - *pp); len = (int)(end - *pp);
name = deref_func_name(*pp, &len, partial, flags & TFN_NO_AUTOLOAD); name = deref_func_name(*pp, &len, partial, type,
flags & TFN_NO_AUTOLOAD);
if (name == *pp) if (name == *pp)
name = NULL; name = NULL;
} }
@@ -3064,7 +3095,7 @@ define_function(exarg_T *eap, char_u *name_arg)
else else
{ {
name = trans_function_name(&p, &is_global, eap->skip, name = trans_function_name(&p, &is_global, eap->skip,
TFN_NO_AUTOLOAD, &fudi, NULL); TFN_NO_AUTOLOAD, &fudi, NULL, NULL);
paren = (vim_strchr(p, '(') != NULL); paren = (vim_strchr(p, '(') != NULL);
if (name == NULL && (fudi.fd_dict == NULL || !paren) && !eap->skip) if (name == NULL && (fudi.fd_dict == NULL || !paren) && !eap->skip)
{ {
@@ -3479,7 +3510,8 @@ define_function(exarg_T *eap, char_u *name_arg)
if (*p == '!') if (*p == '!')
p = skipwhite(p + 1); p = skipwhite(p + 1);
p += eval_fname_script(p); p += eval_fname_script(p);
vim_free(trans_function_name(&p, NULL, TRUE, 0, NULL, NULL)); vim_free(trans_function_name(&p, NULL, TRUE, 0, NULL,
NULL, NULL));
if (*skipwhite(p) == '(') if (*skipwhite(p) == '(')
{ {
if (nesting == MAX_FUNC_NESTING - 1) if (nesting == MAX_FUNC_NESTING - 1)
@@ -3616,7 +3648,7 @@ define_function(exarg_T *eap, char_u *name_arg)
{ {
hashtab_T *ht; hashtab_T *ht;
v = find_var(name, &ht, FALSE); v = find_var(name, &ht, TRUE);
if (v != NULL && v->di_tv.v_type == VAR_FUNC) if (v != NULL && v->di_tv.v_type == VAR_FUNC)
{ {
emsg_funcname(N_("E707: Function name conflicts with variable: %s"), emsg_funcname(N_("E707: Function name conflicts with variable: %s"),
@@ -4007,7 +4039,7 @@ function_exists(char_u *name, int no_deref)
flag = TFN_INT | TFN_QUIET | TFN_NO_AUTOLOAD; flag = TFN_INT | TFN_QUIET | TFN_NO_AUTOLOAD;
if (no_deref) if (no_deref)
flag |= TFN_NO_DEREF; flag |= TFN_NO_DEREF;
p = trans_function_name(&nm, &is_global, FALSE, flag, NULL, NULL); p = trans_function_name(&nm, &is_global, FALSE, flag, NULL, NULL, NULL);
nm = skipwhite(nm); nm = skipwhite(nm);
// Only accept "funcname", "funcname ", "funcname (..." and // Only accept "funcname", "funcname ", "funcname (..." and
@@ -4027,7 +4059,7 @@ get_expanded_name(char_u *name, int check)
int is_global = FALSE; int is_global = FALSE;
p = trans_function_name(&nm, &is_global, FALSE, p = trans_function_name(&nm, &is_global, FALSE,
TFN_INT|TFN_QUIET, NULL, NULL); TFN_INT|TFN_QUIET, NULL, NULL, NULL);
if (p != NULL && *nm == NUL if (p != NULL && *nm == NUL
&& (!check || translated_function_exists(p, is_global))) && (!check || translated_function_exists(p, is_global)))
@@ -4097,7 +4129,8 @@ ex_delfunction(exarg_T *eap)
int is_global = FALSE; int is_global = FALSE;
p = eap->arg; p = eap->arg;
name = trans_function_name(&p, &is_global, eap->skip, 0, &fudi, NULL); name = trans_function_name(&p, &is_global, eap->skip, 0, &fudi,
NULL, NULL);
vim_free(fudi.fd_newkey); vim_free(fudi.fd_newkey);
if (name == NULL) if (name == NULL)
{ {
@@ -4328,6 +4361,7 @@ ex_call(exarg_T *eap)
funcdict_T fudi; funcdict_T fudi;
partial_T *partial = NULL; partial_T *partial = NULL;
evalarg_T evalarg; evalarg_T evalarg;
type_T *type = NULL;
fill_evalarg_from_eap(&evalarg, eap, eap->skip); fill_evalarg_from_eap(&evalarg, eap, eap->skip);
if (eap->skip) if (eap->skip)
@@ -4343,8 +4377,8 @@ ex_call(exarg_T *eap)
return; return;
} }
tofree = trans_function_name(&arg, NULL, eap->skip, tofree = trans_function_name(&arg, NULL, eap->skip, TFN_INT,
TFN_INT, &fudi, &partial); &fudi, &partial, in_vim9script() ? &type : NULL);
if (fudi.fd_newkey != NULL) if (fudi.fd_newkey != NULL)
{ {
// Still need to give an error message for missing key. // Still need to give an error message for missing key.
@@ -4363,8 +4397,8 @@ ex_call(exarg_T *eap)
// contents. For VAR_PARTIAL get its partial, unless we already have one // contents. For VAR_PARTIAL get its partial, unless we already have one
// from trans_function_name(). // from trans_function_name().
len = (int)STRLEN(tofree); len = (int)STRLEN(tofree);
name = deref_func_name(tofree, &len, name = deref_func_name(tofree, &len, partial != NULL ? NULL : &partial,
partial != NULL ? NULL : &partial, FALSE); in_vim9script() && type == NULL ? &type : NULL, FALSE);
// Skip white space to allow ":call func ()". Not good, but required for // Skip white space to allow ":call func ()". Not good, but required for
// backward compatibility. // backward compatibility.
@@ -4416,6 +4450,7 @@ ex_call(exarg_T *eap)
funcexe.evaluate = !eap->skip; funcexe.evaluate = !eap->skip;
funcexe.partial = partial; funcexe.partial = partial;
funcexe.selfdict = fudi.fd_dict; funcexe.selfdict = fudi.fd_dict;
funcexe.check_type = type;
if (get_func_tv(name, -1, &rettv, &arg, &evalarg, &funcexe) == FAIL) if (get_func_tv(name, -1, &rettv, &arg, &evalarg, &funcexe) == FAIL)
{ {
failed = TRUE; failed = TRUE;

View File

@@ -750,6 +750,8 @@ static char *(features[]) =
static int included_patches[] = static int included_patches[] =
{ /* Add new patch number below this line */ { /* Add new patch number below this line */
/**/
2306,
/**/ /**/
2305, 2305,
/**/ /**/

View File

@@ -1790,9 +1790,9 @@ generate_PCALL(
stack->ga_len + offset]; stack->ga_len + offset];
type_T *expected; type_T *expected;
if (varargs && i >= type->tt_min_argcount - 1) if (varargs && i >= type->tt_argcount - 1)
expected = type->tt_args[ expected = type->tt_args[
type->tt_min_argcount - 1]->tt_member; type->tt_argcount - 1]->tt_member;
else else
expected = type->tt_args[i]; expected = type->tt_args[i];
if (need_type(actual, expected, offset, if (need_type(actual, expected, offset,

View File

@@ -3423,7 +3423,7 @@ ex_disassemble(exarg_T *eap)
} }
else else
fname = trans_function_name(&arg, &is_global, FALSE, fname = trans_function_name(&arg, &is_global, FALSE,
TFN_INT | TFN_QUIET | TFN_NO_AUTOLOAD, NULL, NULL); TFN_INT | TFN_QUIET | TFN_NO_AUTOLOAD, NULL, NULL, NULL);
if (fname == NULL) if (fname == NULL)
{ {
semsg(_(e_invarg2), eap->arg); semsg(_(e_invarg2), eap->arg);

View File

@@ -527,6 +527,46 @@ check_arg_type(type_T *expected, type_T *actual, int argidx)
return check_type(expected, actual, TRUE, argidx); return check_type(expected, actual, TRUE, argidx);
} }
/*
* 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. * Skip over a type definition and return a pointer to just after it.
* When "optional" is TRUE then a leading "?" is accepted. * When "optional" is TRUE then a leading "?" is accepted.