summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBram Moolenaar <Bram@vim.org>2021-01-02 15:41:03 +0100
committerBram Moolenaar <Bram@vim.org>2021-01-02 15:41:03 +0100
commitaa210a3aeccc33c6051978017959126b037f94af (patch)
tree29c2f91c51dc55a52e427f89643ae4b9c4c56c58
parent3e0107ea16349b354e0e9712e95b09ef019e99e5 (diff)
patch 8.2.2272: Vim9: extend() can violate the type of a variablev8.2.2272
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)
-rw-r--r--src/dict.c13
-rw-r--r--src/evalvars.c16
-rw-r--r--src/list.c9
-rw-r--r--src/proto/vim9script.pro2
-rw-r--r--src/structs.h2
-rw-r--r--src/testdir/test_vim9_builtin.vim51
-rw-r--r--src/testdir/test_vim9_disassemble.vim5
-rw-r--r--src/version.c2
-rw-r--r--src/vim9compile.c24
-rw-r--r--src/vim9execute.c27
-rw-r--r--src/vim9script.c11
11 files changed, 149 insertions, 13 deletions
diff --git a/src/dict.c b/src/dict.c
index 5abb9964ae..fc8756c57b 100644
--- a/src/dict.c
+++ b/src/dict.c
@@ -107,6 +107,8 @@ rettv_dict_set(typval_T *rettv, dict_T *d)
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_u *action)
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_u *action)
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));
diff --git a/src/evalvars.c b/src/evalvars.c
index 94b857ed9b..7fb190c3db 100644
--- 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)
diff --git a/src/list.c b/src/list.c
index 1c41f07031..56ee5a5778 100644
--- 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_T n)
/*
* 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);
diff --git a/src/proto/vim9script.pro b/src/proto/vim9script.pro
index 0eced60850..833d44b233 100644
--- 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);
diff --git a/src/structs.h b/src/structs.h
index 102be0eaaf..e2045104ca 100644
--- 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
diff --git a/src/testdir/test_vim9_builtin.vim b/src/testdir/test_vim9_builtin.vim
index accbca237b..c86b0df6c4 100644
--- 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'))
diff --git a/src/testdir/test_vim9_disassemble.vim b/src/testdir/test_vim9_disassemble.vim
index fa51224250..b0f84a6e1e 100644
--- 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*',
diff --git a/src/version.c b/src/version.c
index d6e92b188b..94a22c00da 100644
--- 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,
diff --git a/src/vim9compile.c b/src/vim9compile.c
index 303573f914..bb33d38e9e 100644
--- 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 *eap, cmdidx_T cmdidx, cctx_T *cctx)
// ":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;
diff --git a/src/vim9execute.c b/src/vim9execute.c
index 5721c0e723..5cfc327e8e 100644
--- 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);
diff --git a/src/vim9script.c b/src/vim9script.c
index 269300c5a7..b7c0f67d19 100644
--- a/src/vim9script.c
+++ b/src/vim9script.c
@@ -661,10 +661,10 @@ vim9_declare_scriptvar(exarg_T *eap, char_u *arg)
* 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, dictitem_T *di, typval_T *tv, type_T *type)
}
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.