changeset 33949:84b93d95a952 v9.0.2169

patch 9.0.2169: Vim9: builtin funcs may accept a non-value Commit: https://github.com/vim/vim/commit/d8bf87c9fbd92fd6b837446e886d47e557adadbc Author: Ernie Rael <errael@raelity.com> 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 <errael@raelity.com> Signed-off-by: Christian Brabandt <cb@256bit.org>
author Christian Brabandt <cb@256bit.org>
date Sat, 16 Dec 2023 14:15:03 +0100
parents e858c9f6a9aa
children ba10efba5843
files src/evalfunc.c src/proto/vim9type.pro src/testdir/test_vim9_builtin.vim src/testdir/test_vim9_typealias.vim src/version.c src/vim9expr.c src/vim9type.c
diffstat 7 files changed, 355 insertions(+), 43 deletions(-) [+]
line wrap: on
line diff
--- 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;
 }
--- 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 : */
--- 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 <empty>')
 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
--- a/src/testdir/test_vim9_typealias.vim
+++ b/src/testdir/test_vim9_typealias.vim
@@ -296,7 +296,7 @@ def Test_typealias()
     type A = list<string>
     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<func>
-    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<func>
     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<any> 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
--- 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,
--- 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)
--- 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