# HG changeset patch # User Bram Moolenaar # Date 1597691703 -7200 # Node ID fb74a338769490955a092be24ba6d84c02426831 # Parent 9b0141b6cfc2d68eb394030f2b77da3dcf05110a patch 8.2.1473: items in a list given to :const can still be modified Commit: https://github.com/vim/vim/commit/021bda56710d98c09a6b35610a476ab2dd8c58ad Author: Bram Moolenaar Date: Mon Aug 17 21:07:22 2020 +0200 patch 8.2.1473: items in a list given to :const can still be modified 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. diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -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! diff --git a/src/eval.c b/src/eval.c --- a/src/eval.c +++ b/src/eval.c @@ -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) { diff --git a/src/evalvars.c b/src/evalvars.c --- a/src/evalvars.c +++ b/src/evalvars.c @@ -173,7 +173,7 @@ static char_u *list_arg_vars(exarg_T *ea 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 lo 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 lo } 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 lo 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 lo 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); } /* diff --git a/src/testdir/test_const.vim b/src/testdir/test_const.vim --- a/src/testdir/test_const.vim +++ b/src/testdir/test_const.vim @@ -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 diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -755,6 +755,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 1473, +/**/ 1472, /**/ 1471,