Skip to content

Commit

Permalink
patch 9.0.1955: Vim9: lockvar issues with objects/classes
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
errael authored and chrisbra committed Sep 29, 2023
1 parent 112431f commit ee865f3
Show file tree
Hide file tree
Showing 12 changed files with 776 additions and 27 deletions.
6 changes: 5 additions & 1 deletion src/errors.h
Original file line number Diff line number Diff line change
Expand Up @@ -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[]
Expand Down
101 changes: 94 additions & 7 deletions src/eval.c
Original file line number Diff line number Diff line change
Expand Up @@ -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]",
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -1122,6 +1183,7 @@ get_lval(
return NULL;
lp->ll_name_end = tp;
}
// TODO: check inside class?
}
}
if (lp->ll_name == NULL)
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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)
{
Expand All @@ -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;
Expand All @@ -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)
Expand Down
83 changes: 81 additions & 2 deletions src/evalvars.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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
Expand All @@ -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));
Expand Down
1 change: 1 addition & 0 deletions src/globals.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/proto/vim9instr.pro
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
10 changes: 10 additions & 0 deletions src/structs.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit ee865f3

Please sign in to comment.