]> granicus.if.org Git - vim/commitdiff
patch 8.2.1473: items in a list given to :const can still be modified v8.2.1473
authorBram Moolenaar <Bram@vim.org>
Mon, 17 Aug 2020 19:07:22 +0000 (21:07 +0200)
committerBram Moolenaar <Bram@vim.org>
Mon, 17 Aug 2020 19:07:22 +0000 (21:07 +0200)
Problem:    Items in a list given to :const can still be modified.
Solution:   Work like ":lockvar! name" but don't lock referenced items.
            Make locking a blob work.

runtime/doc/eval.txt
src/eval.c
src/evalvars.c
src/testdir/test_const.vim
src/version.c

index 079eecb928f5c3acadc24181bfe0a6a1facbf250..370964e567f7d8e4e0ebc9fbfbd3d8fa3e6a4aac 100644 (file)
@@ -12262,10 +12262,18 @@ text...
                                :const x = 1
 <                      is equivalent to: >
                                :let x = 1
-                               :lockvar 1 x
+                               :lockvar! x
 <                      This is useful if you want to make sure the variable
-                       is not modified.
-                                                       *E995*
+                       is not modified.  If the value is a List or Dictionary
+                       literal then the items also cannot be changed: >
+                               const ll = [1, 2, 3]
+                               let ll[1] = 5  " Error!
+<                      Nested references are not locked: >
+                               let lvar = ['a']
+                               const lconst = [0, lvar]
+                               let lconst[0] = 2  " Error!
+                               let lconst[1][0] = 'b'  " OK
+<                                                      *E995*
                        |:const| does not allow to for changing a variable: >
                                :let x = 1
                                :const x = 2  " Error!
index 3c4a8c562d659e40460f10f6e22ed76ba719c562..c80013960a30ff7819996d53576d43dd2ed3cda6 100644 (file)
@@ -1218,6 +1218,8 @@ set_var_lval(
                semsg(_(e_letwrong), op);
                return;
            }
+           if (var_check_lock(lp->ll_blob->bv_lock, lp->ll_name, FALSE))
+               return;
 
            if (lp->ll_range && rettv->v_type == VAR_BLOB)
            {
index 4ebcb451295812fd7d1273404e0ccfeeee283423..f59dde9c69365314e33f002198bf8b15873c9ad1 100644 (file)
@@ -173,7 +173,7 @@ static char_u *list_arg_vars(exarg_T *eap, char_u *arg, int *first);
 static char_u *ex_let_one(char_u *arg, typval_T *tv, int copy, int flags, char_u *endchars, char_u *op);
 static int do_unlet_var(lval_T *lp, char_u *name_end, exarg_T *eap, int deep, void *cookie);
 static int do_lock_var(lval_T *lp, char_u *name_end, exarg_T *eap, int deep, void *cookie);
-static void item_lock(typval_T *tv, int deep, int lock);
+static void item_lock(typval_T *tv, int deep, int lock, int check_refcount);
 static void delete_var(hashtab_T *ht, hashitem_T *hi);
 static void list_one_var(dictitem_T *v, char *prefix, int *first);
 static void list_one_var_a(char *prefix, char_u *name, int type, char_u *string, int *first);
@@ -1703,7 +1703,7 @@ do_lock_var(
                    di->di_flags |= DI_FLAGS_LOCK;
                else
                    di->di_flags &= ~DI_FLAGS_LOCK;
-               item_lock(&di->di_tv, deep, lock);
+               item_lock(&di->di_tv, deep, lock, FALSE);
            }
        }
        *name_end = cc;
@@ -1715,26 +1715,28 @@ do_lock_var(
        // (un)lock a range of List items.
        while (li != NULL && (lp->ll_empty2 || lp->ll_n2 >= lp->ll_n1))
        {
-           item_lock(&li->li_tv, deep, lock);
+           item_lock(&li->li_tv, deep, lock, FALSE);
            li = li->li_next;
            ++lp->ll_n1;
        }
     }
     else if (lp->ll_list != NULL)
        // (un)lock a List item.
-       item_lock(&lp->ll_li->li_tv, deep, lock);
+       item_lock(&lp->ll_li->li_tv, deep, lock, FALSE);
     else
        // (un)lock a Dictionary item.
-       item_lock(&lp->ll_di->di_tv, deep, lock);
+       item_lock(&lp->ll_di->di_tv, deep, lock, FALSE);
 
     return ret;
 }
 
 /*
  * Lock or unlock an item.  "deep" is nr of levels to go.
+ * When "check_refcount" is TRUE do not lock a list or dict with a reference
+ * count larger than 1.
  */
     static void
