Skip to content

Commit ee865f3

Browse files
erraelchrisbra
authored andcommitted
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 <[email protected]> Co-authored-by: Ernie Rael <[email protected]>
1 parent 112431f commit ee865f3

12 files changed

Lines changed: 776 additions & 27 deletions

File tree

src/errors.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3534,8 +3534,12 @@ EXTERN char e_missing_name_after_implements[]
35343534
INIT(= N_("E1389: Missing name after implements"));
35353535
EXTERN char e_cannot_use_an_object_variable_except_with_the_new_method_str[]
35363536
INIT(= N_("E1390: Cannot use an object variable \"this.%s\" except with the \"new\" method"));
3537+
EXTERN char e_cannot_lock_object_variable_str[]
3538+
INIT(= N_("E1391: Cannot (un)lock variable \"%s\" in class \"%s\""));
3539+
EXTERN char e_cannot_lock_class_variable_str[]
3540+
INIT(= N_("E1392: Cannot (un)lock class variable \"%s\" in class \"%s\""));
35373541
#endif
3538-
// E1391 - E1499 unused (reserved for Vim9 class support)
3542+
// E1393 - E1499 unused (reserved for Vim9 class support)
35393543
EXTERN char e_cannot_mix_positional_and_non_positional_str[]
35403544
INIT(= N_("E1500: Cannot mix positional and non-positional arguments: %s"));
35413545
EXTERN char e_fmt_arg_nr_unused_str[]

src/eval.c

Lines changed: 94 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -985,6 +985,62 @@ eval_foldexpr(win_T *wp, int *cp)
985985
}
986986
#endif
987987

