mirror of
https://github.com/vim/vim.git
synced 2025-09-23 03:43:49 -04:00
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.
This commit is contained in:
@@ -12262,10 +12262,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!
|
||||||
|
@@ -1218,6 +1218,8 @@ set_var_lval(
|
|||||||
semsg(_(e_letwrong), op);
|
semsg(_(e_letwrong), op);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (var_check_lock(lp->ll_blob->bv_lock, lp->ll_name, FALSE))
|
||||||
|
return;
|
||||||
|
|
||||||
if (lp->ll_range && rettv->v_type == VAR_BLOB)
|
if (lp->ll_range && rettv->v_type == VAR_BLOB)
|
||||||
{
|
{
|
||||||
|
@@ -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 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_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 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 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(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);
|
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;
|
di->di_flags |= DI_FLAGS_LOCK;
|
||||||
else
|
else
|
||||||
di->di_flags &= ~DI_FLAGS_LOCK;
|
di->di_flags &= ~DI_FLAGS_LOCK;
|
||||||
item_lock(&di->di_tv, deep, lock);
|
item_lock(&di->di_tv, deep, lock, FALSE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*name_end = cc;
|
*name_end = cc;
|
||||||
@@ -1715,26 +1715,28 @@ do_lock_var(
|
|||||||
// (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))
|
||||||
{
|
{
|
||||||
item_lock(&li->li_tv, deep, lock);
|
item_lock(&li->li_tv, deep, lock, FALSE);
|
||||||
li = li->li_next;
|
li = li->li_next;
|
||||||
++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.
|
||||||
item_lock(&lp->ll_li->li_tv, deep, lock);
|
item_lock(&lp->ll_li->li_tv, deep, lock, FALSE);
|
||||||
else
|
else
|
||||||
// (un)lock a Dictionary item.
|
// (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;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Lock or unlock an item. "deep" is nr of levels to go.
|
* 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
|
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;
|
static int recurse = 0;
|
||||||
list_T *l;
|
list_T *l;
|
||||||
@@ -1776,7 +1778,8 @@ item_lock(typval_T *tv, int deep, int lock)
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case VAR_BLOB:
|
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)
|
if (lock)
|
||||||
b->bv_lock |= VAR_LOCKED;
|
b->bv_lock |= VAR_LOCKED;
|
||||||
@@ -1785,7 +1788,8 @@ item_lock(typval_T *tv, int deep, int lock)
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case VAR_LIST:
|
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)
|
if (lock)
|
||||||
l->lv_lock |= VAR_LOCKED;
|
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)
|
if ((deep < 0 || deep > 1) && l->lv_first != &range_list_item)
|
||||||
// recursive: lock/unlock the items the List contains
|
// recursive: lock/unlock the items the List contains
|
||||||
FOR_ALL_LIST_ITEMS(l, li)
|
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;
|
break;
|
||||||
case VAR_DICT:
|
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)
|
if (lock)
|
||||||
d->dv_lock |= VAR_LOCKED;
|
d->dv_lock |= VAR_LOCKED;
|
||||||
@@ -1813,7 +1818,8 @@ item_lock(typval_T *tv, int deep, int lock)
|
|||||||
if (!HASHITEM_EMPTY(hi))
|
if (!HASHITEM_EMPTY(hi))
|
||||||
{
|
{
|
||||||
--todo;
|
--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)
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@@ -273,20 +273,35 @@ 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
|
||||||
|
|
||||||
" vim: shiftwidth=2 sts=2 expandtab
|
" vim: shiftwidth=2 sts=2 expandtab
|
||||||
|
@@ -754,6 +754,8 @@ static char *(features[]) =
|
|||||||
|
|
||||||
static int included_patches[] =
|
static int included_patches[] =
|
||||||
{ /* Add new patch number below this line */
|
{ /* Add new patch number below this line */
|
||||||
|
/**/
|
||||||
|
1473,
|
||||||
/**/
|
/**/
|
||||||
1472,
|
1472,
|
||||||
/**/
|
/**/
|
||||||
|
Reference in New Issue
Block a user