forked from aniani/vim
patch 9.0.1955: Vim9: lockvar issues with objects/classes
Problem: Vim9: lockvar issues with objects/classes Solution: fix `get_lhs()` object/class access and avoid `SEGV`, make error messages more accurate. - `get_lval()` detects/returns object/class access - `compile_lock_unlock()` generate code for bare static and obj_arg access - `do_lock_var()` check lval for `ll_object`/`ll_class` and fail if so. Details: - Add `ll_object`/`ll_class`/`ll_oi` to `lval_T`. - Add `lockunlock_T` to `isn_T` for `is_arg` to specify handling of `lval_root` in `get_lval()`. - In `get_lval()`, fill in `ll_object`/`ll_class`/`ll_oi` as needed; when no `[idx] or .key`, check lval_root on the way out. - In `do_lock_var()` check for `ll_object`/`ll_class`; also bullet proof ll_dict case and give `Dictionay required` if problem. (not needed to avoid lockvar crash anymore) - In `compile_lock_unlock()` compile for the class variable and func arg cases. closes: #13174 Signed-off-by: Christian Brabandt <cb@256bit.org> Co-authored-by: Ernie Rael <errael@raelity.com>
This commit is contained in:
parent
112431f217
commit
ee865f37ac
@ -3534,8 +3534,12 @@ EXTERN char e_missing_name_after_implements[]
|
||||
INIT(= N_("E1389: Missing name after implements"));
|
||||
EXTERN char e_cannot_use_an_object_variable_except_with_the_new_method_str[]
|
||||
INIT(= N_("E1390: Cannot use an object variable \"this.%s\" except with the \"new\" method"));
|
||||
EXTERN char e_cannot_lock_object_variable_str[]
|
||||
INIT(= N_("E1391: Cannot (un)lock variable \"%s\" in class \"%s\""));
|
||||
EXTERN char e_cannot_lock_class_variable_str[]
|
||||
INIT(= N_("E1392: Cannot (un)lock class variable \"%s\" in class \"%s\""));
|
||||
#endif
|
||||
// E1391 - E1499 unused (reserved for Vim9 class support)
|
||||
// E1393 - E1499 unused (reserved for Vim9 class support)
|
||||
EXTERN char e_cannot_mix_positional_and_non_positional_str[]
|
||||
INIT(= N_("E1500: Cannot mix positional and non-positional arguments: %s"));
|
||||
EXTERN char e_fmt_arg_nr_unused_str[]
|
||||
|
101
src/eval.c
101
src/eval.c
@ -985,6 +985,62 @@ eval_foldexpr(win_T *wp, int *cp)
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Fill in "lp" using "root". This is used in a special case when
|
||||
* "get_lval()" parses a bare word when "lval_root" is not NULL.
|
||||
*
|
||||
* This is typically called with "lval_root" as "root". For a class, find
|
||||
* the name from lp in the class from root, fill in lval_T if found. For a
|
||||
* complex type, list/dict use it as the result; just put the root into
|
||||
* ll_tv.
|
||||
*
|
||||
* "lval_root" is a hack used during run-time/instr-execution to provide the
|
||||
* starting point for "get_lval()" to traverse a chain of indexes. In some
|
||||
* cases get_lval sees a bare name and uses this function to populate the
|
||||
* lval_T.
|
||||
*
|
||||
* For setting up "lval_root" (currently only used with lockvar)
|
||||
* compile_lock_unlock - pushes object on stack (which becomes lval_root)
|
||||
* execute_instructions: ISN_LOCKUNLOCK - sets lval_root from stack.
|
||||
*/
|
||||
static void
|
||||
get_lval_root(lval_T *lp, typval_T *root, int is_arg)
|
||||
{
|
||||
#ifdef LOG_LOCKVAR
|
||||
ch_log(NULL, "LKVAR: get_lvalroot(): name %s", lp->ll_name);
|
||||
#endif
|
||||
if (!is_arg && root->v_type == VAR_CLASS)
|
||||
{
|
||||
if (root->vval.v_class != NULL)
|
||||
{
|
||||
// Special special case. Look for a bare class variable reference.
|
||||
class_T *cl = root->vval.v_class;
|
||||
int m_idx;
|
||||
ocmember_T *m = class_member_lookup(cl, lp->ll_name,
|
||||
lp->ll_name_end - lp->ll_name, &m_idx);
|
||||
if (m != NULL)
|
||||
{
|
||||
// Assuming "inside class" since bare reference.
|
||||
lp->ll_class = root->vval.v_class;
|
||||
lp->ll_oi = m_idx;
|
||||
lp->ll_valtype = m->ocm_type;
|
||||
lp->ll_tv = &lp->ll_class->class_members_tv[m_idx];
|
||||
#ifdef LOG_LOCKVAR
|
||||
ch_log(NULL, "LKVAR: get_lvalroot() class member: name %s",
|
||||
lp->ll_name);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef LOG_LOCKVAR
|
||||
ch_log(NULL, "LKVAR: get_lvalroot() any type");
|
||||
#endif
|
||||
lp->ll_tv = root;
|
||||
lp->ll_is_root = TRUE;
|
||||
}
|
||||
|
||||
/*
|
||||
* Get an lval: variable, Dict item or List item that can be assigned a value
|
||||
* to: "name", "na{me}", "name[expr]", "name[expr:expr]", "name[expr][expr]",
|
||||
@ -1028,6 +1084,11 @@ get_lval(
|
||||
int writing = 0;
|
||||
int vim9script = in_vim9script();
|
||||
|
||||
#ifdef LOG_LOCKVAR
|
||||
ch_log(NULL, "LKVAR: get_lval(): name %s, lval_root %p",
|
||||
name, (void*)lval_root);
|
||||
#endif
|
||||
|
||||
// Clear everything in "lp".
|
||||
CLEAR_POINTER(lp);
|
||||
|
||||
@ -1122,6 +1183,7 @@ get_lval(
|
||||
return NULL;
|
||||
lp->ll_name_end = tp;
|
||||
}
|
||||
// TODO: check inside class?
|
||||
}
|
||||
}
|
||||
if (lp->ll_name == NULL)
|
||||
@ -1157,7 +1219,11 @@ get_lval(
|
||||
|
||||
// Without [idx] or .key we are done.
|
||||
if ((*p != '[' && *p != '.'))
|
||||
{
|
||||
if (lval_root != NULL)
|
||||
get_lval_root(lp, lval_root, lval_root_is_arg);
|
||||
return p;
|
||||
}
|
||||
|
||||
if (vim9script && lval_root != NULL)
|
||||
{
|
||||
@ -1350,6 +1416,8 @@ get_lval(
|
||||
}
|
||||
}
|
||||
lp->ll_list = NULL;
|
||||
lp->ll_object = NULL;
|
||||
lp->ll_class = NULL;
|
||||
|
||||
// a NULL dict is equivalent with an empty dict
|
||||
if (lp->ll_tv->vval.v_dict == NULL)
|
||||
@ -1482,6 +1550,8 @@ get_lval(
|
||||
clear_tv(&var1);
|
||||
|
||||
lp->ll_dict = NULL;
|
||||
lp->ll_object = NULL;
|
||||
lp->ll_class = NULL;
|
||||
lp->ll_list = lp->ll_tv->vval.v_list;
|
||||
lp->ll_li = check_range_index_one(lp->ll_list, &lp->ll_n1,
|
||||
(flags & GLV_ASSIGN_WITH_OP) == 0, quiet);
|
||||
@ -1516,10 +1586,22 @@ get_lval(
|
||||
}
|
||||
else // v_type == VAR_CLASS || v_type == VAR_OBJECT
|
||||
{
|
||||
class_T *cl = (v_type == VAR_OBJECT
|
||||
&& lp->ll_tv->vval.v_object != NULL)
|
||||
? lp->ll_tv->vval.v_object->obj_class
|
||||
: lp->ll_tv->vval.v_class;
|
||||
lp->ll_dict = NULL;
|
||||
lp->ll_list = NULL;
|
||||
|
||||
class_T *cl;
|
||||
if (v_type == VAR_OBJECT && lp->ll_tv->vval.v_object != NULL)
|
||||
{
|
||||
cl = lp->ll_tv->vval.v_object->obj_class;
|
||||
lp->ll_object = lp->ll_tv->vval.v_object;
|
||||
}
|
||||
else
|
||||
{
|
||||
cl = lp->ll_tv->vval.v_class;
|
||||
lp->ll_object = NULL;
|
||||
}
|
||||
lp->ll_class = cl;
|
||||
|
||||
// TODO: what if class is NULL?
|
||||
if (cl != NULL)
|
||||
{
|
||||
@ -1539,6 +1621,7 @@ get_lval(
|
||||
fp = method_lookup(cl,
|
||||
round == 1 ? VAR_CLASS : VAR_OBJECT,
|
||||
key, p - key, &m_idx);
|
||||
lp->ll_oi = m_idx;
|
||||
if (fp != NULL)
|
||||
{
|
||||
lp->ll_ufunc = fp;
|
||||
@ -1548,12 +1631,16 @@ get_lval(
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: dont' check access if inside class
|
||||
// TODO: is GLV_READ_ONLY the right thing to use
|
||||
// for class/object member access?
|
||||
// Probably in some cases. Need inside class check
|
||||
if (lp->ll_valtype == NULL)
|
||||
{
|
||||
int m_idx;
|
||||
ocmember_T *om;
|
||||
|
||||
om = member_lookup(cl, v_type, key, p - key, &m_idx);
|
||||
ocmember_T *om
|
||||
= member_lookup(cl, v_type, key, p - key, &m_idx);
|
||||
lp->ll_oi = m_idx;
|
||||
if (om != NULL)
|
||||
{
|
||||
switch (om->ocm_access)
|
||||
|
@ -2123,6 +2123,30 @@ do_unlet(char_u *name, int forceit)
|
||||
return FAIL;
|
||||
}
|
||||
|
||||
static void
|
||||
report_lockvar_member(char *msg, lval_T *lp)
|
||||
{
|
||||
int did_alloc = FALSE;
|
||||
char_u *vname = (char_u *)"";
|
||||
char_u *class_name = lp->ll_class != NULL
|
||||
? lp->ll_class->class_name : (char_u *)"";
|
||||
if (lp->ll_name != NULL)
|
||||
{
|
||||
if (lp->ll_name_end == NULL)
|
||||
vname = lp->ll_name;
|
||||
else
|
||||
{
|
||||
vname = vim_strnsave(lp->ll_name, lp->ll_name_end - lp->ll_name);
|
||||
if (vname == NULL)
|
||||
return;
|
||||
did_alloc = TRUE;
|
||||
}
|
||||
}
|
||||
semsg(_(msg), vname, class_name);
|
||||
if (did_alloc)
|
||||
vim_free(vname);
|
||||
}
|
||||
|
||||
/*
|
||||
* Lock or unlock variable indicated by "lp".
|
||||
* "deep" is the levels to go (-1 for unlimited);
|
||||
@ -2141,6 +2165,10 @@ do_lock_var(
|
||||
int cc;
|
||||
dictitem_T *di;
|
||||
|
||||
#ifdef LOG_LOCKVAR
|
||||
ch_log(NULL, "LKVAR: do_lock_var(): name %s, is_root %d", lp->ll_name, lp->ll_is_root);
|
||||
#endif
|
||||
|
||||
if (lp->ll_tv == NULL)
|
||||
{
|
||||
cc = *name_end;
|
||||
@ -2201,10 +2229,13 @@ do_lock_var(
|
||||
}
|
||||
*name_end = cc;
|
||||
}
|
||||
else if (deep == 0)
|
||||
else if (deep == 0 && lp->ll_object == NULL && lp->ll_class == NULL)
|
||||
{
|
||||
// nothing to do
|
||||
}
|
||||
else if (lp->ll_is_root)
|
||||
// (un)lock the item.
|
||||
item_lock(lp->ll_tv, deep, lock, FALSE);
|
||||
else if (lp->ll_range)
|
||||
{
|
||||
listitem_T *li = lp->ll_li;
|
||||
@ -2220,13 +2251,57 @@ do_lock_var(
|
||||
else if (lp->ll_list != NULL)
|
||||
// (un)lock a List item.
|
||||
item_lock(&lp->ll_li->li_tv, deep, lock, FALSE);
|
||||
else if (lp->ll_object != NULL) // This check must be before ll_class.
|
||||
{
|
||||
// (un)lock an object variable.
|
||||
report_lockvar_member(e_cannot_lock_object_variable_str, lp);
|
||||
ret = FAIL;
|
||||
}
|
||||
else if (lp->ll_class != NULL)
|
||||
{
|
||||
// (un)lock a class variable.
|
||||
report_lockvar_member(e_cannot_lock_class_variable_str, lp);
|
||||
ret = FAIL;
|
||||
}
|
||||
else
|
||||
{
|
||||
// (un)lock a Dictionary item.
|
||||
item_lock(&lp->ll_di->di_tv, deep, lock, FALSE);
|
||||
if (lp->ll_di == NULL)
|
||||
{
|
||||
emsg(_(e_dictionary_required));
|
||||
ret = FAIL;
|
||||
}
|
||||
else
|
||||
item_lock(&lp->ll_di->di_tv, deep, lock, FALSE);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#ifdef LOG_LOCKVAR
|
||||
static char *
|
||||
vartype_tostring(vartype_T vartype)
|
||||
{
|
||||
return
|
||||
vartype == VAR_BOOL ? "v_number"
|
||||
: vartype == VAR_SPECIAL ? "v_number"
|
||||
: vartype == VAR_NUMBER ? "v_number"
|
||||
: vartype == VAR_FLOAT ? "v_float"
|
||||
: vartype == VAR_STRING ? "v_string"
|
||||
: vartype == VAR_BLOB ? "v_blob"
|
||||
: vartype == VAR_FUNC ? "v_string"
|
||||
: vartype == VAR_PARTIAL ? "v_partial"
|
||||
: vartype == VAR_LIST ? "v_list"
|
||||
: vartype == VAR_DICT ? "v_dict"
|
||||
: vartype == VAR_JOB ? "v_job"
|
||||
: vartype == VAR_CHANNEL ? "v_channel"
|
||||
: vartype == VAR_INSTR ? "v_instr"
|
||||
: vartype == VAR_CLASS ? "v_class"
|
||||
: vartype == VAR_OBJECT ? "v_object"
|
||||
: "";
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* 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
|
||||
@ -2243,6 +2318,10 @@ item_lock(typval_T *tv, int deep, int lock, int check_refcount)
|
||||
hashitem_T *hi;
|
||||
int todo;
|
||||
|
||||
#ifdef LOG_LOCKVAR
|
||||
ch_log(NULL, "LKVAR: item_lock(): type %s", vartype_tostring(tv->v_type));
|
||||
#endif
|
||||
|
||||
if (recurse >= DICT_MAXNEST)
|
||||
{
|
||||
emsg(_(e_variable_nested_too_deep_for_unlock));
|
||||
|
@ -1954,6 +1954,7 @@ EXTERN int timer_busy INIT(= 0); // when timer is inside vgetc() then > 0
|
||||
EXTERN int input_busy INIT(= 0); // when inside get_user_input() then > 0
|
||||
|
||||
EXTERN typval_T *lval_root INIT(= NULL);
|
||||
EXTERN int lval_root_is_arg INIT(= 0);
|
||||
#endif
|
||||
|
||||
#ifdef FEAT_BEVAL_TERM
|
||||
|
@ -69,6 +69,7 @@ int generate_MULT_EXPR(cctx_T *cctx, isntype_T isn_type, int count);
|
||||
int generate_ECHOWINDOW(cctx_T *cctx, int count, long time);
|
||||
int generate_SOURCE(cctx_T *cctx, int sid);
|
||||
int generate_PUT(cctx_T *cctx, int regname, linenr_T lnum);
|
||||
int generate_LOCKUNLOCK(cctx_T *cctx, char_u *line, int is_arg);
|
||||
int generate_EXEC_copy(cctx_T *cctx, isntype_T isntype, char_u *line);
|
||||
int generate_EXEC(cctx_T *cctx, isntype_T isntype, char_u *str);
|
||||
int generate_LEGACY_EVAL(cctx_T *cctx, char_u *line);
|
||||
|
@ -4535,6 +4535,11 @@ typedef struct
|
||||
* "tv" points to the (first) list item value
|
||||
* "li" points to the (first) list item
|
||||
* "range", "n1", "n2" and "empty2" indicate what items are used.
|
||||
* For a member in a class/object: TODO: verify fields
|
||||
* "name" points to the (expanded) variable name.
|
||||
* "exp_name" NULL or non-NULL, to be freed later.
|
||||
* "tv" points to the (first) list item value
|
||||
* "oi" index into member array, see _type to determine which array
|
||||
* For an existing Dict item:
|
||||
* "name" points to the (expanded) variable name.
|
||||
* "exp_name" NULL or non-NULL, to be freed later.
|
||||
@ -4571,6 +4576,11 @@ typedef struct lval_S
|
||||
type_T *ll_valtype; // type expected for the value or NULL
|
||||
blob_T *ll_blob; // The Blob or NULL
|
||||
ufunc_T *ll_ufunc; // The function or NULL
|
||||
object_T *ll_object; // The object or NULL, class is not NULL
|
||||
class_T *ll_class; // The class or NULL, object may be NULL
|
||||
int ll_oi; // The object/class member index
|
||||
int ll_is_root; // Special case. ll_tv is lval_root,
|
||||
// ignore the rest.
|
||||
} lval_T;
|
||||
|
||||
// Structure used to save the current state. Used when executing Normal mode
|
||||
|
@ -3480,7 +3480,7 @@ enddef
|
||||
|
||||
" Test for locking a variable referring to an object and reassigning to another
|
||||
" object.
|
||||
def Test_object_lockvar()
|
||||
def Test_lockvar_object()
|
||||
var lines =<< trim END
|
||||
vim9script
|
||||
|
||||
@ -3515,6 +3515,480 @@ def Test_object_lockvar()
|
||||
v9.CheckSourceSuccess(lines)
|
||||
enddef
|
||||
|
||||
" Test trying to lock an object variable from various places
|
||||
def Test_lockvar_object_variable()
|
||||
# An object variable lockvar has several cases:
|
||||
# object method, scriptlevel, scriplevel from :def, :def arg
|
||||
# method arg, static method arg.
|
||||
# Also different depths
|
||||
|
||||
# TODO: handle inside_class in vim9class
|
||||
# lockvar of a read-only currently fails even if inside
|
||||
|
||||
#
|
||||
# lockvar of read-only object variable
|
||||
#
|
||||
|
||||
# read-only lockvar from object method
|
||||
var lines =<< trim END
|
||||
vim9script
|
||||
|
||||
class C
|
||||
this.val1: number
|
||||
def Lock()
|
||||
lockvar this.val1
|
||||
enddef
|
||||
endclass
|
||||
var o = C.new(3)
|
||||
o.Lock()
|
||||
END
|
||||
# TODO: wrong error
|
||||
v9.CheckSourceFailure(lines, 'E1335: Variable "val1" in class "C" is not writable')
|
||||
|
||||
# read-only lockvar from scriptlevel
|
||||
lines =<< trim END
|
||||
vim9script
|
||||
|
||||
class C
|
||||
this.val2: number
|
||||
endclass
|
||||
var o = C.new(3)
|
||||
lockvar o.val2
|
||||
END
|
||||
v9.CheckSourceFailure(lines, 'E1335: Variable "val2" in class "C" is not writable')
|
||||
|
||||
# read-only lockvar of scriptlevel variable from def
|
||||
lines =<< trim END
|
||||
vim9script
|
||||
|
||||
class C
|
||||
this.val3: number
|
||||
endclass
|
||||
var o = C.new(3)
|
||||
def Lock()
|
||||
lockvar o.val3
|
||||
enddef
|
||||
Lock()
|
||||
END
|
||||
v9.CheckSourceFailure(lines, 'E1335: Variable "val3" in class "C" is not writable')
|
||||
|
||||
# read-only lockvar of def argument variable
|
||||
lines =<< trim END
|
||||
vim9script
|
||||
|
||||
class C
|
||||
this.val4: number
|
||||
endclass
|
||||
def Lock(o: C)
|
||||
lockvar o.val4
|
||||
enddef
|
||||
Lock(C.new(3))
|
||||
END
|
||||
v9.CheckSourceFailure(lines, 'E1335: Variable "val4" in class "C" is not writable')
|
||||
|
||||
# TODO: the following tests use type "any" for argument. Need a run time
|
||||
# check for access. Probably OK as is for now.
|
||||
|
||||
# read-only lockvar from object method arg
|
||||
lines =<< trim END
|
||||
vim9script
|
||||
|
||||
class C
|
||||
this.val5: number
|
||||
def Lock(o_any: any)
|
||||
lockvar o_any.val5
|
||||
enddef
|
||||
endclass
|
||||
var o = C.new(3)
|
||||
o.Lock(C.new(5))
|
||||
END
|
||||
# TODO: wrong error, tricky since type "any"
|
||||
v9.CheckSourceFailure(lines, 'E1335: Variable "val5" in class "C" is not writable')
|
||||
|
||||
# read-only lockvar from class method arg
|
||||
lines =<< trim END
|
||||
vim9script
|
||||
|
||||
class C
|
||||
this.val6: number
|
||||
static def Lock(o_any: any)
|
||||
lockvar o_any.val6
|
||||
enddef
|
||||
endclass
|
||||
var o = C.new(3)
|
||||
C.Lock(o)
|
||||
END
|
||||
# TODO: wrong error, tricky since type "any"
|
||||
v9.CheckSourceFailure(lines, 'E1335: Variable "val6" in class "C" is not writable')
|
||||
|
||||
#
|
||||
# lockvar of public object variable
|
||||
#
|
||||
|
||||
# lockvar from object method
|
||||
lines =<< trim END
|
||||
vim9script
|
||||
|
||||
class C
|
||||
public this.val1: number
|
||||
def Lock()
|
||||
lockvar this.val1
|
||||
enddef
|
||||
endclass
|
||||
var o = C.new(3)
|
||||
o.Lock()
|
||||
END
|
||||
v9.CheckSourceFailure(lines, 'E1391: Cannot (un)lock variable "this.val1" in class "C"', 1)
|
||||
|
||||
# lockvar from scriptlevel
|
||||
lines =<< trim END
|
||||
vim9script
|
||||
|
||||
class C
|
||||
public this.val2: number
|
||||
endclass
|
||||
var o = C.new(3)
|
||||
lockvar o.val2
|
||||
END
|
||||
v9.CheckSourceFailure(lines, 'E1391: Cannot (un)lock variable "o.val2" in class "C"', 7)
|
||||
|
||||
# lockvar of scriptlevel variable from def
|
||||
lines =<< trim END
|
||||
vim9script
|
||||
|
||||
class C
|
||||
public this.val3: number
|
||||
endclass
|
||||
var o = C.new(3)
|
||||
def Lock()
|
||||
lockvar o.val3
|
||||
enddef
|
||||
Lock()
|
||||
END
|
||||
v9.CheckSourceFailure(lines, 'E1391: Cannot (un)lock variable "o.val3" in class "C"', 1)
|
||||
|
||||
# lockvar of def argument variable
|
||||
lines =<< trim END
|
||||
vim9script
|
||||
|
||||
class C
|
||||
public this.val4: number
|
||||
endclass
|
||||
def Lock(o: C)
|
||||
lockvar o.val4
|
||||
enddef
|
||||
Lock(C.new(3))
|
||||
END
|
||||
v9.CheckSourceFailure(lines, 'E1391: Cannot (un)lock variable "o.val4" in class "C"', 1)
|
||||
|
||||
# lockvar from object method arg
|
||||
lines =<< trim END
|
||||
vim9script
|
||||
|
||||
class C
|
||||
public this.val5: number
|
||||
def Lock(o_any: any)
|
||||
lockvar o_any.val5
|
||||
enddef
|
||||
endclass
|
||||
var o = C.new(3)
|
||||
o.Lock(C.new(5))
|
||||
END
|
||||
v9.CheckSourceFailure(lines, 'E1391: Cannot (un)lock variable "o_any.val5" in class "C"', 1)
|
||||
|
||||
# lockvar from class method arg
|
||||
lines =<< trim END
|
||||
vim9script
|
||||
|
||||
class C
|
||||
public this.val6: number
|
||||
static def Lock(o_any: any)
|
||||
lockvar o_any.val6
|
||||
enddef
|
||||
endclass
|
||||
var o = C.new(3)
|
||||
C.Lock(o)
|
||||
END
|
||||
v9.CheckSourceFailure(lines, 'E1391: Cannot (un)lock variable "o_any.val6" in class "C"', 1)
|
||||
enddef
|
||||
|
||||
" Test trying to lock a class variable from various places
|
||||
def Test_lockvar_class_variable()
|
||||
|
||||
# lockvar bare static from object method
|
||||
var lines =<< trim END
|
||||
vim9script
|
||||
|
||||
class C
|
||||
public static sval1: number
|
||||
def Lock()
|
||||
lockvar sval1
|
||||
enddef
|
||||
endclass
|
||||
var o = C.new()
|
||||
o.Lock()
|
||||
END
|
||||
v9.CheckSourceFailure(lines, 'E1392: Cannot (un)lock class variable "sval1" in class "C"', 1)
|
||||
|
||||
# lockvar C.static from object method
|
||||
lines =<< trim END
|
||||
vim9script
|
||||
|
||||
class C
|
||||
public static sval2: number
|
||||
def Lock()
|
||||
lockvar C.sval2
|
||||
enddef
|
||||
endclass
|
||||
var o = C.new()
|
||||
o.Lock()
|
||||
END
|
||||
v9.CheckSourceFailure(lines, 'E1392: Cannot (un)lock class variable "C.sval2" in class "C"', 1)
|
||||
|
||||
# lockvar bare static from class method
|
||||
lines =<< trim END
|
||||
vim9script
|
||||
|
||||
class C
|
||||
public static sval3: number
|
||||
static def Lock()
|
||||
lockvar sval3
|
||||
enddef
|
||||
endclass
|
||||
C.Lock()
|
||||
END
|
||||
v9.CheckSourceFailure(lines, 'E1392: Cannot (un)lock class variable "sval3" in class "C"', 1)
|
||||
|
||||
# lockvar C.static from class method
|
||||
lines =<< trim END
|
||||
vim9script
|
||||
|
||||
class C
|
||||
public static sval4: number
|
||||
static def Lock()
|
||||
lockvar C.sval4
|
||||
enddef
|
||||
endclass
|
||||
C.Lock()
|
||||
END
|
||||
v9.CheckSourceFailure(lines, 'E1392: Cannot (un)lock class variable "C.sval4" in class "C"', 1)
|
||||
|
||||
# lockvar C.static from script level
|
||||
lines =<< trim END
|
||||
vim9script
|
||||
|
||||
class C
|
||||
public static sval5: number
|
||||
endclass
|
||||
lockvar C.sval5
|
||||
END
|
||||
v9.CheckSourceFailure(lines, 'E1392: Cannot (un)lock class variable "C.sval5" in class "C"', 6)
|
||||
|
||||
# lockvar o.static from script level
|
||||
lines =<< trim END
|
||||
vim9script
|
||||
|
||||
class C
|
||||
public static sval6: number
|
||||
endclass
|
||||
var o = C.new()
|
||||
lockvar o.sval6
|
||||
END
|
||||
v9.CheckSourceFailure(lines, 'E1375: Class variable "sval6" accessible only using class "C"', 7)
|
||||
enddef
|
||||
|
||||
" Test locking an argument to :def
|
||||
def Test_lockvar_argument()
|
||||
# Lockvar a function arg
|
||||
var lines =<< trim END
|
||||
vim9script
|
||||
|
||||
def Lock(val: any)
|
||||
lockvar val
|
||||
enddef
|
||||
|
||||
var d = {a: 1, b: 2}
|
||||
Lock(d)
|
||||
|
||||
d->extend({c: 3})
|
||||
END
|
||||
v9.CheckSourceFailure(lines, 'E741: Value is locked: extend() argument')
|
||||
|
||||
# Lockvar a function arg. Verify "sval" is interpreted as argument and not a
|
||||
# class member in "C". This tests lval_root_is_arg.
|
||||
lines =<< trim END
|
||||
vim9script
|
||||
|
||||
class C
|
||||
public static sval: list<number>
|
||||
endclass
|
||||
|
||||
def Lock2(sval: any)
|
||||
lockvar sval
|
||||
enddef
|
||||
|
||||
var o = C.new()
|
||||
Lock2(o)
|
||||
END
|
||||
v9.CheckSourceSuccess(lines)
|
||||
|
||||
# Lock a class.
|
||||
lines =<< trim END
|
||||
vim9script
|
||||
|
||||
class C
|
||||
public static sval: list<number>
|
||||
endclass
|
||||
|
||||
def Lock2(sval: any)
|
||||
lockvar sval
|
||||
enddef
|
||||
|
||||
Lock2(C)
|
||||
END
|
||||
v9.CheckSourceSuccess(lines)
|
||||
|
||||
# Lock an object.
|
||||
lines =<< trim END
|
||||
vim9script
|
||||
|
||||
class C
|
||||
public static sval: list<number>
|
||||
endclass
|
||||
|
||||
def Lock2(sval: any)
|
||||
lockvar sval
|
||||
enddef
|
||||
|
||||
Lock2(C.new())
|
||||
END
|
||||
v9.CheckSourceSuccess(lines)
|
||||
|
||||
# In this case (unlike previous) "lockvar sval" is a class member.
|
||||
lines =<< trim END
|
||||
vim9script
|
||||
|
||||
class C
|
||||
public static sval: list<number>
|
||||
def Lock2()
|
||||
lockvar sval
|
||||
enddef
|
||||
endclass
|
||||
|
||||
|
||||
var o = C.new()
|
||||
o.Lock2()
|
||||
END
|
||||
v9.CheckSourceFailure(lines, 'E1392: Cannot (un)lock class variable "sval" in class "C"', 1)
|
||||
enddef
|
||||
|
||||
" Test that this can be locked without error
|
||||
def Test_lockvar_this()
|
||||
# lockvar this
|
||||
var lines =<< trim END
|
||||
vim9script
|
||||
class C
|
||||
def TLock()
|
||||
lockvar this
|
||||
enddef
|
||||
endclass
|
||||
var o = C.new()
|
||||
o.TLock()
|
||||
END
|
||||
v9.CheckSourceSuccess(lines)
|
||||
|
||||
# lockvar four (four letter word, but not this)
|
||||
lines =<< trim END
|
||||
vim9script
|
||||
class C
|
||||
def TLock4()
|
||||
var four: number
|
||||
lockvar four
|
||||
enddef
|
||||
endclass
|
||||
var o = C.new()
|
||||
o.TLock4()
|
||||
END
|
||||
v9.CheckSourceFailure(lines, 'E1178: Cannot lock or unlock a local variable')
|
||||
|
||||
# lockvar this5; "this" + one char, 5 letter word, starting with "this"
|
||||
lines =<< trim END
|
||||
vim9script
|
||||
class C
|
||||
def TLock5()
|
||||
var this5: number
|
||||
lockvar this5
|
||||
enddef
|
||||
endclass
|
||||
var o = C.new()
|
||||
o.TLock5()
|
||||
END
|
||||
v9.CheckSourceFailure(lines, 'E1178: Cannot lock or unlock a local variable')
|
||||
enddef
|
||||
|
||||
" Test some general lockvar cases
|
||||
def Test_lockvar_general()
|
||||
# lockvar an object and a class. It does nothing
|
||||
var lines =<< trim END
|
||||
vim9script
|
||||
class C
|
||||
endclass
|
||||
var o = C.new()
|
||||
lockvar o
|
||||
lockvar C
|
||||
END
|
||||
v9.CheckSourceSuccess(lines)
|
||||
|
||||
# Lock a list element that's nested in an object variable from a :def
|
||||
lines =<< trim END
|
||||
vim9script
|
||||
|
||||
class C
|
||||
public this.val: list<list<number>> = [ [1], [2], [3] ]
|
||||
endclass
|
||||
def Lock2(obj: any)
|
||||
lockvar obj.val[1]
|
||||
enddef
|
||||
|
||||
var o = C.new()
|
||||
Lock2(o)
|
||||
o.val[0] = [9]
|
||||
assert_equal([ [9], [2], [3] ], o.val)
|
||||
try
|
||||
o.val[1] = [999]
|
||||
call assert_false(true, 'assign should have failed')
|
||||
catch
|
||||
assert_exception('E741:')
|
||||
endtry
|
||||
o.val[2] = [8]
|
||||
assert_equal([ [9], [2], [8] ], o.val)
|
||||
END
|
||||
v9.CheckSourceSuccess(lines)
|
||||
|
||||
# Lock a list element that's nested in an object variable from scriptlevel
|
||||
lines =<< trim END
|
||||
vim9script
|
||||
|
||||
class C
|
||||
public this.val: list<list<number>> = [ [1], [2], [3] ]
|
||||
endclass
|
||||
|
||||
var o = C.new()
|
||||
lockvar o.val[1]
|
||||
o.val[0] = [9]
|
||||
assert_equal([ [9], [2], [3] ], o.val)
|
||||
try
|
||||
o.val[1] = [999]
|
||||
call assert_false(true, 'assign should have failed')
|
||||
catch
|
||||
assert_exception('E741:')
|
||||
endtry
|
||||
o.val[2] = [8]
|
||||
assert_equal([ [9], [2], [8] ], o.val)
|
||||
END
|
||||
v9.CheckSourceSuccess(lines)
|
||||
enddef
|
||||
|
||||
" Test for a private object method
|
||||
def Test_private_object_method()
|
||||
# Try calling a private method using an object (at the script level)
|
||||
|
@ -699,6 +699,8 @@ static char *(features[]) =
|
||||
|
||||
static int included_patches[] =
|
||||
{ /* Add new patch number below this line */
|
||||
/**/
|
||||
1955,
|
||||
/**/
|
||||
1954,
|
||||
/**/
|
||||
|
@ -499,12 +499,19 @@ typedef struct {
|
||||
class_T *cm_class;
|
||||
int cm_idx;
|
||||
} classmember_T;
|
||||
|
||||
// arguments to ISN_STOREINDEX
|
||||
typedef struct {
|
||||
vartype_T si_vartype;
|
||||
class_T *si_class;
|
||||
} storeindex_T;
|
||||
|
||||
// arguments to ISN_LOCKUNLOCK
|
||||
typedef struct {
|
||||
char_u *string; // for exec_command
|
||||
int is_arg; // is lval_root a function arg
|
||||
} lockunlock_T;
|
||||
|
||||
/*
|
||||
* Instruction
|
||||
*/
|
||||
@ -561,6 +568,7 @@ struct isn_S {
|
||||
construct_T construct;
|
||||
classmember_T classmember;
|
||||
storeindex_T storeindex;
|
||||
lockunlock_T lockunlock;
|
||||
} isn_arg;
|
||||
};
|
||||
|
||||
|
@ -189,10 +189,15 @@ compile_lock_unlock(
|
||||
int cc = *name_end;
|
||||
char_u *p = lvp->ll_name;
|
||||
int ret = OK;
|
||||
size_t len;
|
||||
char_u *buf;
|
||||
isntype_T isn = ISN_EXEC;
|
||||
char *cmd = eap->cmdidx == CMD_lockvar ? "lockvar" : "unlockvar";
|
||||
int is_arg = FALSE;
|
||||
|
||||
#ifdef LOG_LOCKVAR
|
||||
ch_log(NULL, "LKVAR: compile_lock_unlock(): cookie %p, name %s",
|
||||
coookie, p);
|
||||
#endif
|
||||
|
||||
if (cctx->ctx_skip == SKIP_YES)
|
||||
return OK;
|
||||
@ -207,20 +212,68 @@ compile_lock_unlock(
|
||||
if (p[1] != ':')
|
||||
{
|
||||
char_u *end = find_name_end(p, NULL, NULL, FNE_CHECK_START);
|
||||
// If name is is locally accessible, except for local var,
|
||||
// then put it on the stack to use with ISN_LOCKUNLOCK.
|
||||
// This could be v.memb, v[idx_key]; bare class variable,
|
||||
// function arg. The local variable on the stack, will be passed
|
||||
// to ex_lockvar() indirectly.
|
||||
|
||||
if (lookup_local(p, end - p, NULL, cctx) == OK)
|
||||
char_u *name = NULL;
|
||||
int len = end - p;
|
||||
|
||||
if (lookup_local(p, len, NULL, cctx) == OK)
|
||||
{
|
||||
char_u *s = p;
|
||||
|
||||
if (*end != '.' && *end != '[')
|
||||
// Handle "this", "this.val", "anyvar[idx]"
|
||||
if (*end != '.' && *end != '['
|
||||
&& (len != 4 || STRNCMP("this", p, len) != 0))
|
||||
{
|
||||
emsg(_(e_cannot_lock_unlock_local_variable));
|
||||
return FAIL;
|
||||
}
|
||||
|
||||
// For "d.member" put the local variable on the stack, it will be
|
||||
// passed to ex_lockvar() indirectly.
|
||||
if (compile_load(&s, end, cctx, FALSE, FALSE) == FAIL)
|
||||
// Push the local on the stack, could be "this".
|
||||
name = p;
|
||||
#ifdef LOG_LOCKVAR
|
||||
ch_log(NULL, "LKVAR: compile... lookup_local: name %s", name);
|
||||
#endif
|
||||
}
|
||||
if (name == NULL)
|
||||
{
|
||||
class_T *cl;
|
||||
if (cctx_class_member_idx(cctx, p, len, &cl) >= 0)
|
||||
{
|
||||
if (*end != '.' && *end != '[')
|
||||
{
|
||||
// Push the class of the bare class variable name
|
||||
name = cl->class_name;
|
||||
len = STRLEN(name);
|
||||
#ifdef LOG_LOCKVAR
|
||||
ch_log(NULL, "LKVAR: compile... cctx_class_member: name %s",
|
||||
name);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
if (name == NULL)
|
||||
{
|
||||
int idx;
|
||||
type_T *type;
|
||||
// Can lockvar any function arg.
|
||||
// TODO: test arg[idx]/arg.member
|
||||
if (arg_exists(p, len, &idx, &type, NULL, cctx) == OK)
|
||||
{
|
||||
name = p;
|
||||
is_arg = TRUE;
|
||||
#ifdef LOG_LOCKVAR
|
||||
ch_log(NULL, "LKVAR: compile... arg_exists: name %s", name);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
if (name != NULL)
|
||||
{
|
||||
#ifdef LOG_LOCKVAR
|
||||
ch_log(NULL, "LKVAR: compile... INS_LOCKUNLOCK %s", name);
|
||||
#endif
|
||||
if (compile_load(&name, name + len, cctx, FALSE, FALSE) == FAIL)
|
||||
return FAIL;
|
||||
isn = ISN_LOCKUNLOCK;
|
||||
}
|
||||
@ -228,7 +281,7 @@ compile_lock_unlock(
|
||||
|
||||
// Checking is done at runtime.
|
||||
*name_end = NUL;
|
||||
len = name_end - p + 20;
|
||||
size_t len = name_end - p + 20;
|
||||
buf = alloc(len);
|
||||
if (buf == NULL)
|
||||
ret = FAIL;
|
||||
@ -238,7 +291,13 @@ compile_lock_unlock(
|
||||
vim_snprintf((char *)buf, len, "%s! %s", cmd, p);
|
||||
else
|
||||
vim_snprintf((char *)buf, len, "%s %d %s", cmd, deep, p);
|
||||
ret = generate_EXEC_copy(cctx, isn, buf);
|
||||
#ifdef LOG_LOCKVAR
|
||||
ch_log(NULL, "LKVAR: compile... buf %s", buf);
|
||||
#endif
|
||||
if (isn == ISN_LOCKUNLOCK)
|
||||
ret = generate_LOCKUNLOCK(cctx, buf, is_arg);
|
||||
else
|
||||
ret = generate_EXEC_copy(cctx, isn, buf);
|
||||
|
||||
vim_free(buf);
|
||||
*name_end = cc;
|
||||
|
@ -1957,7 +1957,7 @@ fill_partial_and_closure(
|
||||
* Execute iptr->isn_arg.string as an Ex command.
|
||||
*/
|
||||
static int
|
||||
exec_command(isn_T *iptr)
|
||||
exec_command(isn_T *iptr, char_u *cmd_string)
|
||||
{
|
||||
source_cookie_T cookie;
|
||||
|
||||
@ -1965,8 +1965,7 @@ exec_command(isn_T *iptr)
|
||||
// Pass getsourceline to get an error for a missing ":end" command.
|
||||
CLEAR_FIELD(cookie);
|
||||
cookie.sourcing_lnum = iptr->isn_lnum - 1;
|
||||
if (do_cmdline(iptr->isn_arg.string,
|
||||
getsourceline, &cookie,
|
||||
if (do_cmdline(cmd_string, getsourceline, &cookie,
|
||||
DOCMD_VERBOSE|DOCMD_NOWAIT|DOCMD_KEYTYPED) == FAIL
|
||||
|| did_emsg)
|
||||
return FAIL;
|
||||
@ -3182,7 +3181,7 @@ exec_instructions(ectx_T *ectx)
|
||||
|
||||
// execute Ex command line
|
||||
case ISN_EXEC:
|
||||
if (exec_command(iptr) == FAIL)
|
||||
if (exec_command(iptr, iptr->isn_arg.string) == FAIL)
|
||||
goto on_error;
|
||||
break;
|
||||
|
||||
@ -4179,16 +4178,24 @@ exec_instructions(ectx_T *ectx)
|
||||
|
||||
case ISN_LOCKUNLOCK:
|
||||
{
|
||||
// TODO: could put lval_root info in struct
|
||||
typval_T *lval_root_save = lval_root;
|
||||
int lval_root_is_arg_save = lval_root_is_arg;
|
||||
int res;
|
||||
#ifdef LOG_LOCKVAR
|
||||
ch_log(NULL, "LKVAR: execute INS_LOCKUNLOCK isn_arg %s",
|
||||
iptr->isn_arg.string);
|
||||
#endif
|
||||
|
||||
// Stack has the local variable, argument the whole :lock
|
||||
// or :unlock command, like ISN_EXEC.
|
||||
--ectx->ec_stack.ga_len;
|
||||
lval_root = STACK_TV_BOT(0);
|
||||
res = exec_command(iptr);
|
||||
lval_root_is_arg = iptr->isn_arg.lockunlock.is_arg;
|
||||
res = exec_command(iptr, iptr->isn_arg.lockunlock.string);
|
||||
clear_tv(lval_root);
|
||||
lval_root = lval_root_save;
|
||||
lval_root_is_arg = lval_root_is_arg_save;
|
||||
if (res == FAIL)
|
||||
goto on_error;
|
||||
}
|
||||
|
@ -2169,6 +2169,23 @@ generate_PUT(cctx_T *cctx, int regname, linenr_T lnum)
|
||||
return OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* Generate an EXEC instruction that takes a string argument.
|
||||
* A copy is made of "line".
|
||||
*/
|
||||
int
|
||||
generate_LOCKUNLOCK(cctx_T *cctx, char_u *line, int is_arg)
|
||||
{
|
||||
isn_T *isn;
|
||||
|
||||
RETURN_OK_IF_SKIP(cctx);
|
||||
if ((isn = generate_instr(cctx, ISN_LOCKUNLOCK)) == NULL)
|
||||
return FAIL;
|
||||
isn->isn_arg.lockunlock.string = vim_strsave(line);
|
||||
isn->isn_arg.lockunlock.is_arg = is_arg;
|
||||
return OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* Generate an EXEC instruction that takes a string argument.
|
||||
* A copy is made of "line".
|
||||
|
Loading…
x
Reference in New Issue
Block a user