To: vim_dev@googlegroups.com Subject: Patch 8.2.2272 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ 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 = {a: 1} + extend(d, {b: 2}) + END + CheckDefAndScriptSuccess(lines) + + lines =<< trim END + var d: dict = {a: 1} + extend(d, {b: 'x'}) + END + CheckDefFailure(lines, 'E1013: Argument 2: type mismatch, expected dict but got dict', 2) + CheckScriptFailure(['vim9script'] + lines, 'E1012:', 3) + + lines =<< trim END + var d: dict = {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 = [1] + extend(l, [2]) + END + CheckDefAndScriptSuccess(lines) + + lines =<< trim END + var l: list = [1] + extend(l, ['x']) + END + CheckDefFailure(lines, 'E1013: Argument 2: type mismatch, expected list but got list', 2) + CheckScriptFailure(['vim9script'] + lines, 'E1012:', 3) + + lines =<< trim END + var l: list = [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): list 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('\d*_ScriptFuncStoreMember\_s*' .. 'var locallist: list = []\_s*' .. '\d NEWLIST size 0\_s*' .. + '\d SETTYPE list\_s*' .. '\d STORE $0\_s*' .. 'locallist\[0\] = 123\_s*' .. '\d PUSHNR 123\_s*' .. *************** *** 265,270 **** --- 266,272 ---- '\d STORELIST\_s*' .. 'var localdict: dict = {}\_s*' .. '\d NEWDICT size 0\_s*' .. + '\d SETTYPE dict\_s*' .. '\d STORE $1\_s*' .. 'localdict\["a"\] = 456\_s*' .. '\d\+ PUSHNR 456\_s*' .. *************** *** 347,352 **** --- 349,355 ---- assert_match('\d*_ListAdd\_s*' .. 'var l: list = []\_s*' .. '\d NEWLIST size 0\_s*' .. + '\d SETTYPE list\_s*' .. '\d STORE $0\_s*' .. 'add(l, 123)\_s*' .. '\d LOAD $0\_s*' .. *************** *** 1034,1039 **** --- 1037,1043 ---- assert_match('ForLoop\_s*' .. 'var res: list\_s*' .. '\d NEWLIST size 0\_s*' .. + '\d SETTYPE list\_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\_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 -- 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 ///