mirror of
https://github.com/neovim/neovim.git
synced 2025-09-12 14:28:18 +00:00
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.
021bda5671
This commit is contained in:
@@ -10871,10 +10871,18 @@ text...
|
|||||||
:const x = 1
|
:const x = 1
|
||||||
< is equivalent to: >
|
< is equivalent to: >
|
||||||
:let x = 1
|
:let x = 1
|
||||||
:lockvar 1 x
|
:lockvar! x
|
||||||
< This is useful if you want to make sure the variable
|
< This is useful if you want to make sure the variable
|
||||||
is not modified.
|
is not modified. If the value is a List or Dictionary
|
||||||
*E995*
|
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. >
|
|:const| does not allow to for changing a variable. >
|
||||||
:let x = 1
|
:let x = 1
|
||||||
:const x = 2 " Error!
|
:const x = 2 " Error!
|
||||||
|
@@ -2373,6 +2373,9 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv,
|
|||||||
EMSG2(_(e_letwrong), op);
|
EMSG2(_(e_letwrong), op);
|
||||||
return;
|
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_range && rettv->v_type == VAR_BLOB) {
|
||||||
if (lp->ll_empty2) {
|
if (lp->ll_empty2) {
|
||||||
@@ -3082,7 +3085,7 @@ static int do_lock_var(lval_T *lp, char_u *name_end FUNC_ATTR_UNUSED,
|
|||||||
} else {
|
} else {
|
||||||
di->di_flags &= ~DI_FLAGS_LOCK;
|
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) {
|
} 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.
|
// (un)lock a range of List items.
|
||||||
while (li != NULL && (lp->ll_empty2 || lp->ll_n2 >= lp->ll_n1)) {
|
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);
|
li = TV_LIST_ITEM_NEXT(lp->ll_list, li);
|
||||||
lp->ll_n1++;
|
lp->ll_n1++;
|
||||||
}
|
}
|
||||||
} else if (lp->ll_list != NULL) {
|
} else if (lp->ll_list != NULL) {
|
||||||
// (un)lock a List item.
|
// (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 {
|
} else {
|
||||||
// (un)lock a Dictionary item.
|
// (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;
|
return ret;
|
||||||
@@ -9536,7 +9539,10 @@ static void set_var_const(const char *name, const size_t name_len,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (is_const) {
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -2659,7 +2659,10 @@ void tv_copy(const typval_T *const from, typval_T *const to)
|
|||||||
/// @param[out] tv Item to (un)lock.
|
/// @param[out] tv Item to (un)lock.
|
||||||
/// @param[in] deep Levels to (un)lock, -1 to (un)lock everything.
|
/// @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.
|
/// @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
|
FUNC_ATTR_NONNULL_ALL
|
||||||
{
|
{
|
||||||
// TODO(ZyX-I): Make this not recursive
|
// 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) {
|
switch (tv->v_type) {
|
||||||
case VAR_BLOB: {
|
case VAR_BLOB: {
|
||||||
blob_T *const b = tv->vval.v_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);
|
CHANGE_LOCK(lock, b->bv_lock);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case VAR_LIST: {
|
case VAR_LIST: {
|
||||||
list_T *const l = tv->vval.v_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);
|
CHANGE_LOCK(lock, l->lv_lock);
|
||||||
if (deep < 0 || deep > 1) {
|
if (deep < 0 || deep > 1) {
|
||||||
// Recursive: lock/unlock the items the List contains.
|
// Recursive: lock/unlock the items the List contains.
|
||||||
TV_LIST_ITER(l, li, {
|
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: {
|
case VAR_DICT: {
|
||||||
dict_T *const d = tv->vval.v_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);
|
CHANGE_LOCK(lock, d->dv_lock);
|
||||||
if (deep < 0 || deep > 1) {
|
if (deep < 0 || deep > 1) {
|
||||||
// recursive: lock/unlock the items the List contains
|
// recursive: lock/unlock the items the List contains
|
||||||
TV_DICT_ITER(d, di, {
|
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);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -244,18 +244,33 @@ func Test_const_with_eval_name()
|
|||||||
call assert_fails('const {s2} = "bar"', 'E995:')
|
call assert_fails('const {s2} = "bar"', 'E995:')
|
||||||
endfunc
|
endfunc
|
||||||
|
|
||||||
func Test_lock_depth_is_1()
|
func Test_lock_depth_is_2()
|
||||||
const l = [1, 2, 3]
|
" Modify list - error when changing item or adding/removing items
|
||||||
const d = {'foo': 10}
|
const l = [1, 2, [3, 4]]
|
||||||
|
call assert_fails('let l[0] = 42', 'E741:')
|
||||||
" Modify list - setting item is OK, adding/removing items not
|
call assert_fails('let l[2][0] = 42', 'E741:')
|
||||||
let l[0] = 42
|
|
||||||
call assert_fails('call add(l, 4)', 'E741:')
|
call assert_fails('call add(l, 4)', 'E741:')
|
||||||
call assert_fails('unlet l[1]', 'E741:')
|
call assert_fails('unlet l[1]', 'E741:')
|
||||||
|
|
||||||
" Modify dict - changing item is OK, adding/removing items not
|
" Modify blob - error when changing
|
||||||
let d['foo'] = 'hello'
|
const b = 0z001122
|
||||||
let d.foo = 44
|
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("let d['bar'] = 'hello'", 'E741:')
|
||||||
call assert_fails("unlet d['foo']", '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
|
endfunc
|
||||||
|
@@ -2531,7 +2531,7 @@ describe('typval.c', function()
|
|||||||
value='tr',
|
value='tr',
|
||||||
dict={},
|
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)
|
eq(lib.VAR_UNLOCKED, p_tv.vval.v_partial.pt_dict.dv_lock)
|
||||||
end)
|
end)
|
||||||
itp('does not change VAR_FIXED values', function()
|
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
|
d_tv.vval.v_dict.dv_lock = lib.VAR_FIXED
|
||||||
l_tv.v_lock = lib.VAR_FIXED
|
l_tv.v_lock = lib.VAR_FIXED
|
||||||
l_tv.vval.v_list.lv_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(d_tv, 1, true, false)
|
||||||
lib.tv_item_lock(l_tv, 1, true)
|
lib.tv_item_lock(l_tv, 1, true, false)
|
||||||
eq(lib.VAR_FIXED, d_tv.v_lock)
|
eq(lib.VAR_FIXED, d_tv.v_lock)
|
||||||
eq(lib.VAR_FIXED, l_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, d_tv.vval.v_dict.dv_lock)
|
||||||
eq(lib.VAR_FIXED, l_tv.vval.v_list.lv_lock)
|
eq(lib.VAR_FIXED, l_tv.vval.v_list.lv_lock)
|
||||||
lib.tv_item_lock(d_tv, 1, false)
|
lib.tv_item_lock(d_tv, 1, false, false)
|
||||||
lib.tv_item_lock(l_tv, 1, false)
|
lib.tv_item_lock(l_tv, 1, false, false)
|
||||||
eq(lib.VAR_FIXED, d_tv.v_lock)
|
eq(lib.VAR_FIXED, d_tv.v_lock)
|
||||||
eq(lib.VAR_FIXED, l_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, d_tv.vval.v_dict.dv_lock)
|
||||||
@@ -2561,9 +2561,9 @@ describe('typval.c', function()
|
|||||||
local d_tv = lua2typvalt(null_dict)
|
local d_tv = lua2typvalt(null_dict)
|
||||||
local s_tv = lua2typvalt(null_string)
|
local s_tv = lua2typvalt(null_string)
|
||||||
alloc_log:clear()
|
alloc_log:clear()
|
||||||
lib.tv_item_lock(l_tv, 1, true)
|
lib.tv_item_lock(l_tv, 1, true, false)
|
||||||
lib.tv_item_lock(d_tv, 1, true)
|
lib.tv_item_lock(d_tv, 1, true, false)
|
||||||
lib.tv_item_lock(s_tv, 1, true)
|
lib.tv_item_lock(s_tv, 1, true, false)
|
||||||
eq(null_list, typvalt2lua(l_tv))
|
eq(null_list, typvalt2lua(l_tv))
|
||||||
eq(null_dict, typvalt2lua(d_tv))
|
eq(null_dict, typvalt2lua(d_tv))
|
||||||
eq(null_string, typvalt2lua(s_tv))
|
eq(null_string, typvalt2lua(s_tv))
|
||||||
|
Reference in New Issue
Block a user