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:
parent
3e0107ea16
commit
aa210a3aec
13
src/dict.c
13
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));
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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'))
|
||||
|
@ -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*',
|
||||
|
@ -750,6 +750,8 @@ static char *(features[]) =
|
||||
|
||||
static int included_patches[] =
|
||||
{ /* Add new patch number below this line */
|
||||
/**/
|
||||
2272,
|
||||
/**/
|
||||
2271,
|
||||
/**/
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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.
|
||||
|
Loading…
x
Reference in New Issue
Block a user