]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 8.2.2272: Vim9: extend() can violate the type of a variable v8.2.2272
authorBram Moolenaar <Bram@vim.org>
Sat, 2 Jan 2021 14:41:03 +0000 (15:41 +0100)
committerBram Moolenaar <Bram@vim.org>
Sat, 2 Jan 2021 14:41:03 +0000 (15:41 +0100)
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)

src/dict.c
src/evalvars.c
src/list.c
src/proto/vim9script.pro
src/structs.h
src/testdir/test_vim9_builtin.vim
src/testdir/test_vim9_disassemble.vim
src/version.c
src/vim9compile.c
src/vim9execute.c
src/vim9script.c

index 5abb9964ae6180ec11bc35b016a217318f648c15..fc8756c57bc32d931aa5b718dcc0cced51cc3618 100644 (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));
index 94b857ed9bcfe2cf2d1753ccecee844c331b80bd..7fb190c3db9c6aef5488b340bb4e90f3d72aabba 100644 (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)
index 1c41f07031efd83dd57619671dbb69a2903f09e0..56ee5a5778bf5362866e2f146c90cec44ec2477c 100644 (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);
index 0eced60850f8cfec8f81c568476aa1315bd47a03..833d44b2331abfee4431bfd1adf886432ab3e9f2 100644 (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);
index 102be0eaaf3450fc78c338f15c1c7a8d939b49de..e2045104ca03158868d73dded883814b751a59ed 100644 (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
index accbca237b970543def9384702e15dbe8cf5185a..c86b0df6c489d98e734f9a8b30c673fc5f923826 100644 (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'))
index fa512242501971c8dad567b8e2a232756e5c28bf..b0f84a6e1e961b26bfc2de2abb03aaea908f3be9 100644 (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*',
index d6e92b188be2a4aa083be4c2de273a692bfb06b2..94a22c00daa3989e7e02919fbaac945907c22215 100644 (file)
@@ -750,6 +750,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    2272,
 /**/
     2271,
 /**/
index 303573f914ceab1447b5cc8966136a57e61fd38b..bb33d38e9ef0d0be6f1e948ce85dcf7de7bfc92e 100644 (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;
 
index 5721c0e723a788781d1573c7511c52be153b1477..5cfc327e8e67861f63225b6fce0126b6542f466a 100644 (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);
index 269300c5a7650627ffad7acbe93ec52eb3295ef5..b7c0f67d19d51735ef8c67118b5fb964f3454a20 100644 (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.