diff options
author | Sean Dewar <seandewar@users.noreply.github.com> | 2021-07-31 00:20:20 +0100 |
---|---|---|
committer | Sean Dewar <seandewar@users.noreply.github.com> | 2021-09-16 00:13:41 +0100 |
commit | 9e38c4a79fe0351e9e18bc57cf48b960e8d31e88 (patch) | |
tree | a0ba140fd2bf69e6a85ef7e76b3a7b51a76f0f55 | |
parent | ffaf881b42fd22b8bf423c5754d48ce93f59bf4b (diff) | |
download | rneovim-9e38c4a79fe0351e9e18bc57cf48b960e8d31e88.tar.gz rneovim-9e38c4a79fe0351e9e18bc57cf48b960e8d31e88.tar.bz2 rneovim-9e38c4a79fe0351e9e18bc57cf48b960e8d31e88.zip |
vim-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.
https://github.com/vim/vim/commit/021bda56710d98c09a6b35610a476ab2dd8c58ad
-rw-r--r-- | runtime/doc/eval.txt | 14 | ||||
-rw-r--r-- | src/nvim/eval.c | 16 | ||||
-rw-r--r-- | src/nvim/eval/typval.c | 15 | ||||
-rw-r--r-- | src/nvim/testdir/test_const.vim | 33 | ||||
-rw-r--r-- | test/unit/eval/typval_spec.lua | 16 |
5 files changed, 63 insertions, 31 deletions
diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index 7532bcfba7..7f22bab7f2 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -10871,10 +10871,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/nvim/eval.c b/src/nvim/eval.c index 26fc02b7c9..903673123c 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -2373,6 +2373,9 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv, EMSG2(_(e_letwrong), op); return; } + if (var_check_lock(lp->ll_blob->bv_lock, lp->ll_name, TV_CSTRING)) { + return; + } if (lp->ll_range && rettv->v_type == VAR_BLOB) { if (lp->ll_empty2) { @@ -3082,7 +3085,7 @@ static int do_lock_var(lval_T *lp, char_u *name_end FUNC_ATTR_UNUSED, } else { di->di_flags &= ~DI_FLAGS_LOCK; } - tv_item_lock(&di->di_tv, deep, lock); + tv_item_lock(&di->di_tv, deep, lock, false); } } } else if (lp->ll_range) { @@ -3090,16 +3093,16 @@ static int do_lock_var(lval_T *lp, char_u *name_end FUNC_ATTR_UNUSED, // (un)lock a range of List items. while (li != NULL && (lp->ll_empty2 || lp->ll_n2 >= lp->ll_n1)) { - tv_item_lock(TV_LIST_ITEM_TV(li), deep, lock); + tv_item_lock(TV_LIST_ITEM_TV(li), deep, lock, false); li = TV_LIST_ITEM_NEXT(lp->ll_list, li); lp->ll_n1++; } } else if (lp->ll_list != NULL) { // (un)lock a List item. - tv_item_lock(TV_LIST_ITEM_TV(lp->ll_li), deep, lock); + tv_item_lock(TV_LIST_ITEM_TV(lp->ll_li), deep, lock, false); } else { // (un)lock a Dictionary item. - tv_item_lock(&lp->ll_di->di_tv, deep, lock); + tv_item_lock(&lp->ll_di->di_tv, deep, lock, false); } return ret; @@ -9536,7 +9539,10 @@ static void set_var_const(const char *name, const size_t name_len, } if (is_const) { - tv_item_lock(&v->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. + tv_item_lock(&v->di_tv, DICT_MAXNEST, true, true); } } diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index d983ce14b5..b08fe274f0 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -2659,7 +2659,10 @@ void tv_copy(const typval_T *const from, typval_T *const to) /// @param[out] tv Item to (un)lock. /// @param[in] deep Levels to (un)lock, -1 to (un)lock everything. /// @param[in] lock True if it is needed to lock an item, false to unlock. -void tv_item_lock(typval_T *const tv, const int deep, const bool lock) +/// @param[in] check_refcount If true, do not lock a list or dict with a +/// reference count larger than 1. +void tv_item_lock(typval_T *const tv, const int deep, const bool lock, + const bool check_refcount) FUNC_ATTR_NONNULL_ALL { // TODO(ZyX-I): Make this not recursive @@ -2688,19 +2691,19 @@ void tv_item_lock(typval_T *const tv, const int deep, const bool lock) switch (tv->v_type) { case VAR_BLOB: { blob_T *const b = tv->vval.v_blob; - if (b != NULL) { + if (b != NULL && !(check_refcount && b->bv_refcount > 1)) { CHANGE_LOCK(lock, b->bv_lock); } break; } case VAR_LIST: { list_T *const l = tv->vval.v_list; - if (l != NULL) { + if (l != NULL && !(check_refcount && l->lv_refcount > 1)) { CHANGE_LOCK(lock, l->lv_lock); if (deep < 0 || deep > 1) { // Recursive: lock/unlock the items the List contains. TV_LIST_ITER(l, li, { - tv_item_lock(TV_LIST_ITEM_TV(li), deep - 1, lock); + tv_item_lock(TV_LIST_ITEM_TV(li), deep - 1, lock, check_refcount); }); } } @@ -2708,12 +2711,12 @@ void tv_item_lock(typval_T *const tv, const int deep, const bool lock) } case VAR_DICT: { dict_T *const d = tv->vval.v_dict; - if (d != NULL) { + if (d != NULL && !(check_refcount && d->dv_refcount > 1)) { CHANGE_LOCK(lock, d->dv_lock); if (deep < 0 || deep > 1) { // recursive: lock/unlock the items the List contains TV_DICT_ITER(d, di, { - tv_item_lock(&di->di_tv, deep - 1, lock); + tv_item_lock(&di->di_tv, deep - 1, lock, check_refcount); }); } } diff --git a/src/nvim/testdir/test_const.vim b/src/nvim/testdir/test_const.vim index ea69c8cba4..0d064617a5 100644 --- a/src/nvim/testdir/test_const.vim +++ b/src/nvim/testdir/test_const.vim @@ -244,18 +244,33 @@ 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 diff --git a/test/unit/eval/typval_spec.lua b/test/unit/eval/typval_spec.lua index d81e272877..e61b568f3a 100644 --- a/test/unit/eval/typval_spec.lua +++ b/test/unit/eval/typval_spec.lua @@ -2531,7 +2531,7 @@ describe('typval.c', function() value='tr', dict={}, }) - lib.tv_item_lock(p_tv, -1, true) + lib.tv_item_lock(p_tv, -1, true, false) eq(lib.VAR_UNLOCKED, p_tv.vval.v_partial.pt_dict.dv_lock) end) itp('does not change VAR_FIXED values', function() @@ -2542,14 +2542,14 @@ describe('typval.c', function() d_tv.vval.v_dict.dv_lock = lib.VAR_FIXED l_tv.v_lock = lib.VAR_FIXED l_tv.vval.v_list.lv_lock = lib.VAR_FIXED - lib.tv_item_lock(d_tv, 1, true) - lib.tv_item_lock(l_tv, 1, true) + lib.tv_item_lock(d_tv, 1, true, false) + lib.tv_item_lock(l_tv, 1, true, false) eq(lib.VAR_FIXED, d_tv.v_lock) eq(lib.VAR_FIXED, l_tv.v_lock) eq(lib.VAR_FIXED, d_tv.vval.v_dict.dv_lock) eq(lib.VAR_FIXED, l_tv.vval.v_list.lv_lock) - lib.tv_item_lock(d_tv, 1, false) - lib.tv_item_lock(l_tv, 1, false) + lib.tv_item_lock(d_tv, 1, false, false) + lib.tv_item_lock(l_tv, 1, false, false) eq(lib.VAR_FIXED, d_tv.v_lock) eq(lib.VAR_FIXED, l_tv.v_lock) eq(lib.VAR_FIXED, d_tv.vval.v_dict.dv_lock) @@ -2561,9 +2561,9 @@ describe('typval.c', function() local d_tv = lua2typvalt(null_dict) local s_tv = lua2typvalt(null_string) alloc_log:clear() - lib.tv_item_lock(l_tv, 1, true) - lib.tv_item_lock(d_tv, 1, true) - lib.tv_item_lock(s_tv, 1, true) + lib.tv_item_lock(l_tv, 1, true, false) + lib.tv_item_lock(d_tv, 1, true, false) + lib.tv_item_lock(s_tv, 1, true, false) eq(null_list, typvalt2lua(l_tv)) eq(null_dict, typvalt2lua(d_tv)) eq(null_string, typvalt2lua(s_tv)) |