988+
/*
989+
* Fill in "lp" using "root". This is used in a special case when
990+
* "get_lval()" parses a bare word when "lval_root" is not NULL.
991+
*
992+
* This is typically called with "lval_root" as "root". For a class, find
993+
* the name from lp in the class from root, fill in lval_T if found. For a
994+
* complex type, list/dict use it as the result; just put the root into
995+
* ll_tv.
996+
*
997+
* "lval_root" is a hack used during run-time/instr-execution to provide the
998+
* starting point for "get_lval()" to traverse a chain of indexes. In some
999+
* cases get_lval sees a bare name and uses this function to populate the
1000+
* lval_T.
1001+
*
1002+
* For setting up "lval_root" (currently only used with lockvar)
1003+
* compile_lock_unlock - pushes object on stack (which becomes lval_root)
1004+
* execute_instructions: ISN_LOCKUNLOCK - sets lval_root from stack.
1005+
*/
1006+
static void
1007+
get_lval_root(lval_T *lp, typval_T *root, int is_arg)
1008+
{
1009+
#ifdef LOG_LOCKVAR
1010+
ch_log(NULL, "LKVAR: get_lvalroot(): name %s", lp->ll_name);
1011+
#endif
1012+
if (!is_arg && root->v_type == VAR_CLASS)
1013+
{
1014+
if (root->vval.v_class != NULL)
1015+
{
1016+
// Special special case. Look for a bare class variable reference.
1017+
class_T *cl = root->vval.v_class;
1018+
int m_idx;
1019+
ocmember_T *m = class_member_lookup(cl, lp->ll_name,
1020+
lp->ll_name_end - lp->ll_name, &m_idx);
1021+
if (m != NULL)
1022+
{
1023+
// Assuming "inside class" since bare reference.
1024+
lp->ll_class = root->vval.v_class;
1025+
lp->ll_oi = m_idx;
1026+
lp->ll_valtype = m->ocm_type;
1027+
lp->ll_tv = &lp->ll_class->class_members_tv[m_idx];
1028+
#ifdef LOG_LOCKVAR
1029+
ch_log(NULL, "LKVAR: get_lvalroot() class member: name %s",
1030+
lp->ll_name);
1031+
#endif
1032+
return;
1033+
}
1034+
}
1035+
}
1036+
1037+
#ifdef LOG_LOCKVAR
1038+
ch_log(NULL, "LKVAR: get_lvalroot() any type");
1039+
#endif
1040+
lp->ll_tv = root;
1041+
lp->ll_is_root = TRUE;
1042+
}
1043+
9881044
/*
9891045
* Get an lval: variable, Dict item or List item that can be assigned a value
9901046
* to: "name", "na{me}", "name[expr]", "name[expr:expr]", "name[expr][expr]",
@@ -1028,6 +1084,11 @@ get_lval(
10281084
int writing = 0;
10291085
int vim9script = in_vim9script();
10301086

1087+
#ifdef LOG_LOCKVAR
1088+
ch_log(NULL, "LKVAR: get_lval(): name %s, lval_root %p",
1089+
name, (void*)lval_root);
1090+
#endif
1091+
10311092
// Clear everything in "lp".
10321093
CLEAR_POINTER(lp);
10331094

@@ -1122,6 +1183,7 @@ get_lval(
11221183
return NULL;
11231184
lp->ll_name_end = tp;
11241185
}
1186+
// TODO: check inside class?
11251187
}
11261188
}
11271189
if (lp->ll_name == NULL)
@@ -1157,7 +1219,11 @@ get_lval(
11571219

11581220
// Without [idx] or .key we are done.
11591221
if ((*p != '[' && *p != '.'))
1222+
{
1223+
if (lval_root != NULL)
1224+
get_lval_root(lp, lval_root, lval_root_is_arg);
11601225
return p;
1226+
}
11611227

11621228
if (vim9script && lval_root != NULL)
11631229
{
@@ -1350,6 +1416,8 @@ get_lval(
13501416
}
13511417
}
13521418
lp->ll_list = NULL;
1419+
lp->ll_object = NULL;
1420+
lp->ll_class = NULL;
13531421

13541422
// a NULL dict is equivalent with an empty dict
13551423
if (lp->ll_tv->vval.v_dict == NULL)
@@ -1482,6 +1550,8 @@ get_lval(
14821550
clear_tv(&var1);
14831551

14841552
lp->ll_dict = NULL;
1553+
lp->ll_object = NULL;
1554+
lp->ll_class = NULL;
14851555
lp->ll_list = lp->ll_tv->vval.v_list;
14861556
lp->ll_li = check_range_index_one(lp->ll_list, &lp->ll_n1,
14871557
(flags & GLV_ASSIGN_WITH_OP) == 0, quiet);
@@ -1516,10 +1586,22 @@ get_lval(
15161586
}
15171587
else // v_type == VAR_CLASS || v_type == VAR_OBJECT
15181588
{
1519-
class_T *cl = (v_type == VAR_OBJECT
1520-
&& lp->ll_tv->vval.v_object != NULL)
1521-
? lp->ll_tv->vval.v_object->obj_class
1522-
: lp->ll_tv->vval.v_class;
1589+
lp->ll_dict = NULL;
1590+
lp->ll_list = NULL;
1591+
1592+
class_T *cl;
1593+
if (v_type == VAR_OBJECT && lp->ll_tv->vval.v_object != NULL)
1594+
{
1595+
cl = lp->ll_tv->vval.v_object->obj_class;
1596+
lp->ll_object = lp->ll_tv->vval.v_object;
1597+
}
1598+
else
1599+
{
1600+
cl = lp->ll_tv->vval.v_class;
1601+
lp->ll_object = NULL;
1602+
}
1603+
lp->ll_class = cl;
1604+
15231605
// TODO: what if class is NULL?
15241606
if (cl != NULL)
15251607
{
@@ -1539,6 +1621,7 @@ get_lval(
15391621
fp = method_lookup(cl,
15401622
round == 1 ? VAR_CLASS : VAR_OBJECT,
15411623
key, p - key, &m_idx);
1624+
lp->ll_oi = m_idx;
15421625
if (fp != NULL)
15431626
{
15441627
lp->ll_ufunc = fp;
@@ -1548,12 +1631,16 @@ get_lval(
15481631
}
15491632
}
15501633

1634+
// TODO: dont' check access if inside class
1635+
// TODO: is GLV_READ_ONLY the right thing to use
1636+
// for class/object member access?
1637+
// Probably in some cases. Need inside class check
15511638
if (lp->ll_valtype == NULL)
15521639
{
15531640
int m_idx;
1554-
ocmember_T *om;
1555-
1556-
om = member_lookup(cl, v_type, key, p - key, &m_idx);
1641+
ocmember_T *om
1642+
= member_lookup(cl, v_type, key, p - key, &m_idx);
1643+
lp->ll_oi = m_idx;
15571644
if (om != NULL)
15581645
{
15591646
switch (om->ocm_access)

src/evalvars.c

Lines changed: 81 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2123,6 +2123,30 @@ do_unlet(char_u *name, int forceit)
21232123
return FAIL;
21242124
}
21252125

2126+
static void
2127+
report_lockvar_member(char *msg, lval_T *lp)
2128+
{
2129+
int did_alloc = FALSE;
2130+
char_u *vname = (char_u *)"";
2131+
char_u *class_name = lp->ll_class != NULL
2132+
? lp->ll_class->class_name : (char_u *)"";
2133+
if (lp->ll_name != NULL)
2134+
{
2135+
if (lp->ll_name_end == NULL)
2136+
vname = lp->ll_name;
2137+
else
2138+
{
2139+
vname = vim_strnsave(lp->ll_name, lp->ll_name_end - lp->ll_name);
2140+
if (vname == NULL)
2141+
return;
2142+
did_alloc = TRUE;
2143+
}
2144+
}
2145+
semsg(_(msg), vname, class_name);
2146+
if (did_alloc)
2147+
vim_free(vname);
2148+
}
2149+
21262150
/*
21272151
* Lock or unlock variable indicated by "lp".
21282152
* "deep" is the levels to go (-1 for unlimited);
@@ -2141,6 +2165,10 @@ do_lock_var(
21412165
int cc;
21422166
dictitem_T *di;
21432167

2168+
#ifdef LOG_LOCKVAR
2169+
ch_log(NULL, "LKVAR: do_lock_var(): name %s, is_root %d", lp->ll_name, lp->ll_is_root);
2170+
#endif
2171+
21442172
if (lp->ll_tv == NULL)
21452173
{
21462174
cc = *name_end;
@@ -2201,10 +2229,13 @@ do_lock_var(
22012229
}
22022230
*name_end = cc;
22032231
}
2204-
else if (deep == 0)
2232+
else if (deep == 0 && lp->ll_object == NULL && lp->ll_class == NULL)
22052233
{
22062234
// nothing to do
22072235
}
2236+
else if (lp->ll_is_root)
2237+
// (un)lock the item.
2238+
item_lock(lp->ll_tv, deep, lock, FALSE);
22082239
else if (lp->ll_range)
22092240
{
22102241
listitem_T *li = lp->ll_li;
@@ -2220,13 +2251,57 @@ do_lock_var(
22202251
else if (lp->ll_list != NULL)
22212252
// (un)lock a List item.
22222253
item_lock(&lp->ll_li->li_tv, deep, lock, FALSE);
2254+
else if (lp->ll_object != NULL) // This check must be before ll_class.
2255+
{
2256+
// (un)lock an object variable.
2257+
report_lockvar_member(e_cannot_lock_object_variable_str, lp);
2258+
ret = FAIL;
2259+
}
2260+
else if (lp->ll_class != NULL)
2261+
{
2262+
// (un)lock a class variable.
2263+
report_lockvar_member(e_cannot_lock_class_variable_str, lp);
2264+
ret = FAIL;
2265+
}
22232266
else
2267+
{
22242268
// (un)lock a Dictionary item.
2225-
item_lock(&lp->ll_di->di_tv, deep, lock, FALSE);
2269+
if (lp->ll_di == NULL)
2270+
{
2271+
emsg(_(e_dictionary_required));
2272+
ret = FAIL;
2273+
}
2274+
else
2275+
item_lock(&lp->ll_di->di_tv, deep, lock, FALSE);
2276+
}
22262277

22272278
return ret;
22282279
}
22292280

2281+
#ifdef LOG_LOCKVAR
2282+
static char *
2283+
vartype_tostring(vartype_T vartype)
2284+
{
2285+
return
2286+
vartype == VAR_BOOL ? "v_number"
2287+
: vartype == VAR_SPECIAL ? "v_number"
2288+
: vartype == VAR_NUMBER ? "v_number"
2289+
: vartype == VAR_FLOAT ? "v_float"
2290+
: vartype == VAR_STRING ? "v_string"
2291+
: vartype == VAR_BLOB ? "v_blob"
2292+
: vartype == VAR_FUNC ? "v_string"
2293+
: vartype == VAR_PARTIAL ? "v_partial"
2294+
: vartype == VAR_LIST ? "v_list"
2295+
: vartype == VAR_DICT ? "v_dict"
2296+
: vartype == VAR_JOB ? "v_job"
2297+
: vartype == VAR_CHANNEL ? "v_channel"
2298+
: vartype == VAR_INSTR ? "v_instr"
2299+
: vartype == VAR_CLASS ? "v_class"
2300+
: vartype == VAR_OBJECT ? "v_object"
2301+
: "";
2302+
}
2303+
#endif
2304+
22302305
/*
22312306
* Lock or unlock an item. "deep" is nr of levels to go.
22322307
* 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)
22432318
hashitem_T *hi;
22442319
int todo;
22452320

2321+
#ifdef LOG_LOCKVAR
2322+
ch_log(NULL, "LKVAR: item_lock(): type %s", vartype_tostring(tv->v_type));
2323+
#endif
2324+
22462325
if (recurse >= DICT_MAXNEST)
22472326
{
22482327
emsg(_(e_variable_nested_too_deep_for_unlock));

src/globals.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1954,6 +1954,7 @@ EXTERN int timer_busy INIT(= 0); // when timer is inside vgetc() then > 0
19541954
EXTERN int input_busy INIT(= 0); // when inside get_user_input() then > 0
19551955

19561956
EXTERN typval_T *lval_root INIT(= NULL);
1957+
EXTERN int lval_root_is_arg INIT(= 0);
19571958
#endif
19581959

19591960
#ifdef FEAT_BEVAL_TERM

src/proto/vim9instr.pro

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ int generate_MULT_EXPR(cctx_T *cctx, isntype_T isn_type, int count);
6969
int generate_ECHOWINDOW(cctx_T *cctx, int count, long time);
7070
int generate_SOURCE(cctx_T *cctx, int sid);
7171
int generate_PUT(cctx_T *cctx, int regname, linenr_T lnum);
72+
int generate_LOCKUNLOCK(cctx_T *cctx, char_u *line, int is_arg);
7273
int generate_EXEC_copy(cctx_T *cctx, isntype_T isntype, char_u *line);
7374
int generate_EXEC(cctx_T *cctx, isntype_T isntype, char_u *str);
7475
int generate_LEGACY_EVAL(cctx_T *cctx, char_u *line);

src/structs.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4535,6 +4535,11 @@ typedef struct
45354535
* "tv" points to the (first) list item value
45364536
* "li" points to the (first) list item
45374537
* "range", "n1", "n2" and "empty2" indicate what items are used.
4538+
* For a member in a class/object: TODO: verify fields
4539+
* "name" points to the (expanded) variable name.
4540+
* "exp_name" NULL or non-NULL, to be freed later.
4541+
* "tv" points to the (first) list item value
4542+
* "oi" index into member array, see _type to determine which array
45384543
* For an existing Dict item:
45394544
* "name" points to the (expanded) variable name.
45404545
* "exp_name" NULL or non-NULL, to be freed later.
@@ -4571,6 +4576,11 @@ typedef struct lval_S
45714576
type_T *ll_valtype; // type expected for the value or NULL
45724577
blob_T *ll_blob; // The Blob or NULL
45734578
ufunc_T *ll_ufunc; // The function or NULL
4579+
object_T *ll_object; // The object or NULL, class is not NULL
4580+
class_T *ll_class; // The class or NULL, object may be NULL
4581+
int ll_oi; // The object/class member index
4582+
int ll_is_root; // Special case. ll_tv is lval_root,
4583+
// ignore the rest.
45744584
} lval_T;
45754585

45764586
// Structure used to save the current state. Used when executing Normal mode

0 commit comments

Comments
 (0)