-item_lock(typval_T *tv, int deep, int lock)
+item_lock(typval_T *tv, int deep, int lock, int check_refcount)
 {
     static int recurse = 0;
     list_T     *l;
@@ -1776,7 +1778,8 @@ item_lock(typval_T *tv, int deep, int lock)
            break;
 
        case VAR_BLOB:
-           if ((b = tv->vval.v_blob) != NULL)
+           if ((b = tv->vval.v_blob) != NULL
+                                   && !(check_refcount && b->bv_refcount > 1))
            {
                if (lock)
                    b->bv_lock |= VAR_LOCKED;
@@ -1785,7 +1788,8 @@ item_lock(typval_T *tv, int deep, int lock)
            }
            break;
        case VAR_LIST:
-           if ((l = tv->vval.v_list) != NULL)
+           if ((l = tv->vval.v_list) != NULL
+                                   && !(check_refcount && l->lv_refcount > 1))
            {
                if (lock)
                    l->lv_lock |= VAR_LOCKED;
@@ -1794,11 +1798,12 @@ item_lock(typval_T *tv, int deep, int lock)
                if ((deep < 0 || deep > 1) && l->lv_first != &range_list_item)
                    // recursive: lock/unlock the items the List contains
                    FOR_ALL_LIST_ITEMS(l, li)
-                       item_lock(&li->li_tv, deep - 1, lock);
+                       item_lock(&li->li_tv, deep - 1, lock, check_refcount);
            }
            break;
        case VAR_DICT:
-           if ((d = tv->vval.v_dict) != NULL)
+           if ((d = tv->vval.v_dict) != NULL
+                                   && !(check_refcount && d->dv_refcount > 1))
            {
                if (lock)
                    d->dv_lock |= VAR_LOCKED;
@@ -1813,7 +1818,8 @@ item_lock(typval_T *tv, int deep, int lock)
                        if (!HASHITEM_EMPTY(hi))
                        {
                            --todo;
-                           item_lock(&HI2DI(hi)->di_tv, deep - 1, lock);
+                           item_lock(&HI2DI(hi)->di_tv, deep - 1, lock,
+                                                              check_refcount);
                        }
                    }
                }
@@ -3087,7 +3093,10 @@ set_var_const(
     }
 
     if (flags & LET_IS_CONST)
-       item_lock(&di->di_tv, 1, TRUE);
+       // Like :lockvar! name: lock the value and what it contains, but only
+       // if the reference count is up to one.  That locks only literal
+       // values.
+       item_lock(&di->di_tv, DICT_MAXNEST, TRUE, TRUE);
 }
 
 /*
index c993f56a8f393da1702a05af49afb46fc887a4d8..107e2503e0920889c6adeb36e3af7a0e10ab0321 100644 (file)
@@ -273,20 +273,35 @@ func Test_const_with_eval_name()
     call assert_fails('const {s2} = "bar"', 'E995:')
 endfunc
 
-func Test_lock_depth_is_1()
-    const l = [1, 2, 3]
-    const d = {'foo': 10}
-
-    " Modify list - setting item is OK, adding/removing items not
-    let l[0] = 42
+func Test_lock_depth_is_2()
+    " Modify list - error when changing item or adding/removing items
+    const l = [1, 2, [3, 4]]
+    call assert_fails('let l[0] = 42', 'E741:')
+    call assert_fails('let l[2][0] = 42', 'E741:')
     call assert_fails('call add(l, 4)', 'E741:')
     call assert_fails('unlet l[1]', 'E741:')
 
-    " Modify dict - changing item is OK, adding/removing items not
-    let d['foo'] = 'hello'
-    let d.foo = 44
+    " Modify blob - error when changing
+    const b = 0z001122
+    call assert_fails('let b[0] = 42', 'E741:')
+
+    " Modify dict - error when changing item or adding/removing items
+    const d = {'foo': 10}
+    call assert_fails("let d['foo'] = 'hello'", 'E741:')
+    call assert_fails("let d.foo = 'hello'", 'E741:')
     call assert_fails("let d['bar'] = 'hello'", 'E741:')
     call assert_fails("unlet d['foo']", 'E741:')
+
+    " Modifying list or dict item contents is OK.
+    let lvar = ['a', 'b']
+    let bvar = 0z1122
+    const l2 = [0, lvar, bvar]
+    let l2[1][0] = 'c'
+    let l2[2][1] = 0x33
+    call assert_equal([0, ['c', 'b'], 0z1133], l2)
+
+    const d2 = #{a: 0, b: lvar, c: 4}
+    let d2.b[1] = 'd'
 endfunc
 
 " vim: shiftwidth=2 sts=2 expandtab
index 92649a3ec52155d03809ffd7fb6e867d9c600e5f..d57661b7ed48461c1aa2010e3e55b22cc39a60a1 100644 (file)
@@ -754,6 +754,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    1473,
 /**/
     1472,
 /**/