Example:
:let l = ['a', 'b', 3]
:lockvar 1 l
:let l += ['x']
E741: Value is locked: l
:let l
l ['a', 'b', 3, 'x']
When the error message is shown, the list has already been changed.
Changing lists in the v: scope has a related problem: v:oldfiles is not
locked nor readonly and thus modifiable (as documented in
':he v:oldfiles'), but changing such a v: scope list variable prints an
error message and does not succeed, except when extending it with '+=':
:let v:oldfiles += ['test']
E685: Internal error: set_var()
:let v:oldfiles
v:oldfiles ['fileA', 'fileB', ..., 'test']
:let v:oldfiles = ['otherfile']
E685: Internal error: set_var()
:let v:oldfiles
v:oldfiles ['fileA', 'fileB', ..., 'test']
The patch adds checks for the lock and readonly flags of a variable
before preparing a [+-.]= assignment, as that preparation alone is
sufficient to extend an existing list variable. (Preparations for other
variable types do not change existing variables.)
The patch also adds code to allow v: scope lists, dicts, floats and
funcrefs to be changed, unless they are locked or readonly or unless the
change also would change the variable type.
So far, this only affects v:oldfiles.
v:key is either set to a string or a number and is readonly, v:val
can have any type but is readonly, and the remaining v: scope
variables have types fixed at string or number, and most are
readonly as well.
With these changes in place, a '+=' extension of a list, once prepared,
cannot be canceled by any of the subsequent checks in set_var() before
the variable setting is finalized. These checks are:
- Check for user lock and readonly flags. Same as done in tv_op().
- Two checks for variable type mismatches.
For a list extension, tv_op() would have failed on a type mismatch
already, and for others the assignment target variable has not been
changed yet, so there is no problem if the assignment fails here.
Note that in other places tv_op() is used to change the target value(s)
directly, and no subsequent checks are done that could cause problems.
A remaining problem is that the interdependence of the checks and
preparations in set_var_lval() and tv_op() with checks in set_var()
is implicit, and this makes this code more difficult to maintain. One
could consider moving the tv_op() for setting plain variables into
set_var(), but I have not looked into this sufficiently.
diff -r cf5d9c1e4c8a src/eval.c
--- a/src/eval.c Fri Apr 17 22:08:16 2015 +0200
+++ b/src/eval.c Mon Apr 20 07:23:13 2015 +0200
@@ -783,7 +783,7 @@ static char_u *find_name_end __ARGS((cha
static char_u * make_expanded_name __ARGS((char_u *in_start, char_u
*expr_start, char_u *expr_end, char_u *in_end));
static int eval_isnamec __ARGS((int c));
static int eval_isnamec1 __ARGS((int c));
-static int get_var_tv __ARGS((char_u *name, int len, typval_T *rettv, int
verbose, int no_autoload));
+static int get_var_tv __ARGS((char_u *name, int len, typval_T *rettv,
dictitem_T **dip, int verbose, int no_autoload));
static int handle_subscript __ARGS((char_u **arg, typval_T *rettv, int
evaluate, int verbose));
static typval_T *alloc_tv __ARGS((void));
static typval_T *alloc_string_tv __ARGS((char_u *string));
@@ -2257,7 +2257,7 @@ list_arg_vars(eap, arg, first)
{
if (tofree != NULL)
name = tofree;
- if (get_var_tv(name, len, &tv, TRUE, FALSE) == FAIL)
+ if (get_var_tv(name, len, &tv, NULL, TRUE, FALSE) == FAIL)
error = TRUE;
else
{
@@ -2927,9 +2927,11 @@ set_var_lval(lp, endp, rettv, copy, op)
/* handle +=, -= and .= */
if (get_var_tv(lp->ll_name, (int)STRLEN(lp->ll_name),
- &tv, TRUE, FALSE) == OK)
- {
- if (tv_op(&tv, rettv, op) == OK)
+ &tv, &di, TRUE, FALSE)
== OK)
+ {
+ if (!var_check_ro(di->di_flags, lp->ll_name)
+ && !tv_check_lock(di->di_tv.v_lock, lp->ll_name)
+ && tv_op(&tv, rettv, op) == OK)
set_var(lp->ll_name, &tv, FALSE);
clear_tv(&tv);
}
@@ -5246,7 +5248,7 @@ eval7(arg, rettv, evaluate, want_string)
}
}
else if (evaluate)
- ret = get_var_tv(s, len, rettv, TRUE, FALSE);
+ ret = get_var_tv(s, len, rettv, NULL, TRUE, FALSE);
else
ret = OK;
}
@@ -10374,7 +10376,7 @@ f_exists(argvars, rettv)
{
if (tofree != NULL)
name = tofree;
- n = (get_var_tv(name, len, &tv, FALSE, TRUE) == OK);
+ n = (get_var_tv(name, len, &tv, NULL, FALSE, TRUE) == OK);
if (n)
{
/* handle d.key, l[idx], f(expr) */
@@ -20649,10 +20651,11 @@ set_cmdarg(eap, oldarg)
* Return OK or FAIL.
*/
static int
-get_var_tv(name, len, rettv, verbose, no_autoload)
+get_var_tv(name, len, rettv, dip, verbose, no_autoload)
char_u *name;
int len; /* length of "name" */
typval_T *rettv; /* NULL when only checking existence */
+ dictitem_T **dip; /* non-NULL when typval's dict item is needed */
int verbose; /* may give error message */
int no_autoload; /* do not use script autoloading */
{
@@ -20683,7 +20686,11 @@ get_var_tv(name, len, rettv, verbose, no
{
v = find_var(name, NULL, no_autoload);
if (v != NULL)
+ {
tv = &v->di_tv;
+ if (dip != NULL)
+ *dip = v;
+ }
}
if (tv == NULL)
@@ -21477,8 +21484,8 @@ set_var(name, tv, copy)
}
/*
- * Handle setting internal v: variables separately: we don't change
- * the type.
+ * Handle setting internal v: variables separately where needed to
+ * prevent changing the type.
*/
if (ht == &vimvarht)
{
@@ -21493,10 +21500,9 @@ set_var(name, tv, copy)
v->di_tv.vval.v_string = tv->vval.v_string;
tv->vval.v_string = NULL;
}
- }
- else if (v->di_tv.v_type != VAR_NUMBER)
- EMSG2(_(e_intern2), "set_var()");
- else
+ return;
+ }
+ else if (v->di_tv.v_type == VAR_NUMBER)
{
v->di_tv.vval.v_number = get_tv_number(tv);
if (STRCMP(varname, "searchforward") == 0)
@@ -21508,8 +21514,10 @@ set_var(name, tv, copy)
redraw_all_later(SOME_VALID);
}
#endif
- }
- return;
+ return;
+ }
+ else if (v->di_tv.v_type != tv->v_type)
+ EMSG2(_(e_intern2), "set_var()");
}
clear_tv(&v->di_tv);
diff -r cf5d9c1e4c8a src/testdir/test55.in
--- a/src/testdir/test55.in Fri Apr 17 22:08:16 2015 +0200
+++ b/src/testdir/test55.in Mon Apr 20 07:23:13 2015 +0200
@@ -442,6 +442,17 @@ let l = [0, 1, 2, 3]
:unlockvar 1 b:
:unlet! b:testvar
:"
+:$put ='No :let += of locked list variable:'
+:let l = ['a', 'b', 3]
+:lockvar 1 l
+:try
+: let l += ['x']
+: $put ='did :let +='
+:catch
+: $put =v:exception[:14]
+:endtry
+:$put =string(l)
+:"
:unlet l
:let l = [1, 2, 3, 4]
:lockvar! l
diff -r cf5d9c1e4c8a src/testdir/test55.ok
--- a/src/testdir/test55.ok Fri Apr 17 22:08:16 2015 +0200
+++ b/src/testdir/test55.ok Mon Apr 20 07:23:13 2015 +0200
@@ -144,6 +144,9 @@ No extend() of write-protected scope-lev
Vim(put):E742:
No :unlet of variable in locked scope:
Vim(unlet):E741:
+No :let += of locked list variable:
+Vim(let):E741:
+['a', 'b', 3]
[1, 2, 3, 4]
[1, 2, 3, 4]
[1, 2, 3, 4]
--
Olaf Dabrunz (oda <at> fctrace.org)
--
--
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php
---
You received this message because you are subscribed to the Google Groups
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
For more options, visit https://groups.google.com/d/optout.