# HG changeset patch # User Christian Brabandt # Date 1693860304 -7200 # Node ID 9efd99a717c19d88145870fb6e6f4cfbee31535e # Parent 6564d48fe98abc9321f4acaba1abd26430a24c06 patch 9.0.1867: Vim9: access to interface statics possible Commit: https://github.com/vim/vim/commit/18143d3111b2122c7a94ca51085a60b3073cb139 Author: Ernie Rael Date: Mon Sep 4 22:30:41 2023 +0200 patch 9.0.1867: Vim9: access to interface statics possible Problem: Vim9: access to interface statics possible Solution: Prevent direct access to interface statics closes: #13007 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 @@ -3513,5 +3513,6 @@ EXTERN char e_method_str_type_mismatch_e INIT(= N_("E1407: Member \"%s\": type mismatch, expected %s but got %s")); EXTERN char e_aptypes_is_null_str_nr[] INIT(= "E1408: Internal error: ap_types or ap_types[idx] is NULL: %s: %d"); - +EXTERN char e_interface_static_direct_access_str[] + INIT(= N_("E1409: Cannot directly access interface \"%s\" static member \"%s\"")); // E1371 - E1399 unused diff --git a/src/eval.c b/src/eval.c --- a/src/eval.c +++ b/src/eval.c @@ -1180,6 +1180,14 @@ get_lval( return NULL; lp->ll_tv = &v->di_tv; } + if (vim9script && writing && lp->ll_tv->v_type == VAR_CLASS + && (lp->ll_tv->vval.v_class->class_flags & CLASS_INTERFACE) != 0) + { + if (!quiet) + semsg(_(e_interface_static_direct_access_str), + lp->ll_tv->vval.v_class->class_name, lp->ll_name); + return NULL; + } if (vim9script && (flags & GLV_NO_DECL) == 0) { diff --git a/src/proto/vim9class.pro b/src/proto/vim9class.pro --- a/src/proto/vim9class.pro +++ b/src/proto/vim9class.pro @@ -1,5 +1,5 @@ /* vim9class.c */ -int object_index_from_itf_index(class_T *itf, int is_method, int idx, class_T *cl); +int object_index_from_itf_index(class_T *itf, int is_method, int idx, class_T *cl, int is_static); void ex_class(exarg_T *eap); type_T *class_member_type(class_T *cl, int is_object, char_u *name, char_u *name_end, int *member_idx, ocmember_T **m); void ex_enum(exarg_T *eap); diff --git a/src/proto/vim9instr.pro b/src/proto/vim9instr.pro --- a/src/proto/vim9instr.pro +++ b/src/proto/vim9instr.pro @@ -4,8 +4,8 @@ isn_T *generate_instr_drop(cctx_T *cctx, isn_T *generate_instr_type(cctx_T *cctx, isntype_T isn_type, type_T *type); isn_T *generate_instr_debug(cctx_T *cctx); int generate_CONSTRUCT(cctx_T *cctx, class_T *cl); -int generate_GET_OBJ_MEMBER(cctx_T *cctx, int idx, type_T *type); -int generate_GET_ITF_MEMBER(cctx_T *cctx, class_T *itf, int idx, type_T *type); +int generate_GET_OBJ_MEMBER(cctx_T *cctx, int idx, type_T *type, int is_static); +int generate_GET_ITF_MEMBER(cctx_T *cctx, class_T *itf, int idx, type_T *type, int is_static); int generate_STORE_THIS(cctx_T *cctx, int idx); int may_generate_2STRING(int offset, int tolerant, cctx_T *cctx); int generate_add_instr(cctx_T *cctx, vartype_T vartype, type_T *type1, type_T *type2, exprtype_T expr_type); 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 @@ -509,6 +509,36 @@ def Test_assignment_with_operator() assert_equal(23, f.x) END v9.CheckScriptSuccess(lines) + + # do the same thing, but through an interface + lines =<< trim END + vim9script + + interface I + public this.x: number + endinterface + + class Foo implements I + public this.x: number + + def Add(n: number) + var i: I = this + i.x += n + enddef + endclass + + var f = Foo.new(3) + f.Add(17) + assert_equal(20, f.x) + + def AddToFoo(i: I) + i.x += 3 + enddef + + AddToFoo(f) + assert_equal(23, f.x) + END + v9.CheckScriptSuccess(lines) enddef def Test_list_of_objects() @@ -3762,6 +3792,142 @@ def Test_dup_member_variable() v9.CheckScriptFailure(lines, 'E1369: Duplicate member: val') enddef +def Test_interface_static_member_access() + # In a class cannot read from interface static + var lines =<< trim END + vim9script + interface I + public static num: number + endinterface + class C implements I + public static num = 3 + def F() + var x = I.num + enddef + endclass + C.new().F() + END + v9.CheckScriptFailure(lines, 'E1409: Cannot directly access interface "I" static member "num"') + + # In a class cannot write to interface static + lines =<< trim END + vim9script + interface I + public static num: number + endinterface + class C implements I + public static num = 3 + def F() + I.num = 7 + enddef + endclass + C.new().F() + END + v9.CheckScriptFailure(lines, 'E1409: Cannot directly access interface "I" static member "num"') + + # In a def cannot read from interface static + lines =<< trim END + vim9script + interface I + public static num: number + endinterface + def F() + var x = I.num + enddef + F() + END + v9.CheckScriptFailure(lines, 'E1409: Cannot directly access interface "I" static member "num"') + + # In a def cannot write to interface static + lines =<< trim END + vim9script + interface I + public static num: number + endinterface + def F() + I.num = 7 + enddef + F() + END + v9.CheckScriptFailure(lines, 'E1409: Cannot directly access interface "I" static member "num"') + + # script level cannot read interface static + lines =<< trim END + vim9script + interface I + public static s_var1: number + endinterface + + var x = I.s_var1 + END + v9.CheckScriptFailure(lines, 'E1409: Cannot directly access interface "I" static member "s_var1"') + + # script level cannot write interface static + lines =<< trim END + vim9script + interface I + public static s_var1: number + endinterface + + I.s_var1 = 3 + END + v9.CheckScriptFailure(lines, 'E1409: Cannot directly access interface "I" static member "I.s_var1 = 3"') + +enddef + +def Test_static_member_access_outside_class() + # Verify access of statics implemented from interface + # in a :def (outside of a class) + # Note the order of the static is different + # between the interface and the class, + # since they are allocated in order in each interface/class; + # so the static index is mapped from interfaced to class as needed. + + # Check reading statics + var lines =<< trim END + vim9script + + interface I + public static s_var1: number + public static s_var2: number + endinterface + + class C implements I + public static s_var2 = 2 + public static x_static = 7 + public static s_var1 = 1 + endclass + + def F1(): number + assert_equal(1, C.s_var1) + assert_equal(2, C.s_var2) + assert_equal(7, C.x_static) + return 11 + enddef + + # access the class static through an interface argument + def F2(i: I): number + assert_equal(1, i.s_var1) + assert_equal(2, i.s_var2) + return 22 + enddef + + # access the class static through an object interface + def F3(o: C): number + assert_equal(1, o.s_var1) + assert_equal(2, o.s_var2) + assert_equal(7, o.x_static) + return 33 + enddef + + assert_equal(11, F1()) + var c = C.new() + assert_equal(22, F2(c)) + assert_equal(33, F3(c)) + END + v9.CheckScriptSuccess(lines) +enddef + " Test for accessing a private member outside a class in a def function def Test_private_member_access_outside_class() # private object member variable @@ -3794,6 +3960,63 @@ def Test_private_member_access_outside_c T() END v9.CheckScriptFailure(lines, 'E1089: Unknown variable: _a = 1') + + # private static member variable + lines =<< trim END + vim9script + class A + static _val = 10 + endclass + def T() + var a = A.new() + var x = a._val + enddef + T() + END + v9.CheckScriptFailure(lines, 'E1333: Cannot access private member: _val') + + # private static member variable + lines =<< trim END + vim9script + class A + static _val = 10 + endclass + def T() + var a = A.new() + a._val = 3 + enddef + T() + END + # TODO: wrong error, should be about private member + v9.CheckScriptFailure(lines, 'E1089: Unknown variable') + + # private static class variable + lines =<< trim END + vim9script + class A + static _val = 10 + endclass + def T() + var x = A._val + enddef + T() + END + v9.CheckScriptFailure(lines, 'E1333: Cannot access private member: _val') + + # private static class variable + lines =<< trim END + vim9script + class A + static _val = 10 + endclass + def T() + A._val = 3 + enddef + T() + END + v9.CheckScriptFailure(lines, 'E1333: Cannot access private member: _val') + + enddef " Test for changing the member access of an interface in a implementation class diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -700,6 +700,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 1867, +/**/ 1866, /**/ 1865, diff --git a/src/vim9.h b/src/vim9.h --- a/src/vim9.h +++ b/src/vim9.h @@ -498,6 +498,7 @@ typedef struct { typedef struct { class_T *cm_class; int cm_idx; + int cm_static; } classmember_T; // arguments to ISN_STOREINDEX typedef struct { diff --git a/src/vim9class.c b/src/vim9class.c --- a/src/vim9class.c +++ b/src/vim9class.c @@ -220,9 +220,11 @@ add_members_to_class( * "cl" implementing that interface. */ int -object_index_from_itf_index(class_T *itf, int is_method, int idx, class_T *cl) +object_index_from_itf_index(class_T *itf, int is_method, int idx, class_T *cl, + int is_static) { - if (idx > (is_method ? itf->class_obj_method_count + if (idx >= (is_method ? itf->class_obj_method_count + : is_static ? itf->class_class_member_count : itf->class_obj_member_count)) { siemsg("index %d out of range for interface %s", idx, itf->class_name); @@ -245,8 +247,28 @@ object_index_from_itf_index(class_T *itf cl->class_name, itf->class_name); return 0; } - int *table = (int *)(i2c + 1); - return table[idx]; + if (is_static) + { + // TODO: Need a table for fast lookup? + char_u *name = itf->class_class_members[idx].ocm_name; + for (int i = 0; i < i2c->i2c_class->class_class_member_count; ++i) + { + ocmember_T *m = &i2c->i2c_class->class_class_members[i]; + if (STRCMP(name, m->ocm_name) == 0) + { + return i; + } + } + siemsg("class %s, interface %s, static %s not found", + cl->class_name, itf->class_name, name); + return 0; + } + else + { + // A table follows the i2c for the class + int *table = (int *)(i2c + 1); + return table[idx]; + } } /* @@ -1808,6 +1830,12 @@ class_object_index( semsg(_(e_cannot_access_private_member_str), m->ocm_name); return FAIL; } + if ((cl->class_flags & CLASS_INTERFACE) != 0) + { + semsg(_(e_interface_static_direct_access_str), + cl->class_name, m->ocm_name); + return FAIL; + } typval_T *tv = &cl->class_members_tv[i]; copy_tv(tv, rettv); diff --git a/src/vim9compile.c b/src/vim9compile.c --- a/src/vim9compile.c +++ b/src/vim9compile.c @@ -1874,7 +1874,13 @@ compile_lhs( &lhs->lhs_member_idx, &m); if (lhs->lhs_member_idx < 0) return FAIL; - + if ((cl->class_flags & CLASS_INTERFACE) != 0 + && lhs->lhs_type->tt_type == VAR_CLASS) + { + semsg(_(e_interface_static_direct_access_str), + cl->class_name, m->ocm_name); + return FAIL; + } // If it is private member variable, then accessing it outside the // class is not allowed. if ((m->ocm_access != VIM_ACCESS_ALL) && !inside_class(cctx, cl)) @@ -2112,8 +2118,9 @@ compile_load_lhs_with_index(lhs_T *lhs, return FAIL; } if (cl->class_flags & CLASS_INTERFACE) - return generate_GET_ITF_MEMBER(cctx, cl, lhs->lhs_member_idx, type); - return generate_GET_OBJ_MEMBER(cctx, lhs->lhs_member_idx, type); + return generate_GET_ITF_MEMBER(cctx, cl, lhs->lhs_member_idx, type, + FALSE); + return generate_GET_OBJ_MEMBER(cctx, lhs->lhs_member_idx, type, FALSE); } compile_load_lhs(lhs, var_start, NULL, cctx); diff --git a/src/vim9execute.c b/src/vim9execute.c --- a/src/vim9execute.c +++ b/src/vim9execute.c @@ -2316,8 +2316,8 @@ execute_storeindex(isn_T *iptr, ectx_T * class_T *itf = iptr->isn_arg.storeindex.si_class; if (itf != NULL) // convert interface member index to class member index - lidx = object_index_from_itf_index(itf, FALSE, - lidx, obj->obj_class); + lidx = object_index_from_itf_index(itf, FALSE, lidx, + obj->obj_class, FALSE); } else { @@ -4261,7 +4261,8 @@ exec_instructions(ectx_T *ectx) // convert the interface index to the object index int idx = object_index_from_itf_index(mfunc->cmf_itf, - TRUE, mfunc->cmf_idx, cl); + TRUE, mfunc->cmf_idx, cl, + FALSE); if (call_ufunc(cl->class_obj_methods[idx], NULL, mfunc->cmf_argcount, ectx, NULL, NULL) == FAIL) @@ -4410,7 +4411,8 @@ exec_instructions(ectx_T *ectx) // convert the interface index to the object index int idx = object_index_from_itf_index(extra->fre_class, - TRUE, extra->fre_method_idx, cl); + TRUE, extra->fre_method_idx, cl, + FALSE); ufunc = cl->class_obj_methods[idx]; } else if (extra == NULL || extra->fre_func_name == NULL) @@ -5389,20 +5391,25 @@ exec_instructions(ectx_T *ectx) goto on_error; } + int is_static = iptr->isn_arg.classmember.cm_static; int idx; if (iptr->isn_type == ISN_GET_OBJ_MEMBER) - idx = iptr->isn_arg.number; + idx = iptr->isn_arg.classmember.cm_idx; else { idx = iptr->isn_arg.classmember.cm_idx; // convert the interface index to the object index idx = object_index_from_itf_index( - iptr->isn_arg.classmember.cm_class, - FALSE, idx, obj->obj_class); + iptr->isn_arg.classmember.cm_class, + FALSE, idx, obj->obj_class, is_static); } - // the members are located right after the object struct - typval_T *mtv = ((typval_T *)(obj + 1)) + idx; + // The members are located right after the object struct. + typval_T *mtv; + if (is_static) + mtv = &obj->obj_class->class_members_tv[idx]; + else + mtv = ((typval_T *)(obj + 1)) + idx; copy_tv(mtv, tv); // Unreference the object after getting the member, it may diff --git a/src/vim9expr.c b/src/vim9expr.c --- a/src/vim9expr.c +++ b/src/vim9expr.c @@ -407,8 +407,27 @@ compile_class_object_index(cctx_T *cctx, *arg = name_end; if (cl->class_flags & (CLASS_INTERFACE | CLASS_EXTENDED)) - return generate_GET_ITF_MEMBER(cctx, cl, i, m->ocm_type); - return generate_GET_OBJ_MEMBER(cctx, i, m->ocm_type); + return generate_GET_ITF_MEMBER(cctx, cl, i, m->ocm_type, + FALSE); + return generate_GET_OBJ_MEMBER(cctx, i, m->ocm_type, FALSE); + } + } + + for (int i = 0; i < cl->class_class_member_count; ++i) + { + ocmember_T *m = &cl->class_class_members[i]; + if (STRNCMP(name, m->ocm_name, len) == 0 && m->ocm_name[len] == NUL) + { + if (*name == '_' && !inside_class(cctx, cl)) + { + semsg(_(e_cannot_access_private_member_str), m->ocm_name); + return FAIL; + } + *arg = name_end; + if (cl->class_flags & (CLASS_INTERFACE | CLASS_EXTENDED)) + return generate_GET_ITF_MEMBER(cctx, cl, i, m->ocm_type, + TRUE); + return generate_GET_OBJ_MEMBER(cctx, i, m->ocm_type, TRUE); } } @@ -439,6 +458,13 @@ compile_class_object_index(cctx_T *cctx, ocmember_T *m = &cl->class_class_members[idx]; if (STRNCMP(name, m->ocm_name, len) == 0 && m->ocm_name[len] == NUL) { + // Note: type->tt_type = VAR_CLASS + if ((cl->class_flags & CLASS_INTERFACE) != 0) + { + semsg(_(e_interface_static_direct_access_str), + cl->class_name, m->ocm_name); + return FAIL; + } if (*name == '_' && !inside_class(cctx, cl)) { semsg(_(e_cannot_access_private_member_str), m->ocm_name); diff --git a/src/vim9instr.c b/src/vim9instr.c --- a/src/vim9instr.c +++ b/src/vim9instr.c @@ -136,7 +136,7 @@ generate_CONSTRUCT(cctx_T *cctx, class_T * index. */ int -generate_GET_OBJ_MEMBER(cctx_T *cctx, int idx, type_T *type) +generate_GET_OBJ_MEMBER(cctx_T *cctx, int idx, type_T *type, int is_static) { RETURN_OK_IF_SKIP(cctx); @@ -145,7 +145,9 @@ generate_GET_OBJ_MEMBER(cctx_T *cctx, in if (isn == NULL) return FAIL; - isn->isn_arg.number = idx; + isn->isn_arg.classmember.cm_class = NULL; + isn->isn_arg.classmember.cm_idx = idx; + isn->isn_arg.classmember.cm_static = is_static; return push_type_stack2(cctx, type, &t_any); } @@ -154,7 +156,8 @@ generate_GET_OBJ_MEMBER(cctx_T *cctx, in * by index. */ int -generate_GET_ITF_MEMBER(cctx_T *cctx, class_T *itf, int idx, type_T *type) +generate_GET_ITF_MEMBER(cctx_T *cctx, class_T *itf, int idx, type_T *type, + int is_static) { RETURN_OK_IF_SKIP(cctx); @@ -166,6 +169,7 @@ generate_GET_ITF_MEMBER(cctx_T *cctx, cl isn->isn_arg.classmember.cm_class = itf; ++itf->class_refcount; isn->isn_arg.classmember.cm_idx = idx; + isn->isn_arg.classmember.cm_static = is_static; return push_type_stack2(cctx, type, &t_any); }