To: vim_dev@googlegroups.com Subject: Patch 8.2.2179 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ 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 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('\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 -- Bram@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 ///