changeset 23458:d2b1269c2c68 v8.2.2272

patch 8.2.2272: Vim9: extend() can violate the type of a variable Commit: https://github.com/vim/vim/commit/aa210a3aeccc33c6051978017959126b037f94af Author: Bram Moolenaar <Bram@vim.org> Date: Sat Jan 2 15:41:03 2021 +0100 patch 8.2.2272: Vim9: extend() can violate the type of a variable Problem: Vim9: extend() can violate the type of a variable. Solution: Add the type to the dictionary or list and check items against it. (closes #7593)
author Bram Moolenaar <Bram@vim.org>
date Sat, 02 Jan 2021 15:45:04 +0100
parents 86b9697a8c63
children d050bf102b37
files src/dict.c src/evalvars.c src/list.c src/proto/vim9script.pro src/structs.h src/testdir/test_vim9_builtin.vim src/testdir/test_vim9_disassemble.vim src/version.c src/vim9compile.c src/vim9execute.c src/vim9script.c
diffstat 11 files changed, 149 insertions(+), 13 deletions(-) [+]
line wrap: on
line diff
--- a/src/dict.c
+++ b/src/dict.c
@@ -107,6 +107,8 @@ rettv_dict_set(typval_T *rettv, dict_T *
 dict_free_contents(dict_T *d)
 {
     hashtab_free_contents(&d->dv_hashtab);
+    free_type(d->dv_type);
+    d->dv_type = NULL;
 }
 
 /*
@@ -1057,6 +1059,12 @@ dict_extend(dict_T *d1, dict_T *d2, char
     hashitem_T	*hi2;
     int		todo;
     char_u	*arg_errmsg = (char_u *)N_("extend() argument");
+    type_T	*type;
+
+    if (d1->dv_type != NULL && d1->dv_type->tt_member != NULL)
+	type = d1->dv_type->tt_member;
+    else
+	type = NULL;
 
     todo = (int)d2->dv_hashtab.ht_used;
     for (hi2 = d2->dv_hashtab.ht_array; todo > 0; ++hi2)
@@ -1076,6 +1084,11 @@ dict_extend(dict_T *d1, dict_T *d2, char
 		if (!valid_varname(hi2->hi_key, TRUE))
 		    break;
 	    }
+
+	    if (type != NULL
+		     && check_typval_type(type, &HI2DI(hi2)->di_tv, 0) == FAIL)
+		break;
+
 	    if (di1 == NULL)
 	    {
 		di1 = dictitem_copy(HI2DI(hi2));
--- a/src/evalvars.c
+++ b/src/evalvars.c
@@ -3147,9 +3147,9 @@ set_var_const(
 	    di->di_flags &= ~DI_FLAGS_RELOAD;
 
 	    // A Vim9 script-local variable is also present in sn_all_vars and
-	    // sn_var_vals.
+	    // sn_var_vals.  It may set "type" from "tv".
 	    if (is_script_local && vim9script)
-		update_vim9_script_var(FALSE, di, tv, type);
+		update_vim9_script_var(FALSE, di, tv, &type);
 	}
 
 	// existing variable, need to clear the value
@@ -3237,9 +3237,9 @@ set_var_const(
 	    di->di_flags |= DI_FLAGS_LOCK;
 
 	// A Vim9 script-local variable is also added to sn_all_vars and
-	// sn_var_vals.
+	// sn_var_vals. It may set "type" from "tv".
 	if (is_script_local && vim9script)
-	    update_vim9_script_var(TRUE, di, tv, type);
+	    update_vim9_script_var(TRUE, di, tv, &type);
     }
 
     if (copy || tv->v_type == VAR_NUMBER || tv->v_type == VAR_FLOAT)
@@ -3251,6 +3251,14 @@ set_var_const(
 	init_tv(tv);
     }
 
+    if (vim9script && type != NULL)
+    {
+	if (type->tt_type == VAR_DICT && di->di_tv.vval.v_dict != NULL)
+	    di->di_tv.vval.v_dict->dv_type = alloc_type(type);
+	else if (type->tt_type == VAR_LIST && di->di_tv.vval.v_list != NULL)
+	    di->di_tv.vval.v_list->lv_type = alloc_type(type);
+    }
+
     // ":const var = value" locks the value
     // ":final var = value" locks "var"
     if (flags & ASSIGN_CONST)
--- a/src/list.c
+++ b/src/list.c
@@ -270,6 +270,7 @@ list_free_list(list_T  *l)
     if (l->lv_used_next != NULL)
 	l->lv_used_next->lv_used_prev = l->lv_used_prev;
 
+    free_type(l->lv_type);
     vim_free(l);
 }
 
@@ -689,13 +690,17 @@ list_append_number(list_T *l, varnumber_
 /*
  * Insert typval_T "tv" in list "l" before "item".
  * If "item" is NULL append at the end.
- * Return FAIL when out of memory.
+ * Return FAIL when out of memory or the type is wrong.
  */
     int
 list_insert_tv(list_T *l, typval_T *tv, listitem_T *item)
 {
-    listitem_T	*ni = listitem_alloc();
+    listitem_T	*ni;
 
+    if (l->lv_type != NULL && l->lv_type->tt_member != NULL
+		    && check_typval_type(l->lv_type->tt_member, tv, 0) == FAIL)
+	return FAIL;
+    ni = listitem_alloc();
     if (ni == NULL)
 	return FAIL;
     copy_tv(tv, &ni->li_tv);
--- a/src/proto/vim9script.pro
+++ b/src/proto/vim9script.pro
@@ -10,7 +10,7 @@ void ex_import(exarg_T *eap);
 int find_exported(int sid, char_u *name, ufunc_T **ufunc, type_T **type, cctx_T *cctx);
 char_u *handle_import(char_u *arg_start, garray_T *gap, int import_sid, evalarg_T *evalarg, void *cctx);
 char_u *vim9_declare_scriptvar(exarg_T *eap, char_u *arg);
-void update_vim9_script_var(int create, dictitem_T *di, typval_T *tv, type_T *type);
+void update_vim9_script_var(int create, dictitem_T *di, typval_T *tv, type_T **type);
 void hide_script_var(scriptitem_T *si, int idx, int func_defined);
 void free_all_script_vars(scriptitem_T *si);
 svar_T *find_typval_in_script(typval_T *dest);
--- a/src/structs.h
+++ b/src/structs.h
@@ -1481,6 +1481,7 @@ struct listvar_S
 	    int		lv_idx;		// cached index of an item
 	} mat;
     } lv_u;
