Patch 8.2.2179
Problem: Vim9: crash when indexing a dict with a number.
Solution: Add ISN_STOREINDEX. (closes #7513)
Files: src/vim9compile.c, src/vim9execute.c, src/vim9.h,
src/errors.h, src/testdir/test_vim9_assign.vim,
src/testdir/test_vim9_disassemble.vim
*** ../vim-8.2.2178/src/vim9compile.c 2020-12-20 21:43:31.704882194 +0100
--- src/vim9compile.c 2020-12-21 16:58:04.803979235 +0100
***************
*** 5904,5929 ****
if (type == &t_any)
{
! type_T *idx_type = ((type_T **)stack->ga_data)[
! stack->ga_len - 1];
! // Index on variable of unknown type: guess the type from the
! // index type: number is dict, otherwise dict.
! // TODO: should do the assignment at runtime
! if (idx_type->tt_type == VAR_NUMBER)
! type = &t_list_any;
! else
! type = &t_dict_any;
}
! dest_type = type->tt_type;
! if (dest_type == VAR_DICT
! && may_generate_2STRING(-1, cctx) == FAIL)
! goto theend;
! if (dest_type == VAR_LIST
! && ((type_T **)stack->ga_data)[stack->ga_len - 1]->tt_type
! != VAR_NUMBER)
{
! emsg(_(e_number_exp));
! goto theend;
}
// Load the dict or list. On the stack we then have:
--- 5904,5925 ----
if (type == &t_any)
{
! // Index on variable of unknown type: check at runtime.
! dest_type = VAR_ANY;
}
! else
{
! dest_type = type->tt_type;
! if (dest_type == VAR_DICT
! && may_generate_2STRING(-1, cctx) == FAIL)
! goto theend;
! if (dest_type == VAR_LIST
! && ((type_T **)stack->ga_data)[stack->ga_len - 1]->tt_type
! != VAR_NUMBER)
! {
! emsg(_(e_number_exp));
! goto theend;
! }
}
// Load the dict or list. On the stack we then have:
***************
*** 5956,5970 ****
else
generate_loadvar(cctx, dest, name, lvar, type);
! if (dest_type == VAR_LIST)
{
! if (generate_instr_drop(cctx, ISN_STORELIST, 3) == FAIL)
! goto theend;
! }
! else if (dest_type == VAR_DICT)
! {
! if (generate_instr_drop(cctx, ISN_STOREDICT, 3) == FAIL)
goto theend;
}
else
{
--- 5952,5965 ----
else
generate_loadvar(cctx, dest, name, lvar, type);
! if (dest_type == VAR_LIST || dest_type == VAR_DICT
! || dest_type == VAR_ANY)
{
! isn_T *isn = generate_instr_drop(cctx, ISN_STOREINDEX, 3);
!
! if (isn == NULL)
goto theend;
+ isn->isn_arg.vartype = dest_type;
}
else
{
***************
*** 8194,8201 ****
case ISN_SHUFFLE:
case ISN_SLICE:
case ISN_STORE:
! case ISN_STOREDICT:
! case ISN_STORELIST:
case ISN_STORENR:
case ISN_STOREOUTER:
case ISN_STOREREG:
--- 8189,8195 ----
case ISN_SHUFFLE:
case ISN_SLICE:
case ISN_STORE:
! case ISN_STOREINDEX:
case ISN_STORENR:
case ISN_STOREOUTER:
case ISN_STOREREG:
*** ../vim-8.2.2178/src/vim9execute.c 2020-12-20 21:43:31.704882194 +0100
--- src/vim9execute.c 2020-12-21 17:14:17.544959681 +0100
***************
*** 802,807 ****
--- 802,839 ----
}
/*
+ * Convert "tv" to a string.
+ * Return FAIL if not allowed.
+ */
+ static int
+ do_2string(typval_T *tv, int is_2string_any)
+ {
+ if (tv->v_type != VAR_STRING)
+ {
+ char_u *str;
+
+ if (is_2string_any)
+ {
+ switch (tv->v_type)
+ {
+ case VAR_SPECIAL:
+ case VAR_BOOL:
+ case VAR_NUMBER:
+ case VAR_FLOAT:
+ case VAR_BLOB: break;
+ default: to_string_error(tv->v_type);
+ return FAIL;
+ }
+ }
+ str = typval_tostring(tv);
+ clear_tv(tv);
+ tv->v_type = VAR_STRING;
+ tv->vval.v_string = str;
+ }
+ return OK;
+ }
+
+ /*
* When the value of "sv" is a null list of dict, allocate it.
*/
static void
***************
*** 1700,1791 ****
tv->vval.v_number = iptr->isn_arg.storenr.stnr_val;
break;
! // store value in list variable
! case ISN_STORELIST:
{
typval_T *tv_idx = STACK_TV_BOT(-2);
! varnumber_T lidx = tv_idx->vval.v_number;
! typval_T *tv_list = STACK_TV_BOT(-1);
! list_T *list = tv_list->vval.v_list;
SOURCING_LNUM = iptr->isn_lnum;
! if (lidx < 0 && list->lv_len + lidx >= 0)
! // negative index is relative to the end
! lidx = list->lv_len + lidx;
! if (lidx < 0 || lidx > list->lv_len)
{
! semsg(_(e_listidx), lidx);
! goto on_error;
}
! tv = STACK_TV_BOT(-3);
! if (lidx < list->lv_len)
{
! listitem_T *li = list_find(list, lidx);
!
! if (error_if_locked(li->li_tv.v_lock,
! e_cannot_change_list_item))
! goto failed;
! // overwrite existing list item
! clear_tv(&li->li_tv);
! li->li_tv = *tv;
}
! else
{
! if (error_if_locked(list->lv_lock,
! e_cannot_change_list))
! goto failed;
! // append to list, only fails when out of memory
! if (list_append_tv(list, tv) == FAIL)
! goto failed;
! clear_tv(tv);
! }
! clear_tv(tv_idx);
! clear_tv(tv_list);
! ectx.ec_stack.ga_len -= 3;
! }
! break;
! // store value in dict variable
! case ISN_STOREDICT:
! {
! typval_T *tv_key = STACK_TV_BOT(-2);
! char_u *key = tv_key->vval.v_string;
! typval_T *tv_dict = STACK_TV_BOT(-1);
! dict_T *dict = tv_dict->vval.v_dict;
! dictitem_T *di;
! SOURCING_LNUM = iptr->isn_lnum;
! if (dict == NULL)
! {
! emsg(_(e_dictionary_not_set));
! goto on_error;
}
! if (key == NULL)
! key = (char_u *)"";
! tv = STACK_TV_BOT(-3);
! di = dict_find(dict, key, -1);
! if (di != NULL)
{
! if (error_if_locked(di->di_tv.v_lock,
e_cannot_change_dict_item))
! goto failed;
! // overwrite existing value
! clear_tv(&di->di_tv);
! di->di_tv = *tv;
}
else
{
! if (error_if_locked(dict->dv_lock,
! e_cannot_change_dict))
! goto failed;
! // add to dict, only fails when out of memory
! if (dict_add_tv(dict, (char *)key, tv) == FAIL)
! goto failed;
! clear_tv(tv);
}
! clear_tv(tv_key);
! clear_tv(tv_dict);
ectx.ec_stack.ga_len -= 3;
}
break;
--- 1732,1857 ----
tv->vval.v_number = iptr->isn_arg.storenr.stnr_val;
break;
! // store value in list or dict variable
! case ISN_STOREINDEX:
{
+ vartype_T dest_type = iptr->isn_arg.vartype;
typval_T *tv_idx = STACK_TV_BOT(-2);
! typval_T *tv_dest = STACK_TV_BOT(-1);
! int status = OK;
+ tv = STACK_TV_BOT(-3);
SOURCING_LNUM = iptr->isn_lnum;
! if (dest_type == VAR_ANY)
{
! dest_type = tv_dest->v_type;
! if (dest_type == VAR_DICT)
! status = do_2string(tv_idx, TRUE);
! else if (dest_type == VAR_LIST
! && tv_idx->v_type != VAR_NUMBER)
! {
! emsg(_(e_number_exp));
! status = FAIL;
! }
}
! else if (dest_type != tv_dest->v_type)
{
! // just in case, should be OK
! semsg(_(e_expected_str_but_got_str),
! vartype_name(dest_type),
! vartype_name(tv_dest->v_type));
! status = FAIL;
}
!
! if (status == OK && dest_type == VAR_LIST)
{
! varnumber_T lidx = tv_idx->vval.v_number;
! list_T *list = tv_dest->vval.v_list;
! if (list == NULL)
! {
! emsg(_(e_list_not_set));
! goto on_error;
! }
! if (lidx < 0 && list->lv_len + lidx >= 0)
! // negative index is relative to the end
! lidx = list->lv_len + lidx;
! if (lidx < 0 || lidx > list->lv_len)
! {
! semsg(_(e_listidx), lidx);
! goto on_error;
! }
! if (lidx < list->lv_len)
! {
! listitem_T *li = list_find(list, lidx);
! if (error_if_locked(li->li_tv.v_lock,
! e_cannot_change_list_item))
! goto on_error;
! // overwrite existing list item
! clear_tv(&li->li_tv);
! li->li_tv = *tv;
! }
! else
! {
! if (error_if_locked(list->lv_lock,
! e_cannot_change_list))
! goto on_error;
! // append to list, only fails when out of memory
! if (list_append_tv(list, tv) == FAIL)
! goto failed;
! clear_tv(tv);
! }
}
! else if (status == OK && dest_type == VAR_DICT)
{
! char_u *key = tv_idx->vval.v_string;
! dict_T *dict = tv_dest->vval.v_dict;
! dictitem_T *di;
!
! SOURCING_LNUM = iptr->isn_lnum;
! if (dict == NULL)
! {
! emsg(_(e_dictionary_not_set));
! goto on_error;
! }
! if (key == NULL)
! key = (char_u *)"";
! di = dict_find(dict, key, -1);
! if (di != NULL)
! {
! if (error_if_locked(di->di_tv.v_lock,
e_cannot_change_dict_item))
! goto on_error;
! // overwrite existing value
! clear_tv(&di->di_tv);
! di->di_tv = *tv;
! }
! else
! {
! if (error_if_locked(dict->dv_lock,
! e_cannot_change_dict))
! goto on_error;
! // add to dict, only fails when out of memory
! if (dict_add_tv(dict, (char *)key, tv) == FAIL)
! goto failed;
! clear_tv(tv);
! }
}
else
{
! status = FAIL;
! semsg(_(e_cannot_index_str), vartype_name(dest_type));
}
!
! clear_tv(tv_idx);
! clear_tv(tv_dest);
ectx.ec_stack.ga_len -= 3;
+ if (status == FAIL)
+ {
+ clear_tv(tv);
+ goto on_error;
+ }
}
break;
***************
*** 2921,2951 ****
case ISN_2STRING:
case ISN_2STRING_ANY:
! {
! char_u *str;
!
! tv = STACK_TV_BOT(iptr->isn_arg.number);
! if (tv->v_type != VAR_STRING)
! {
! if (iptr->isn_type == ISN_2STRING_ANY)
! {
! switch (tv->v_type)
! {
! case VAR_SPECIAL:
! case VAR_BOOL:
! case VAR_NUMBER:
! case VAR_FLOAT:
! case VAR_BLOB: break;
! default: to_string_error(tv->v_type);
! goto on_error;
! }
! }
! str = typval_tostring(tv);
! clear_tv(tv);
! tv->v_type = VAR_STRING;
! tv->vval.v_string = str;
! }
! }
break;
case ISN_RANGE:
--- 2987,2995 ----
case ISN_2STRING:
case ISN_2STRING_ANY:
! if (do_2string(STACK_TV_BOT(iptr->isn_arg.number),
! iptr->isn_type == ISN_2STRING_ANY) == FAIL)
! goto on_error;
break;
case ISN_RANGE:
***************
*** 3462,3473 ****
iptr->isn_arg.storenr.stnr_idx);
break;
! case ISN_STORELIST:
! smsg("%4d STORELIST", current);
! break;
!
! case ISN_STOREDICT:
! smsg("%4d STOREDICT", current);
break;
// constants
--- 3506,3525 ----
iptr->isn_arg.storenr.stnr_idx);
break;
! case ISN_STOREINDEX:
! switch (iptr->isn_arg.vartype)
! {
! case VAR_LIST:
! smsg("%4d STORELIST", current);
! break;
! case VAR_DICT:
! smsg("%4d STOREDICT", current);
! break;
! case VAR_ANY:
! smsg("%4d STOREINDEX", current);
! break;
! default: break;
! }
break;
// constants
*** ../vim-8.2.2178/src/vim9.h 2020-12-19 16:30:39.439810130 +0100
--- src/vim9.h 2020-12-21 16:44:31.186583018 +0100
***************
*** 55,62 ****
// ISN_STOREOTHER, // pop into other script variable isn_arg.other.
ISN_STORENR, // store number into local variable isn_arg.storenr.stnr_idx
! ISN_STORELIST, // store into list, value/index/variable on stack
! ISN_STOREDICT, // store into dictionary, value/index/variable on stack
ISN_UNLET, // unlet variable isn_arg.unlet.ul_name
ISN_UNLETENV, // unlet environment variable isn_arg.unlet.ul_name
--- 55,62 ----
// ISN_STOREOTHER, // pop into other script variable isn_arg.other.
ISN_STORENR, // store number into local variable isn_arg.storenr.stnr_idx
! ISN_STOREINDEX, // store into list or dictionary, type isn_arg.vartype,
! // value/index/variable on stack
ISN_UNLET, // unlet variable isn_arg.unlet.ul_name
ISN_UNLETENV, // unlet environment variable isn_arg.unlet.ul_name
***************
*** 304,309 ****
--- 304,310 ----
char_u *string;
varnumber_T number;
blob_T *blob;
+ vartype_T vartype;
#ifdef FEAT_FLOAT
float_T fnumber;
#endif
*** ../vim-8.2.2178/src/errors.h 2020-12-20 21:43:31.704882194 +0100
--- src/errors.h 2020-12-21 16:58:58.399828342 +0100
***************
*** 323,325 ****
--- 323,329 ----
INIT(= N_("E1145: Missing heredoc end marker: %s"));
EXTERN char e_command_not_recognized_str[]
INIT(= N_("E1146: Command not recognized: %s"));
+ EXTERN char e_list_not_set[]
+ INIT(= N_("E1147: List not set"));
+ EXTERN char e_cannot_index_str[]
+ INIT(= N_("E1148: Cannot index a %s"));
*** ../vim-8.2.2178/src/testdir/test_vim9_assign.vim 2020-12-20 15:20:53.326899494 +0100
--- src/testdir/test_vim9_assign.vim 2020-12-21 17:30:16.037772894 +0100
***************
*** 533,538 ****
--- 533,544 ----
# type becomes list<any>
var somelist = rand() > 0 ? [1, 2, 3] : ['a', 'b', 'c']
+
+ var lines =<< trim END
+ var d = {dd: test_null_list()}
+ d.dd[0] = 0
+ END
+ CheckDefExecFailure(lines, 'E1147:', 2)
enddef
def Test_assignment_list_vim9script()
***************
*** 567,578 ****
assert_equal({nest: {this: 123, that: 456}, nr: 0}, anydict)
var lines =<< trim END
- vim9script
var dd = {}
dd.two = 2
assert_equal({two: 2}, dd)
END
! CheckScriptSuccess(lines)
lines =<< trim END
var dd = {one: 1}
--- 573,592 ----
assert_equal({nest: {this: 123, that: 456}, nr: 0}, anydict)
var lines =<< trim END
var dd = {}
dd.two = 2
assert_equal({two: 2}, dd)
END
! CheckDefAndScriptSuccess(lines)
!
! lines =<< trim END
! var d = {dd: {}}
! d.dd[0] = 2
! d.dd['x'] = 3
! d.dd.y = 4
! assert_equal({dd: {0: 2, x: 3, y: 4}}, d)
! END
! CheckDefAndScriptSuccess(lines)
lines =<< trim END
var dd = {one: 1}
***************
*** 641,646 ****
--- 655,672 ----
assert_equal({a: 43}, FillDict())
END
CheckScriptSuccess(lines)
+
+ lines =<< trim END
+ var d = {dd: test_null_dict()}
+ d.dd[0] = 0
+ END
+ CheckDefExecFailure(lines, 'E1103:', 2)
+
+ lines =<< trim END
+ var d = {dd: 'string'}
+ d.dd[0] = 0
+ END
+ CheckDefExecFailure(lines, 'E1148:', 2)
enddef
def Test_assignment_local()
*** ../vim-8.2.2178/src/testdir/test_vim9_disassemble.vim 2020-12-19 16:30:39.439810130 +0100
--- src/testdir/test_vim9_disassemble.vim 2020-12-21 17:22:05.995410794 +0100
***************
*** 276,281 ****
--- 276,305 ----
res)
enddef
+ def s:ScriptFuncStoreIndex()
+ var d = {dd: {}}
+ d.dd[0] = 0
+ enddef
+
+ def Test_disassemble_store_index()
+ var res = execute('disass s:ScriptFuncStoreIndex')
+ assert_match('<SNR>\d*_ScriptFuncStoreIndex\_s*' ..
+ 'var d = {dd: {}}\_s*' ..
+ '\d PUSHS "dd"\_s*' ..
+ '\d NEWDICT size 0\_s*' ..
+ '\d NEWDICT size 1\_s*' ..
+ '\d STORE $0\_s*' ..
+ 'd.dd\[0\] = 0\_s*' ..
+ '\d PUSHNR 0\_s*' ..
+ '\d PUSHNR 0\_s*' ..
+ '\d LOAD $0\_s*' ..
+ '\d MEMBER dd\_s*' ..
+ '\d STOREINDEX\_s*' ..
+ '\d\+ PUSHNR 0\_s*' ..
+ '\d\+ RETURN',
+ res)
+ enddef
+
def s:ListAssign()
var x: string
var y: string
*** ../vim-8.2.2178/src/version.c 2020-12-21 16:02:58.486392542 +0100
--- src/version.c 2020-12-21 16:32:12.372718497 +0100
***************
*** 752,753 ****
--- 752,755 ----
{ /* Add new patch number below this line */
+ /**/
+ 2179,
/**/
--
EXPERIENCE - experience is a wonderful thing. It enables you to
recognise a mistake when you make it again.
/// 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 ///