# HG changeset patch # User Christian Brabandt # Date 1696444203 -7200 # Node ID 4a62e78803db0dca23b040975d52a919e9a8fa6c # Parent d1dddc5cb66e1a50f5ad9742e45478951a652de5 patch 9.0.1982: vim9: clean up from v9.0.1955 Commit: https://github.com/vim/vim/commit/64885645e76b301a6c34fe762c4e29c7a0f63881 Author: Ernie Rael Date: Wed Oct 4 20:16:22 2023 +0200 patch 9.0.1982: vim9: clean up from v9.0.1955 Problem: vim9: clean up from v9.0.1955 Solution: Fix a few remaining issues, improve error message - Use `cl_exec`, the executing class, to check permissions in `get_lval()`. - Handle lockvar of script variable from class. - Add 'in class "Xxx"' to e_cannot_access_private_variable_str. closes: #13222 Signed-off-by: Christian Brabandt Co-authored-by: Ernie Rael diff --git a/src/errors.h b/src/errors.h --- a/src/errors.h +++ b/src/errors.h @@ -3411,7 +3411,7 @@ EXTERN char e_public_must_be_followed_by EXTERN char e_public_variable_name_cannot_start_with_underscore_str[] INIT(= N_("E1332: Public variable name cannot start with underscore: %s")); EXTERN char e_cannot_access_private_variable_str[] - INIT(= N_("E1333: Cannot access private variable: %s")); + INIT(= N_("E1333: Cannot access private variable \"%s\" in class \"%s\"")); // E1334 unused EXTERN char e_variable_is_not_writable_str[] INIT(= N_("E1335: Variable \"%s\" in class \"%s\" is not writable")); diff --git a/src/eval.c b/src/eval.c --- a/src/eval.c +++ b/src/eval.c @@ -985,6 +985,52 @@ eval_foldexpr(win_T *wp, int *cp) } #endif +#ifdef LOG_LOCKVAR +typedef struct flag_string_S +{ + int flag; + char *str; +} flag_string_T; + + static char * +flags_tostring(int flags, flag_string_T *_fstring, char *buf, size_t n) +{ + char *p = buf; + *p = NUL; + for (flag_string_T *fstring = _fstring; fstring->flag; ++fstring) + { + if ((fstring->flag & flags) != 0) + { + size_t len = STRLEN(fstring->str); + if (n > p - buf + len + 7) + { + STRCAT(p, fstring->str); + p += len; + STRCAT(p, " "); + ++p; + } + else + { + STRCAT(buf, "..."); + break; + } + } + } + return buf; +} + +flag_string_T glv_flag_strings[] = { + { GLV_QUIET, "QUIET" }, + { GLV_NO_AUTOLOAD, "NO_AUTOLOAD" }, + { GLV_READ_ONLY, "READ_ONLY" }, + { GLV_NO_DECL, "NO_DECL" }, + { GLV_COMPILING, "COMPILING" }, + { GLV_ASSIGN_WITH_OP, "ASSIGN_WITH_OP" }, + { GLV_PREFER_FUNC, "PREFER_FUNC" }, + { 0, NULL } +}; +#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. @@ -1004,30 +1050,30 @@ eval_foldexpr(win_T *wp, int *cp) * execute_instructions: ISN_LOCKUNLOCK - sets lval_root from stack. */ static void -get_lval_root(lval_T *lp, typval_T *root, int is_arg) +get_lval_root(lval_T *lp, lval_root_T *lr) { #ifdef LOG_LOCKVAR - ch_log(NULL, "LKVAR: get_lvalroot(): name %s", lp->ll_name); + ch_log(NULL, "LKVAR: get_lval_root(): name %s", lp->ll_name); #endif - if (!is_arg && root->v_type == VAR_CLASS) - { - if (root->vval.v_class != NULL) + if (!lr->lr_is_arg && lr->lr_tv->v_type == VAR_CLASS) + { + if (lr->lr_tv->vval.v_class != NULL) { // Special special case. Look for a bare class variable reference. - class_T *cl = root->vval.v_class; + class_T *cl = lr->lr_tv->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_class = lr->lr_tv->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); + ch_log(NULL, "LKVAR: ... class member %s.%s", + lp->ll_class->class_name, lp->ll_name); #endif return; } @@ -1035,10 +1081,52 @@ get_lval_root(lval_T *lp, typval_T *root } #ifdef LOG_LOCKVAR - ch_log(NULL, "LKVAR: get_lvalroot() any type"); + ch_log(NULL, "LKVAR: ... type: %s", vartype_name(lr->lr_tv->v_type)); +#endif + lp->ll_tv = lr->lr_tv; + lp->ll_is_root = TRUE; +} + +/* + * Check if the class has permission to access the member. + * Returns OK or FAIL. + */ + static int +get_lval_check_access( + class_T *cl_exec, // executing class, NULL if :def or script level + class_T *cl, // class which contains the member + ocmember_T *om, // member being accessed + char_u *p, // char after member name + int flags) // GLV flags to check if writing to lval +{ +#ifdef LOG_LOCKVAR + ch_log(NULL, "LKVAR: get_lval_check_access(), cl_exec %p, cl %p, %c", + (void*)cl_exec, (void*)cl, *p); #endif - lp->ll_tv = root; - lp->ll_is_root = TRUE; + if (cl_exec == NULL || cl_exec != cl) + { + switch (om->ocm_access) + { + case VIM_ACCESS_PRIVATE: + semsg(_(e_cannot_access_private_variable_str), + om->ocm_name, cl->class_name); + return FAIL; + case VIM_ACCESS_READ: + // If [idx] or .key following, read only OK. + if (*p == '[' || *p == '.') + break; + if ((flags & GLV_READ_ONLY) == 0) + { + semsg(_(e_variable_is_not_writable_str), + om->ocm_name, cl->class_name); + return FAIL; + } + break; + case VIM_ACCESS_ALL: + break; + } + } + return OK; } /* @@ -1083,10 +1171,19 @@ get_lval( int quiet = flags & GLV_QUIET; int writing = 0; int vim9script = in_vim9script(); + class_T *cl_exec = NULL; // class that is executing, or NULL. #ifdef LOG_LOCKVAR - ch_log(NULL, "LKVAR: get_lval(): name %s, lval_root %p", - name, (void*)lval_root); + if (lval_root == NULL) + ch_log(NULL, + "LKVAR: get_lval(): name %s, lval_root (nil)", name); + else + ch_log(NULL, + "LKVAR: get_lval(): name %s, lr_tv %p lr_is_arg %d", + name, (void*)lval_root->lr_tv, lval_root->lr_is_arg); + char buf[80]; + ch_log(NULL, "LKVAR: ...: GLV flags %s", + flags_tostring(flags, glv_flag_strings, buf, sizeof(buf))); #endif // Clear everything in "lp". @@ -1221,15 +1318,16 @@ get_lval( if ((*p != '[' && *p != '.')) { if (lval_root != NULL) - get_lval_root(lp, lval_root, lval_root_is_arg); + get_lval_root(lp, lval_root); return p; } if (vim9script && lval_root != NULL) { // using local variable - lp->ll_tv = lval_root; + lp->ll_tv = lval_root->lr_tv; v = NULL; + cl_exec = lval_root->lr_cl_exec; } else { @@ -1643,26 +1741,9 @@ get_lval( lp->ll_oi = m_idx; if (om != NULL) { - switch (om->ocm_access) - { - case VIM_ACCESS_PRIVATE: - semsg(_(e_cannot_access_private_variable_str), - om->ocm_name); - return NULL; - case VIM_ACCESS_READ: - // If [idx] or .key following, read only OK. - if (*p == '[' || *p == '.') - break; - if ((flags & GLV_READ_ONLY) == 0) - { - semsg(_(e_variable_is_not_writable_str), - om->ocm_name, cl->class_name); - return NULL; - } - break; - case VIM_ACCESS_ALL: - break; - } + if (get_lval_check_access(cl_exec, cl, om, + p, flags) == FAIL) + return NULL; lp->ll_valtype = om->ocm_type; diff --git a/src/evalvars.c b/src/evalvars.c --- a/src/evalvars.c +++ b/src/evalvars.c @@ -2278,30 +2278,6 @@ do_lock_var( 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 @@ -2319,7 +2295,7 @@ item_lock(typval_T *tv, int deep, int lo int todo; #ifdef LOG_LOCKVAR - ch_log(NULL, "LKVAR: item_lock(): type %s", vartype_tostring(tv->v_type)); + ch_log(NULL, "LKVAR: item_lock(): type %s", vartype_name(tv->v_type)); #endif if (recurse >= DICT_MAXNEST) diff --git a/src/globals.h b/src/globals.h --- a/src/globals.h +++ b/src/globals.h @@ -1953,8 +1953,7 @@ EXTERN int timer_busy INIT(= 0); // w #ifdef FEAT_EVAL 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); +EXTERN lval_root_T *lval_root INIT(= NULL); #endif #ifdef FEAT_BEVAL_TERM diff --git a/src/structs.h b/src/structs.h --- a/src/structs.h +++ b/src/structs.h @@ -4595,6 +4595,16 @@ typedef struct lval_S // ignore the rest. } lval_T; +/** + * This may be used to specify the base type that get_lval() uses when + * following a chain, for example a[idx1][idx2]. + */ +typedef struct lval_root_S { + typval_T *lr_tv; + class_T *lr_cl_exec; // executing class for access checking + int lr_is_arg; +} lval_root_T; + // Structure used to save the current state. Used when executing Normal mode // commands while in any other mode. typedef struct { diff --git a/src/testdir/test_vim9_class.vim b/src/testdir/test_vim9_class.vim --- a/src/testdir/test_vim9_class.vim +++ b/src/testdir/test_vim9_class.vim @@ -626,7 +626,7 @@ def Test_member_any_used_as_object() var outer_obj = Outer.new(inner_obj) F(outer_obj) END - v9.CheckSourceFailure(lines, 'E1333: Cannot access private variable: _value', 1) + v9.CheckSourceFailure(lines, 'E1333: Cannot access private variable "_value" in class "Inner"', 1) # Try modifying a non-existing variable using an "any" object lines =<< trim END @@ -1063,9 +1063,9 @@ def Test_instance_variable_access() assert_equal(1, trip.GetOne()) assert_equal(2, trip.two) assert_equal(3, trip.three) - assert_fails('echo trip._one', 'E1333: Cannot access private variable: _one') - - assert_fails('trip._one = 11', 'E1333: Cannot access private variable: _one') + assert_fails('echo trip._one', 'E1333: Cannot access private variable "_one" in class "Triple"') + + assert_fails('trip._one = 11', 'E1333: Cannot access private variable "_one" in class "Triple"') assert_fails('trip.two = 22', 'E1335: Variable "two" in class "Triple" is not writable') trip.three = 33 assert_equal(33, trip.three) @@ -1315,7 +1315,7 @@ def Test_class_variable_access() var b = B.new() b.Foo() END - v9.CheckSourceFailure(lines, 'E1333: Cannot access private variable: _priv_class_var', 1) + v9.CheckSourceFailure(lines, 'E1333: Cannot access private variable "_priv_class_var" in class "A"', 1) # A private class variable cannot be modified from a child class lines =<< trim END @@ -1333,7 +1333,7 @@ def Test_class_variable_access() var b = B.new() b.Foo() END - v9.CheckSourceFailure(lines, 'E1333: Cannot access private variable: _priv_class_var', 1) + v9.CheckSourceFailure(lines, 'E1333: Cannot access private variable "_priv_class_var" in class "A"', 1) # Access from child class extending a class and from script context lines =<< trim END @@ -1514,8 +1514,8 @@ def Test_class_member() assert_fails('TextPos.counter = 5', 'E1335: Variable "counter" in class "TextPos" is not writable') assert_fails('TextPos.counter += 5', 'E1335: Variable "counter" in class "TextPos" is not writable') - assert_fails('echo TextPos._secret', 'E1333: Cannot access private variable: _secret') - assert_fails('TextPos._secret = 8', 'E1333: Cannot access private variable: _secret') + assert_fails('echo TextPos._secret', 'E1333: Cannot access private variable "_secret" in class "TextPos"') + assert_fails('TextPos._secret = 8', 'E1333: Cannot access private variable "_secret" in class "TextPos"') assert_equal(42, TextPos.anybody) TextPos.anybody = 12 @@ -3522,9 +3522,6 @@ def Test_lockvar_object_variable() # 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 # @@ -3542,8 +3539,7 @@ def Test_lockvar_object_variable() var o = C.new(3) o.Lock() END - # TODO: wrong error - v9.CheckSourceFailure(lines, 'E1335: Variable "val1" in class "C" is not writable') + v9.CheckSourceFailure(lines, 'E1391: Cannot (un)lock variable "this.val1" in class "C"') # read-only lockvar from scriptlevel lines =<< trim END @@ -3602,8 +3598,7 @@ def Test_lockvar_object_variable() 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') + v9.CheckSourceFailure(lines, 'E1391: Cannot (un)lock variable "o_any.val5" in class "C"') # read-only lockvar from class method arg lines =<< trim END @@ -3618,8 +3613,7 @@ def Test_lockvar_object_variable() 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') + v9.CheckSourceFailure(lines, 'E1391: Cannot (un)lock variable "o_any.val6" in class "C"') # # lockvar of public object variable @@ -3987,6 +3981,71 @@ def Test_lockvar_general() assert_equal([ [9], [2], [8] ], o.val) END v9.CheckSourceSuccess(lines) + + # lock a script level variable from an object method + lines =<< trim END + vim9script + + class C + def Lock() + lockvar l + enddef + endclass + + var l = [1] + C.new().Lock() + l[0] = 11 + END + v9.CheckSourceFailure(lines, 'E741: Value is locked: l[0] = 11', 11) + + # lock a list element referenced by a private object variable + # in an object fetched via a script level list + lines =<< trim END + vim9script + + class C + this._v1: list> + def Lock() + lockvar lc[0]._v1[1] + enddef + endclass + + var l = [[1], [2], [3]] + var o = C.new(l) + var lc: list = [ o ] + + o.Lock() + l[0] = [22] + l[1] = [33] + END + v9.CheckSourceFailure(lines, 'E741: Value is locked: l[1] = [33]', 16) + + # similar to the previous test, except the locking code is executing + # in a class that does not own the private variable. + # Note that the locking code is in a class has a private variable of + # the same name. + lines =<< trim END + vim9script + + class C2 + this._v1: list> + def Lock(obj: any) + lockvar lc[0]._v1[1] + enddef + endclass + + class C + this._v1: list> + endclass + + var l = [[1], [2], [3]] + var o = C.new(l) + var lc: list = [ o ] + + var o2 = C2.new() + o2.Lock(o) + END + v9.CheckSourceFailure(lines, 'E1333: Cannot access private variable "_v1" in class "C"') enddef " Test for a private object method @@ -4518,7 +4577,7 @@ def Test_static_inheritence() assert_equal(102, ob.AccessObject()) assert_equal(103, oc.AccessObject()) - assert_fails('echo oc.AccessPrivateStaticThroughClassName()', 'E1333: Cannot access private variable: _svar') + assert_fails('echo oc.AccessPrivateStaticThroughClassName()', 'E1333: Cannot access private variable "_svar" in class "A"') # verify object properly resolves to correct static assert_equal(1, oa.AccessStaticThroughObject()) @@ -4699,7 +4758,7 @@ def Test_private_member_access_outside_c enddef T() END - v9.CheckSourceFailure(lines, 'E1333: Cannot access private variable: _val', 2) + v9.CheckSourceFailure(lines, 'E1333: Cannot access private variable "_val" in class "A"', 2) # access a non-existing private object member variable lines =<< trim END @@ -4754,7 +4813,7 @@ def Test_private_member_access_outside_c enddef T() END - v9.CheckSourceFailure(lines, 'E1333: Cannot access private variable: _val', 1) + v9.CheckSourceFailure(lines, 'E1333: Cannot access private variable "_val" in class "A"', 1) # private static class variable lines =<< trim END @@ -4767,7 +4826,7 @@ def Test_private_member_access_outside_c enddef T() END - v9.CheckSourceFailure(lines, 'E1333: Cannot access private variable: _val', 1) + v9.CheckSourceFailure(lines, 'E1333: Cannot access private variable "_val" in class "A"', 1) enddef " Test for changing the member access of an interface in a implementation class @@ -4828,7 +4887,7 @@ def Test_modify_class_member_from_def_fu A.var3 = {c: 3, d: 4} assert_equal([3, 4], A.var2) assert_equal({c: 3, d: 4}, A.var3) - assert_fails('echo A._priv_var4', 'E1333: Cannot access private variable: _priv_var4') + assert_fails('echo A._priv_var4', 'E1333: Cannot access private variable "_priv_var4" in class "A"') enddef T() END diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -705,6 +705,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 1982, +/**/ 1981, /**/ 1980, diff --git a/src/vim9.h b/src/vim9.h --- a/src/vim9.h +++ b/src/vim9.h @@ -508,8 +508,9 @@ typedef struct { // arguments to ISN_LOCKUNLOCK typedef struct { - char_u *string; // for exec_command - int is_arg; // is lval_root a function arg + char_u *lu_string; // for exec_command + class_T *lu_cl_exec; // executing, null if not class/obj method + int lu_is_arg; // is lval_root a function arg } lockunlock_T; /* diff --git a/src/vim9class.c b/src/vim9class.c --- a/src/vim9class.c +++ b/src/vim9class.c @@ -2172,7 +2172,8 @@ get_member_tv( if (*name == '_') { - semsg(_(e_cannot_access_private_variable_str), m->ocm_name); + semsg(_(e_cannot_access_private_variable_str), m->ocm_name, + cl->class_name); return FAIL; } @@ -2331,7 +2332,8 @@ class_object_index( if (*name == '_') { - semsg(_(e_cannot_access_private_variable_str), m->ocm_name); + semsg(_(e_cannot_access_private_variable_str), m->ocm_name, + cl->class_name); return FAIL; } diff --git a/src/vim9cmds.c b/src/vim9cmds.c --- a/src/vim9cmds.c +++ b/src/vim9cmds.c @@ -212,11 +212,20 @@ 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, + + // The most important point is that something like + // name[idx].member... needs to be resolved at runtime, get_lval(), + // starting from the root "name". + + // These checks are reminiscent of the variable_exists function. + // But most of the matches require special handling. + + // If bare 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. + // function arg. The item on the stack, will be passed + // to ex_lockvar() indirectly and be used as the root for get_lval. + // A bare script variable name needs no special handling. char_u *name = NULL; int len = end - p; @@ -233,7 +242,7 @@ compile_lock_unlock( // Push the local on the stack, could be "this". name = p; #ifdef LOG_LOCKVAR - ch_log(NULL, "LKVAR: compile... lookup_local: name %s", name); + ch_log(NULL, "LKVAR: ... lookup_local: name %s", name); #endif } if (name == NULL) @@ -247,7 +256,7 @@ compile_lock_unlock( name = cl->class_name; len = STRLEN(name); #ifdef LOG_LOCKVAR - ch_log(NULL, "LKVAR: compile... cctx_class_member: name %s", + ch_log(NULL, "LKVAR: ... cctx_class_member: name %s", name); #endif } @@ -255,23 +264,33 @@ compile_lock_unlock( } 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) + if (arg_exists(p, len, NULL, NULL, NULL, cctx) == OK) { name = p; is_arg = TRUE; #ifdef LOG_LOCKVAR - ch_log(NULL, "LKVAR: compile... arg_exists: name %s", name); + ch_log(NULL, "LKVAR: ... arg_exists: name %s", name); +#endif + } + } + if (name == NULL) + { + // No special handling for a bare script variable; but + // if followed by '[' or '.', it's a root for get_lval(). + if (script_var_exists(p, len, cctx, NULL) == OK + && (*end == '.' || *end == '[')) + { + name = p; +#ifdef LOG_LOCKVAR + ch_log(NULL, "LKVAR: ... script_var_exists: name %s", name); #endif } } if (name != NULL) { #ifdef LOG_LOCKVAR - ch_log(NULL, "LKVAR: compile... INS_LOCKUNLOCK %s", name); + ch_log(NULL, "LKVAR: ... INS_LOCKUNLOCK %s", name); #endif if (compile_load(&name, name + len, cctx, FALSE, FALSE) == FAIL) return FAIL; @@ -292,7 +311,7 @@ compile_lock_unlock( else vim_snprintf((char *)buf, len, "%s %d %s", cmd, deep, p); #ifdef LOG_LOCKVAR - ch_log(NULL, "LKVAR: compile... buf %s", buf); + ch_log(NULL, "LKVAR: ... buf %s", buf); #endif if (isn == ISN_LOCKUNLOCK) ret = generate_LOCKUNLOCK(cctx, buf, is_arg); diff --git a/src/vim9execute.c b/src/vim9execute.c --- a/src/vim9execute.c +++ b/src/vim9execute.c @@ -2180,7 +2180,8 @@ execute_storeindex(isn_T *iptr, ectx_T * { if (*member == '_') { - semsg(_(e_cannot_access_private_variable_str), m->ocm_name); + semsg(_(e_cannot_access_private_variable_str), + m->ocm_name, cl->class_name); status = FAIL; } @@ -4178,9 +4179,7 @@ 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; + lval_root_T *lval_root_save = lval_root; int res; #ifdef LOG_LOCKVAR ch_log(NULL, "LKVAR: execute INS_LOCKUNLOCK isn_arg %s", @@ -4190,12 +4189,14 @@ exec_instructions(ectx_T *ectx) // 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); - 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_T root = { STACK_TV_BOT(0), + iptr->isn_arg.lockunlock.lu_cl_exec, + iptr->isn_arg.lockunlock.lu_is_arg }; + lval_root = &root; + res = exec_command(iptr, + iptr->isn_arg.lockunlock.lu_string); + clear_tv(root.lr_tv); lval_root = lval_root_save; - lval_root_is_arg = lval_root_is_arg_save; if (res == FAIL) goto on_error; } diff --git a/src/vim9expr.c b/src/vim9expr.c --- a/src/vim9expr.c +++ b/src/vim9expr.c @@ -407,7 +407,8 @@ compile_class_object_index(cctx_T *cctx, { if (*name == '_' && !inside_class(cctx, cl)) { - semsg(_(e_cannot_access_private_variable_str), m->ocm_name); + semsg(_(e_cannot_access_private_variable_str), m->ocm_name, + cl->class_name); return FAIL; } @@ -442,7 +443,8 @@ compile_class_object_index(cctx_T *cctx, // it is defined. if (*name == '_' && cctx->ctx_ufunc->uf_class != cl) { - semsg(_(e_cannot_access_private_variable_str), m->ocm_name); + semsg(_(e_cannot_access_private_variable_str), m->ocm_name, + cl->class_name); return FAIL; } diff --git a/src/vim9instr.c b/src/vim9instr.c --- a/src/vim9instr.c +++ b/src/vim9instr.c @@ -2170,7 +2170,11 @@ generate_PUT(cctx_T *cctx, int regname, } /* - * Generate an EXEC instruction that takes a string argument. + * Generate a LOCKUNLOCK instruction.The root item, where the indexing starts + * to find the variable, is on the stack. The instr takes + * - the string to parse, "root.b[idx1][idx2].d.val", to find the variable + * - the class, if any, in which the string executes. + * - if the root item is a function argument * A copy is made of "line". */ int @@ -2181,8 +2185,12 @@ generate_LOCKUNLOCK(cctx_T *cctx, char_u 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; + class_T *cl = cctx->ctx_ufunc != NULL ? cctx->ctx_ufunc->uf_class : NULL; + isn->isn_arg.lockunlock.lu_string = vim_strsave(line); + isn->isn_arg.lockunlock.lu_cl_exec = cl; + if (cl != NULL) + ++cl->class_refcount; + isn->isn_arg.lockunlock.lu_is_arg = is_arg; return OK; } @@ -2490,7 +2498,6 @@ delete_instr(isn_T *isn) case ISN_LOADOPT: case ISN_LOADT: case ISN_LOADW: - case ISN_LOCKUNLOCK: case ISN_PUSHEXC: case ISN_PUSHFUNC: case ISN_PUSHS: @@ -2505,6 +2512,11 @@ delete_instr(isn_T *isn) vim_free(isn->isn_arg.string); break; + case ISN_LOCKUNLOCK: + class_unref(isn->isn_arg.lockunlock.lu_cl_exec); + vim_free(isn->isn_arg.lockunlock.lu_string); + break; + case ISN_SUBSTITUTE: { int idx;