+    type_T	*lv_type;	// allocated by alloc_type()
     list_T	*lv_copylist;	// copied list used by deepcopy()
     list_T	*lv_used_next;	// next list in used lists list
     list_T	*lv_used_prev;	// previous list in used lists list
@@ -1544,6 +1545,7 @@ struct dictvar_S
     int		dv_refcount;	// reference count
     int		dv_copyID;	// ID used by deepcopy()
     hashtab_T	dv_hashtab;	// hashtab that refers to the items
+    type_T	*dv_type;	// allocated by alloc_type()
     dict_T	*dv_copydict;	// copied dict used by deepcopy()
     dict_T	*dv_used_next;	// next dict in used dicts list
     dict_T	*dv_used_prev;	// previous dict in used dicts list
--- a/src/testdir/test_vim9_builtin.vim
+++ b/src/testdir/test_vim9_builtin.vim
@@ -252,6 +252,57 @@ def Test_extend_return_type()
   res->assert_equal(6)
 enddef
 
+func g:ExtendDict(d)
+  call extend(a:d, #{xx: 'x'})
+endfunc
+
+def Test_extend_dict_item_type()
+  var lines =<< trim END
+       var d: dict<number> = {a: 1}
+       extend(d, {b: 2})
+  END
+  CheckDefAndScriptSuccess(lines)
+
+  lines =<< trim END
+       var d: dict<number> = {a: 1}
+       extend(d, {b: 'x'})
+  END
+  CheckDefFailure(lines, 'E1013: Argument 2: type mismatch, expected dict<number> but got dict<string>', 2)
+  CheckScriptFailure(['vim9script'] + lines, 'E1012:', 3)
+
+  lines =<< trim END
+       var d: dict<number> = {a: 1}
+       g:ExtendDict(d)
+  END
+  CheckDefExecFailure(lines, 'E1012: Type mismatch; expected number but got string', 0)
+  CheckScriptFailure(['vim9script'] + lines, 'E1012:', 1)
+enddef
+
+func g:ExtendList(l)
+  call extend(a:l, ['x'])
+endfunc
+
+def Test_extend_list_item_type()
+  var lines =<< trim END
+       var l: list<number> = [1]
+       extend(l, [2])
+  END
+  CheckDefAndScriptSuccess(lines)
+
+  lines =<< trim END
+       var l: list<number> = [1]
+       extend(l, ['x'])
+  END
+  CheckDefFailure(lines, 'E1013: Argument 2: type mismatch, expected list<number> but got list<string>', 2)
+  CheckScriptFailure(['vim9script'] + lines, 'E1012:', 3)
+
+  lines =<< trim END
+       var l: list<number> = [1]
+       g:ExtendList(l)
+  END
+  CheckDefExecFailure(lines, 'E1012: Type mismatch; expected number but got string', 0)
+  CheckScriptFailure(['vim9script'] + lines, 'E1012:', 1)
+enddef
 
 def Wrong_dict_key_type(items: list<number>): list<number>
   return filter(items, (_, val) => get({[val]: 1}, 'x'))
--- a/src/testdir/test_vim9_disassemble.vim
+++ b/src/testdir/test_vim9_disassemble.vim
@@ -257,6 +257,7 @@ def Test_disassemble_store_member()
   assert_match('<SNR>\d*_ScriptFuncStoreMember\_s*' ..
         'var locallist: list<number> = []\_s*' ..
         '\d NEWLIST size 0\_s*' ..
+        '\d SETTYPE list<number>\_s*' ..
         '\d STORE $0\_s*' ..
         'locallist\[0\] = 123\_s*' ..
         '\d PUSHNR 123\_s*' ..
@@ -265,6 +266,7 @@ def Test_disassemble_store_member()
         '\d STORELIST\_s*' ..
         'var localdict: dict<number> = {}\_s*' ..
         '\d NEWDICT size 0\_s*' ..
+        '\d SETTYPE dict<number>\_s*' ..
         '\d STORE $1\_s*' ..
         'localdict\["a"\] = 456\_s*' ..
         '\d\+ PUSHNR 456\_s*' ..
@@ -347,6 +349,7 @@ def Test_disassemble_list_add()
   assert_match('<SNR>\d*_ListAdd\_s*' ..
         'var l: list<number> = []\_s*' ..
         '\d NEWLIST size 0\_s*' ..
+        '\d SETTYPE list<number>\_s*' ..
         '\d STORE $0\_s*' ..
         'add(l, 123)\_s*' ..
         '\d LOAD $0\_s*' ..
@@ -1034,6 +1037,7 @@ def Test_disassemble_for_loop()
   assert_match('ForLoop\_s*' ..
         'var res: list<number>\_s*' ..
         '\d NEWLIST size 0\_s*' ..
+        '\d SETTYPE list<number>\_s*' ..
         '\d STORE $0\_s*' ..
         'for i in range(3)\_s*' ..
         '\d STORE -1 in $1\_s*' ..
@@ -1137,6 +1141,7 @@ def Test_disassemble_typecast()
         '\d LOADG g:number\_s*' ..
         '\d CHECKTYPE number stack\[-1\]\_s*' ..
         '\d NEWLIST size 2\_s*' ..
+        '\d SETTYPE list<number>\_s*' ..
         '\d STORE $0\_s*' ..
         '\d PUSHNR 0\_s*' ..
         '\d RETURN\_s*',
--- a/src/version.c
+++ b/src/version.c
@@ -751,6 +751,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    2272,
+/**/
     2271,
 /**/
     2270,
--- a/src/vim9compile.c
+++ b/src/vim9compile.c
@@ -831,6 +831,20 @@ generate_TYPECHECK(
     return OK;
 }
 
+    static int
+generate_SETTYPE(
+	cctx_T	    *cctx,
+	type_T	    *expected)
+{
+    isn_T	*isn;
+
+    RETURN_OK_IF_SKIP(cctx);
+    if ((isn = generate_instr(cctx, ISN_SETTYPE)) == NULL)
+	return FAIL;
+    isn->isn_arg.type.ct_type = alloc_type(expected);
+    return OK;
+}
+
 /*
  * Return TRUE if "actual" could be "expected" and a runtime typecheck is to be
  * used.  Return FALSE if the types will never match.
@@ -6025,6 +6039,15 @@ compile_assignment(char_u *arg, exarg_T 
 		// ":const var": lock the value, but not referenced variables
 		generate_LOCKCONST(cctx);
 
+	    if (is_decl
+		    && (type->tt_type == VAR_DICT || type->tt_type == VAR_LIST)
+		    && type->tt_member != NULL
+		    && type->tt_member != &t_any
+		    && type->tt_member != &t_unknown)
+		// Set the type in the list or dict, so that it can be checked,
+		// also in legacy script.
+		generate_SETTYPE(cctx, type);
+
 	    if (dest != dest_local)
 	    {
 		if (generate_store_var(cctx, dest, opt_flags, vimvaridx,
@@ -8193,6 +8216,7 @@ delete_instr(isn_T *isn)
 	    break;
 
 	case ISN_CHECKTYPE:
+	case ISN_SETTYPE:
 	    free_type(isn->isn_arg.type.ct_type);
 	    break;
 
--- a/src/vim9execute.c
+++ b/src/vim9execute.c
@@ -2994,6 +2994,24 @@ call_def_function(
 		}
 		break;
 
+	    case ISN_SETTYPE:
+		{
+		    checktype_T *ct = &iptr->isn_arg.type;
+
+		    tv = STACK_TV_BOT(-1);
+		    if (tv->v_type == VAR_DICT && tv->vval.v_dict != NULL)
+		    {
+			free_type(tv->vval.v_dict->dv_type);
+			tv->vval.v_dict->dv_type = alloc_type(ct->ct_type);
+		    }
+		    else if (tv->v_type == VAR_LIST && tv->vval.v_list != NULL)
+		    {
+			free_type(tv->vval.v_list->lv_type);
+			tv->vval.v_list->lv_type = alloc_type(ct->ct_type);
+		    }
+		}
+		break;
+
 	    case ISN_2BOOL:
 	    case ISN_COND2BOOL:
 		{
@@ -3890,6 +3908,15 @@ ex_disassemble(exarg_T *eap)
 				iptr->isn_arg.checklen.cl_more_OK ? ">= " : "",
 				iptr->isn_arg.checklen.cl_min_len);
 			       break;
+	    case ISN_SETTYPE:
+		  {
+		      char *tofree;
+
+		      smsg("%4d SETTYPE %s", current,
+			      type_name(iptr->isn_arg.type.ct_type, &tofree));
+		      vim_free(tofree);
+		      break;
+		  }
 	    case ISN_COND2BOOL: smsg("%4d COND2BOOL", current); break;
 	    case ISN_2BOOL: if (iptr->isn_arg.number)
 				smsg("%4d INVERT (!val)", current);
--- a/src/vim9script.c
+++ b/src/vim9script.c
@@ -661,10 +661,10 @@ vim9_declare_scriptvar(exarg_T *eap, cha
  * with a hashtable) and sn_var_vals (lookup by index).
  * When "create" is TRUE this is a new variable, otherwise find and update an
  * existing variable.
- * When "type" is NULL use "tv" for the type.
+ * When "*type" is NULL use "tv" for the type and update "*type".
  */
     void
-update_vim9_script_var(int create, dictitem_T *di, typval_T *tv, type_T *type)
+update_vim9_script_var(int create, dictitem_T *di, typval_T *tv, type_T **type)
 {
     scriptitem_T    *si = SCRIPT_ITEM(current_sctx.sc_sid);
     hashitem_T	    *hi;
@@ -715,10 +715,9 @@ update_vim9_script_var(int create, dicti
     }
     if (sv != NULL)
     {
-	if (type == NULL)
-	    sv->sv_type = typval2type(tv, &si->sn_type_list);
-	else
-	    sv->sv_type = type;
+	if (*type == NULL)
+	    *type = typval2type(tv, &si->sn_type_list);
+	sv->sv_type = *type;
     }
 
     // let ex_export() know the export worked.