Patch 8.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)
Files: src/structs.h, src/evalvars.c, src/dict.c, src/list.c,
src/vim9script.c, src/proto/
vim9script.pro, src/vim9compile.c,
src/vim9execute.c, src/testdir/test_vim9_builtin.vim,
src/testdir/test_vim9_disassemble.vim
*** ../vim-8.2.2271/src/structs.h 2020-12-29 11:14:58.444606193 +0100
--- src/structs.h 2021-01-02 15:15:41.026043142 +0100
***************
*** 1481,1486 ****
--- 1481,1487 ----
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,1549 ****
--- 1545,1551 ----
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
*** ../vim-8.2.2271/src/evalvars.c 2021-01-01 21:05:51.222773812 +0100
--- src/evalvars.c 2021-01-02 15:20:03.281208564 +0100
***************
*** 3147,3155 ****
di->di_flags &= ~DI_FLAGS_RELOAD;
// A Vim9 script-local variable is also present in sn_all_vars and
! // sn_var_vals.
if (is_script_local && vim9script)
! update_vim9_script_var(FALSE, di, tv, type);
}
// existing variable, need to clear the value
--- 3147,3155 ----
di->di_flags &= ~DI_FLAGS_RELOAD;
// A Vim9 script-local variable is also present in sn_all_vars and
! // sn_var_vals. It may set "type" from "tv".
if (is_script_local && vim9script)
! update_vim9_script_var(FALSE, di, tv, &type);
}
// existing variable, need to clear the value
***************
*** 3237,3245 ****
di->di_flags |= DI_FLAGS_LOCK;
// A Vim9 script-local variable is also added to sn_all_vars and
! // sn_var_vals.
if (is_script_local && vim9script)
! update_vim9_script_var(TRUE, di, tv, type);
}
if (copy || tv->v_type == VAR_NUMBER || tv->v_type == VAR_FLOAT)
--- 3237,3245 ----
di->di_flags |= DI_FLAGS_LOCK;
// A Vim9 script-local variable is also added to sn_all_vars and
! // sn_var_vals. It may set "type" from "tv".
if (is_script_local && vim9script)
! update_vim9_script_var(TRUE, di, tv, &type);
}
if (copy || tv->v_type == VAR_NUMBER || tv->v_type == VAR_FLOAT)
***************
*** 3251,3256 ****
--- 3251,3264 ----
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)
*** ../vim-8.2.2271/src/dict.c 2020-12-19 16:30:39.439810130 +0100
--- src/dict.c 2021-01-02 14:39:43.369485535 +0100
***************
*** 107,112 ****
--- 107,114 ----
dict_free_contents(dict_T *d)
{
hashtab_free_contents(&d->dv_hashtab);
+ free_type(d->dv_type);
+ d->dv_type = NULL;
}
/*
***************
*** 1057,1062 ****
--- 1059,1070 ----
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,1081 ****
--- 1084,1094 ----
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));
*** ../vim-8.2.2271/src/list.c 2020-12-18 19:49:52.341571870 +0100
--- src/list.c 2021-01-02 15:25:47.796089768 +0100
***************
*** 270,275 ****
--- 270,276 ----
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,701 ****
/*
* Insert typval_T "tv" in list "l" before "item".
* If "item" is NULL append at the end.
! * Return FAIL when out of memory.
*/
int
list_insert_tv(list_T *l, typval_T *tv, listitem_T *item)
{
! listitem_T *ni = listitem_alloc();
if (ni == NULL)
return FAIL;
copy_tv(tv, &ni->li_tv);
--- 690,706 ----
/*
* Insert typval_T "tv" in list "l" before "item".
* If "item" is NULL append at the end.
! * 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;
+ 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);
*** ../vim-8.2.2271/src/vim9script.c 2020-12-28 20:53:17.495051906 +0100
--- src/vim9script.c 2021-01-02 14:24:36.032723073 +0100
***************
*** 661,670 ****
* 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.
*/
void
! 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;
--- 661,670 ----
* 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 and update "*type".
*/
void
! 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,724 ****
}
if (sv != NULL)
{
! if (type == NULL)
! sv->sv_type = typval2type(tv, &si->sn_type_list);
! else
! sv->sv_type = type;
}
// let ex_export() know the export worked.
--- 715,723 ----
}
if (sv != NULL)
{
! if (*type == NULL)
! *type = typval2type(tv, &si->sn_type_list);
! sv->sv_type = *type;
}
// let ex_export() know the export worked.
*** ../vim-8.2.2271/src/proto/
vim9script.pro 2020-12-28 20:53:17.495051906 +0100
--- src/proto/
vim9script.pro 2021-01-02 14:24:41.376706425 +0100
***************
*** 10,16 ****
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 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);
--- 10,16 ----
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 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);
*** ../vim-8.2.2271/src/vim9compile.c 2021-01-01 21:05:51.222773812 +0100
--- src/vim9compile.c 2021-01-02 15:28:28.375563450 +0100
***************
*** 831,836 ****
--- 831,850 ----
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,6030 ****
--- 6039,6053 ----
// ":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,8198 ****
--- 8216,8222 ----
break;
case ISN_CHECKTYPE:
+ case ISN_SETTYPE:
free_type(isn->isn_arg.type.ct_type);
break;
*** ../vim-8.2.2271/src/vim9execute.c 2021-01-02 12:45:38.234364061 +0100
--- src/vim9execute.c 2021-01-02 15:29:40.923324946 +0100
***************
*** 2994,2999 ****
--- 2994,3017 ----
}
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,3895 ****
--- 3908,3922 ----
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);
*** ../vim-8.2.2271/src/testdir/test_vim9_builtin.vim 2020-12-31 21:28:43.419217945 +0100
--- src/testdir/test_vim9_builtin.vim 2021-01-02 15:34:44.566323195 +0100
***************
*** 252,257 ****
--- 252,308 ----
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'))
*** ../vim-8.2.2271/src/testdir/test_vim9_disassemble.vim 2021-01-01 15:11:00.008727261 +0100
--- src/testdir/test_vim9_disassemble.vim 2021-01-02 15:38:07.033653093 +0100
***************
*** 257,262 ****
--- 257,263 ----
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,270 ****
--- 266,272 ----
'\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,352 ****
--- 349,355 ----
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,1039 ****
--- 1037,1043 ----
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,1142 ****
--- 1141,1147 ----
'\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*',
*** ../vim-8.2.2271/src/version.c 2021-01-02 13:53:55.345783905 +0100
--- src/version.c 2021-01-02 14:27:31.904172044 +0100
***************
*** 752,753 ****
--- 752,755 ----
{ /* Add new patch number below this line */
+ /**/
+ 2272,
/**/
--
From "know your smileys":
¯\_(ツ)_/¯ Shrug
/// Bram Moolenaar -- Br...@Moolenaar.net --
http://www.Moolenaar.net \\\
/// sponsor Vim, vote for features --
http://www.Vim.org/sponsor/ \\\
\\\ an exciting new programming language --
http://www.Zimbu.org ///
\\\ help me help AIDS victims --
http://ICCF-Holland.org ///