changeset 33173:9efd99a717c1 v9.0.1867

patch 9.0.1867: Vim9: access to interface statics possible Commit: https://github.com/vim/vim/commit/18143d3111b2122c7a94ca51085a60b3073cb139 Author: Ernie Rael <errael@raelity.com> 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 <cb@256bit.org> Co-authored-by: Ernie Rael <errael@raelity.com>
author Christian Brabandt <cb@256bit.org>
date Mon, 04 Sep 2023 22:45:04 +0200
parents 6564d48fe98a
children aa51f7730f18
files src/errors.h src/eval.c src/proto/vim9class.pro src/proto/vim9instr.pro src/testdir/test_vim9_class.vim src/version.c src/vim9.h src/vim9class.c src/vim9compile.c src/vim9execute.c src/vim9expr.c src/vim9instr.c
diffstat 12 files changed, 332 insertions(+), 25 deletions(-) [+]
line wrap: on
line diff
--- 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
--- 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)
     {
--- 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);
--- 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);
--- 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
--- 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,
--- 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 {
--- 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);
--- 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);
--- 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
--- 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);
--- 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);
 }