0
0
mirror of https://github.com/vim/vim.git synced 2025-07-26 11:04:33 -04:00

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)
This commit is contained in:
Bram Moolenaar 2021-01-02 15:41:03 +01:00
parent 3e0107ea16
commit aa210a3aec
11 changed files with 149 additions and 13 deletions

View File

@ -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));

View File

@ -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)

View File

@ -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);

View File

@ -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);

View File

@ -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

View File

@ -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'))

View File

@ -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*',

View File

@ -750,6 +750,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
2272,
/**/
2271,
/**/

View File

@ -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;

View File

@ -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);

View File

@ -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.