# HG changeset patch # User Christian Brabandt # Date 1702732503 -3600 # Node ID 84b93d95a952ee1de122613c4bae2843afeea720 # Parent e858c9f6a9aa1225161ae9a525b4a57f40a8cdab patch 9.0.2169: Vim9: builtin funcs may accept a non-value Commit: https://github.com/vim/vim/commit/d8bf87c9fbd92fd6b837446e886d47e557adadbc Author: Ernie Rael Date: Sat Dec 16 14:03:33 2023 +0100 patch 9.0.2169: Vim9: builtin funcs may accept a non-value Problem: Vim9: builtin funcs may accept a non-value Solution: Restrict builtin functions that accept `type` This PR finishes off detection and prevention of using a type as a value. It takes care of builtin functions. However there are some builtin functions, that need to be able to handle types as well as non-args: instanceof(), type(), typename(), string(). A "bit", FE_X, is added to funcentry_T; when set, the builtin function can handle a type (class or type-alias) in addition to a value. Noteworthy change: Discovered that in compile_call() the builtin add() is compiled inline instead of calling the builtin. Had to add a check there. closes: #13688 Signed-off-by: Ernie Rael Signed-off-by: Christian Brabandt diff --git a/src/evalfunc.c b/src/evalfunc.c --- a/src/evalfunc.c +++ b/src/evalfunc.c @@ -1651,7 +1651,7 @@ typedef struct char *f_name; // function name char f_min_argc; // minimal number of arguments char f_max_argc; // maximal number of arguments - char f_argtype; // for method: FEARG_ values + char f_argtype; // for method: FEARG_ values; bits FE_ argcheck_T *f_argcheck; // list of functions to check argument types; // use "arg_any" (not NULL) to accept an // argument of any type @@ -1666,10 +1666,12 @@ typedef struct #define VARGS CHAR_MAX // values for f_argtype; zero means it cannot be used as a method -#define FEARG_1 1 // base is the first argument -#define FEARG_2 2 // base is the second argument -#define FEARG_3 3 // base is the third argument -#define FEARG_4 4 // base is the fourth argument +#define FEARG_1 0x01 // base is the first argument +#define FEARG_2 0x02 // base is the second argument +#define FEARG_3 0x03 // base is the third argument +#define FEARG_4 0x04 // base is the fourth argument +#define FEARG_MASK 0x0F // bits in f_argtype used as argument index +#define FE_X 0x10 // builtin accepts a non-value (class, typealias) #if defined(HAVE_MATH_H) # define MATH_FUNC(name) name @@ -2181,7 +2183,7 @@ static funcentry_T global_functions[] = ret_string, f_inputsecret}, {"insert", 2, 3, FEARG_1, arg23_insert, ret_first_arg, f_insert}, - {"instanceof", 2, VARGS, FEARG_1, arg2_instanceof, + {"instanceof", 2, VARGS, FEARG_1|FE_X, arg2_instanceof, ret_bool, f_instanceof}, {"interrupt", 0, 0, 0, NULL, ret_void, f_interrupt}, @@ -2659,7 +2661,7 @@ static funcentry_T global_functions[] = ret_number, f_strgetchar}, {"stridx", 2, 3, FEARG_1, arg3_string_string_number, ret_number, f_stridx}, - {"string", 1, 1, FEARG_1, NULL, + {"string", 1, 1, FEARG_1|FE_X, NULL, ret_string, f_string}, {"strlen", 1, 1, FEARG_1, arg1_string_or_nr, ret_number, f_strlen}, @@ -2821,7 +2823,7 @@ static funcentry_T global_functions[] = ret_void, f_test_option_not_set}, {"test_override", 2, 2, FEARG_2, arg2_string_number, ret_void, f_test_override}, - {"test_refcount", 1, 1, FEARG_1, NULL, + {"test_refcount", 1, 1, FEARG_1|FE_X, NULL, ret_number, f_test_refcount}, {"test_setmouse", 2, 2, 0, arg2_number, ret_void, f_test_setmouse}, @@ -2853,9 +2855,9 @@ static funcentry_T global_functions[] = ret_string, f_trim}, {"trunc", 1, 1, FEARG_1, arg1_float_or_nr, ret_float, f_trunc}, - {"type", 1, 1, FEARG_1, NULL, + {"type", 1, 1, FEARG_1|FE_X, NULL, ret_number, f_type}, - {"typename", 1, 1, FEARG_1, NULL, + {"typename", 1, 1, FEARG_1|FE_X, NULL, ret_string, f_typename}, {"undofile", 1, 1, FEARG_1, arg1_string, ret_string, f_undofile}, @@ -2928,6 +2930,15 @@ static funcentry_T global_functions[] = }; /* + * Return true if specified function allows a type as an argument. + */ + static int +func_allows_type(int idx) +{ + return (global_functions[idx].f_argtype & FE_X) != 0; +} + +/* * Function given to ExpandGeneric() to obtain the list of internal * or user defined function names. */ @@ -3059,6 +3070,15 @@ internal_func_check_arg_types( int argcount, cctx_T *cctx) { + // Some internal functions accept types like Class as arguments. For other + // functions, check the arguments are not types. + if (!(func_allows_type(idx))) + { + for (int i = 0; i < argcount; ++i) + if (check_type_is_value(types[i].type_curr) == FAIL) + return FAIL; + } + argcheck_T *argchecks = global_functions[idx].f_argcheck; if (argchecks == NULL) @@ -3143,7 +3163,7 @@ check_internal_func(int idx, int argcoun else if (argcount > global_functions[idx].f_max_argc) res = FCERR_TOOMANY; else - return global_functions[idx].f_argtype; + return global_functions[idx].f_argtype & FEARG_MASK; name = internal_func_name(idx); if (res == FCERR_TOOMANY) @@ -3153,6 +3173,24 @@ check_internal_func(int idx, int argcoun return -1; } +/* + * Some internal functions accept types like Class as arguments. For other + * functions, check the arguments are not types. + * + * Return OK/FAIL. + */ + static int +check_args_for_type(int idx, int argcount, typval_T *argvars) +{ + if (!func_allows_type(idx)) + { + for (int i = 0; i < argcount; ++i) + if (check_typval_is_value(&argvars[i]) == FAIL) + return FAIL; + } + return OK; +} + funcerror_T call_internal_func( char_u *name, @@ -3169,6 +3207,8 @@ call_internal_func( return FCERR_TOOFEW; if (argcount > global_functions[i].f_max_argc) return FCERR_TOOMANY; + if (check_args_for_type(i, argcount, argvars) == FAIL) + return FCERR_OTHER; argvars[argcount].v_type = VAR_UNKNOWN; global_functions[i].f_func(argvars, rettv); return FCERR_NONE; @@ -3200,14 +3240,16 @@ call_internal_method( fi = find_internal_func(name); if (fi < 0) return FCERR_UNKNOWN; - if (global_functions[fi].f_argtype == 0) + if ((global_functions[fi].f_argtype & FEARG_MASK) == 0) return FCERR_NOTMETHOD; if (argcount + 1 < global_functions[fi].f_min_argc) return FCERR_TOOFEW; if (argcount + 1 > global_functions[fi].f_max_argc) return FCERR_TOOMANY; - - if (global_functions[fi].f_argtype == FEARG_2) + if (check_args_for_type(fi, argcount, argvars) == FAIL) + return FCERR_OTHER; + + if ((global_functions[fi].f_argtype & FEARG_MASK) == FEARG_2) { if (argcount < 1) return FCERR_TOOFEW; @@ -3218,7 +3260,7 @@ call_internal_method( for (int i = 1; i < argcount; ++i) argv[i + 1] = argvars[i]; } - else if (global_functions[fi].f_argtype == FEARG_3) + else if ((global_functions[fi].f_argtype & FEARG_MASK) == FEARG_3) { if (argcount < 2) return FCERR_TOOFEW; @@ -3230,7 +3272,7 @@ call_internal_method( for (int i = 2; i < argcount; ++i) argv[i + 1] = argvars[i]; } - else if (global_functions[fi].f_argtype == FEARG_4) + else if ((global_functions[fi].f_argtype & FEARG_MASK) == FEARG_4) { if (argcount < 3) return FCERR_TOOFEW; @@ -3252,6 +3294,9 @@ call_internal_method( } argv[argcount + 1].v_type = VAR_UNKNOWN; + if (check_args_for_type(fi, argcount + 1, argv) == FAIL) + return FCERR_OTHER; + global_functions[fi].f_func(argv, rettv); return FCERR_NONE; } diff --git a/src/proto/vim9type.pro b/src/proto/vim9type.pro --- a/src/proto/vim9type.pro +++ b/src/proto/vim9type.pro @@ -36,7 +36,6 @@ type_T *get_member_type_from_stack(int c char *vartype_name(vartype_T type); char *type_name(type_T *type, char **tofree); void f_typename(typval_T *argvars, typval_T *rettv); -int check_vartype_is_value(vartype_T typ); int check_typval_is_value(typval_T *tv); int check_type_is_value(type_T *type); /* vim: set ft=c : */ diff --git a/src/testdir/test_vim9_builtin.vim b/src/testdir/test_vim9_builtin.vim --- a/src/testdir/test_vim9_builtin.vim +++ b/src/testdir/test_vim9_builtin.vim @@ -5048,4 +5048,153 @@ def Test_writefile() v9.CheckDefExecAndScriptFailure(['writefile(["a"], "")'], 'E482: Can''t create file ') enddef +def Test_passing_type_to_builtin() + # type, typename, string, instanceof are allowed type argument + var lines =<< trim END + vim9script + class C + endclass + type T = number + type U = C + var x: any + x = type(C) + x = type(T) + x = typename(C) + x = typename(T) + x = string(C) + x = string(T) + x = instanceof(C.new(), U, C) + END + v9.CheckScriptSuccess(lines) + + # check argument to add at script level + # Note: add() is special cased in compile_call in vim9expr + lines =<< trim END + vim9script + class C + endclass + add([], C) + END + v9.CheckScriptFailure(lines, 'E1405: Class "C" cannot be used as a value') + + # check argument to add in :def + lines =<< trim END + vim9script + class C + endclass + def F() + add([], C) + enddef + F() + END + v9.CheckScriptFailure(lines, 'E1405: Class "C" cannot be used as a value') + + # check member call argument to add at script level + lines =<< trim END + vim9script + class C + endclass + []->add(C) + END + v9.CheckScriptFailure(lines, 'E1405: Class "C" cannot be used as a value') + + # check member call argument to add in :def + lines =<< trim END + vim9script + class C + endclass + def F() + []->add(C) + enddef + F() + END + v9.CheckScriptFailure(lines, 'E1405: Class "C" cannot be used as a value') + + # Try "empty()" builtin + # check argument to empty at script level + lines =<< trim END + vim9script + class C + endclass + empty(C) + END + v9.CheckScriptFailure(lines, 'E1405: Class "C" cannot be used as a value') + + # check argument to empty in :def + lines =<< trim END + vim9script + class C + endclass + def F() + empty(C) + enddef + F() + END + v9.CheckScriptFailure(lines, 'E1405: Class "C" cannot be used as a value') + + # check member call argument to empty at script level + lines =<< trim END + vim9script + class C + endclass + C->empty() + END + v9.CheckScriptFailure(lines, 'E1405: Class "C" cannot be used as a value') + + # check member call argument to empty in :def + lines =<< trim END + vim9script + class C + endclass + def F() + C->empty() + enddef + F() + END + v9.CheckScriptFailure(lines, 'E1405: Class "C" cannot be used as a value') + + # Try "abs()" builtin + # check argument to abs at script level + lines =<< trim END + vim9script + class C + endclass + abs(C) + END + v9.CheckScriptFailure(lines, 'E1405: Class "C" cannot be used as a value') + + # check argument to abs in :def + lines =<< trim END + vim9script + class C + endclass + def F() + abs(C) + enddef + F() + END + v9.CheckScriptFailure(lines, 'E1405: Class "C" cannot be used as a value') + + # check member call argument to abs at script level + lines =<< trim END + vim9script + class C + endclass + C->abs() + END + v9.CheckScriptFailure(lines, 'E1405: Class "C" cannot be used as a value') + + # check member call argument to abs in :def + lines =<< trim END + vim9script + class C + endclass + def F() + C->abs() + enddef + F() + END + v9.CheckScriptFailure(lines, 'E1405: Class "C" cannot be used as a value') +enddef + " vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker diff --git a/src/testdir/test_vim9_typealias.vim b/src/testdir/test_vim9_typealias.vim --- a/src/testdir/test_vim9_typealias.vim +++ b/src/testdir/test_vim9_typealias.vim @@ -296,7 +296,7 @@ def Test_typealias() type A = list var x = json_encode(A) END - v9.CheckSourceFailure(lines, 'E1161: Cannot json encode a typealias', 3) + v9.CheckSourceFailure(lines, 'E1403: Type alias "A" cannot be used as a value', 3) # Comparing type alias with a number (script level) lines =<< trim END @@ -452,9 +452,9 @@ def Test_typealias_with_builtin_function var lines =<< trim END vim9script type A = list - assert_equal(0, empty(A)) + var x = empty(A) END - v9.CheckScriptSuccess(lines) + v9.CheckScriptFailure(lines, 'E1403: Type alias "A" cannot be used as a value', 3) # Using a type alias with len() lines =<< trim END @@ -462,7 +462,7 @@ def Test_typealias_with_builtin_function type A = list var x = len(A) END - v9.CheckScriptFailure(lines, 'E701: Invalid type for len()', 3) + v9.CheckScriptFailure(lines, 'E1403: Type alias "A" cannot be used as a value', 3) # Using a type alias with len() lines =<< trim END @@ -473,7 +473,7 @@ def Test_typealias_with_builtin_function enddef Foo() END - v9.CheckScriptFailure(lines, 'E1013: Argument 1: type mismatch, expected list but got typealias', 1) + v9.CheckScriptFailure(lines, 'E1407: Cannot use a Typealias as a variable or value', 1) # Using a type alias with eval() lines =<< trim END @@ -780,4 +780,134 @@ def Test_class_as_func_argument_or_retur v9.CheckScriptFailure(lines, 'E1405: Class "C" cannot be used as a value', 1) enddef +def Test_passing_typealias_to_builtin() + # type, typename, string, instanceof are allowed type argument + var lines =<< trim END + vim9script + type T = number + var x: any + x = type(T) + x = typename(T) + x = string(T) + END + v9.CheckScriptSuccess(lines) + + # check argument to add at script level + # Note: add() is special cased in compile_call in vim9expr + lines =<< trim END + vim9script + type T = number + add([], T) + END + v9.CheckScriptFailure(lines, 'E1403: Type alias "T" cannot be used as a value') + + # check argument to add in :def + lines =<< trim END + vim9script + type T = number + def F() + add([], T) + enddef + F() + END + v9.CheckScriptFailure(lines, 'E1407: Cannot use a Typealias as a variable or value') + + # check member call argument to add at script level + lines =<< trim END + vim9script + type T = number + []->add(T) + END + v9.CheckScriptFailure(lines, 'E1403: Type alias "T" cannot be used as a value') + + # check member call argument to add in :def + lines =<< trim END + vim9script + type T = number + def F() + []->add(T) + enddef + F() + END + v9.CheckScriptFailure(lines, 'E1407: Cannot use a Typealias as a variable or value') + + # Try "empty()" builtin + # check argument to empty at script level + lines =<< trim END + vim9script + type T = number + empty(T) + END + v9.CheckScriptFailure(lines, 'E1403: Type alias "T" cannot be used as a value') + + # check argument to empty in :def + lines =<< trim END + vim9script + type T = number + def F() + empty(T) + enddef + F() + END + v9.CheckScriptFailure(lines, 'E1407: Cannot use a Typealias as a variable or value') + + # check member call argument to empty at script level + lines =<< trim END + vim9script + type T = number + T->empty() + END + v9.CheckScriptFailure(lines, 'E1403: Type alias "T" cannot be used as a value') + + # check member call argument to empty in :def + lines =<< trim END + vim9script + type T = number + def F() + T->empty() + enddef + F() + END + v9.CheckScriptFailure(lines, 'E1407: Cannot use a Typealias as a variable or value') + + # Try "abs()" builtin + # check argument to abs at script level + lines =<< trim END + vim9script + type T = number + abs(T) + END + v9.CheckScriptFailure(lines, 'E1403: Type alias "T" cannot be used as a value') + + # check argument to abs in :def + lines =<< trim END + vim9script + type T = number + def F() + abs(T) + enddef + F() + END + v9.CheckScriptFailure(lines, 'E1407: Cannot use a Typealias as a variable or value') + + # check member call argument to abs at script level + lines =<< trim END + vim9script + type T = number + T->abs() + END + v9.CheckScriptFailure(lines, 'E1403: Type alias "T" cannot be used as a value') + + # check member call argument to abs in :def + lines =<< trim END + vim9script + type T = number + def F() + T->abs() + enddef + F() + END + v9.CheckScriptFailure(lines, 'E1407: Cannot use a Typealias as a variable or value') +enddef + " vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -705,6 +705,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 2169, +/**/ 2168, /**/ 2167, diff --git a/src/vim9expr.c b/src/vim9expr.c --- a/src/vim9expr.c +++ b/src/vim9expr.c @@ -1143,6 +1143,8 @@ compile_call( if (STRCMP(name, "add") == 0 && argcount == 2) { type_T *type = get_decl_type_on_stack(cctx, 1); + if (check_type_is_value(get_type_on_stack(cctx, 0)) == FAIL) + goto theend; // add() can be compiled to instructions if we know the type if (type->tt_type == VAR_LIST) diff --git a/src/vim9type.c b/src/vim9type.c --- a/src/vim9type.c +++ b/src/vim9type.c @@ -1867,6 +1867,8 @@ f_typename(typval_T *argvars, typval_T * int check_typval_is_value(typval_T *tv) { + if (tv == NULL) + return OK; if (tv->v_type == VAR_CLASS) { semsg(_(e_using_class_as_value_str), tv->vval.v_class->class_name); @@ -1886,6 +1888,8 @@ check_typval_is_value(typval_T *tv) int check_type_is_value(type_T *type) { + if (type == NULL) + return OK; if (type->tt_type == VAR_CLASS) { semsg(_(e_using_class_as_value_str), type->tt_class->class_name); @@ -1893,31 +1897,12 @@ check_type_is_value(type_T *type) } else if (type->tt_type == VAR_TYPEALIAS) { - // Not sure what could be done here to get a name - // TODO: MAYBE AN OPTIONAL ARGUMENT + // TODO: Not sure what could be done here to get a name. + // Maybe an optional argument? emsg(_(e_using_typealias_as_var_val)); return FAIL; } return OK; } -/* - * Same as above, except check vartype_T. - */ - int -check_vartype_is_value(vartype_T typ) -{ - if (typ == VAR_CLASS) - { - emsg(_(e_using_class_as_var_val)); - return FAIL; - } - else if (typ == VAR_TYPEALIAS) - { - emsg(_(e_using_typealias_as_var_val)); - return FAIL; - } - return OK; -} - #endif // FEAT_EVAL