1
0
forked from aniani/vim

patch 9.0.1822: Vim9: no check for duplicate members in extended classes

Problem:  Vim9: no check for duplicate members in extended classes
Solution: Check for duplicate members in extended classes.
          Fix memory leak.

closes: #12948

Signed-off-by: Christian Brabandt <cb@256bit.org>
Co-authored-by: Yegappan Lakshmanan <yegappan@yahoo.com>
This commit is contained in:
Yegappan Lakshmanan
2023-08-29 22:32:02 +02:00
committed by Christian Brabandt
parent 4b9777a1df
commit e3b6c78ddc
6 changed files with 216 additions and 57 deletions

View File

@@ -3487,8 +3487,6 @@ EXTERN char e_cannot_use_a_return_type_with_new[]
EXTERN char e_cannot_access_private_method_str[] EXTERN char e_cannot_access_private_method_str[]
INIT(= N_("E1366: Cannot access private method: %s")); INIT(= N_("E1366: Cannot access private method: %s"));
EXTERN char e_interface_str_and_class_str_function_access_not_same[]
INIT(= N_("E1367: Access type of class method %s differs from interface method %s"));
EXTERN char e_static_cannot_be_followed_by_this[] EXTERN char e_static_cannot_be_followed_by_this[]
INIT(= N_("E1368: Static cannot be followed by \"this\" in a member name")); INIT(= N_("E1368: Static cannot be followed by \"this\" in a member name"));
EXTERN char e_duplicate_member_str[] EXTERN char e_duplicate_member_str[]
@@ -3512,4 +3510,4 @@ EXTERN char e_member_str_type_mismatch_expected_str_but_got_str[]
EXTERN char e_method_str_type_mismatch_expected_str_but_got_str[] EXTERN char e_method_str_type_mismatch_expected_str_but_got_str[]
INIT(= N_("E1407: Member \"%s\": type mismatch, expected %s but got %s")); INIT(= N_("E1407: Member \"%s\": type mismatch, expected %s but got %s"));
// E1371 - E1399 unused // E1367, E1371 - E1399 unused

View File

@@ -1790,7 +1790,6 @@ struct ufunc_S
class_T *uf_class; // for object method and constructor; does not class_T *uf_class; // for object method and constructor; does not
// count for class_refcount // count for class_refcount
int uf_private; // TRUE if class or object private method
garray_T uf_args; // arguments, including optional arguments garray_T uf_args; // arguments, including optional arguments
garray_T uf_def_args; // default argument expressions garray_T uf_def_args; // default argument expressions

View File

@@ -2376,7 +2376,7 @@ def Test_extends_method_crashes_vim()
endclass endclass
class Bool extends Property class Bool extends Property
this.value: bool this.value2: bool
endclass endclass
def Observe(obj: Property, who: Observer) def Observe(obj: Property, who: Observer)
@@ -2594,13 +2594,10 @@ def Test_multi_level_member_access()
class A class A
this.val1: number = 0 this.val1: number = 0
this.val2: number = 0
this.val3: number = 0
endclass endclass
class B extends A class B extends A
this.val2: number = 0 this.val2: number = 0
this.val3: number = 0
endclass endclass
class C extends B class C extends B
@@ -2609,14 +2606,11 @@ def Test_multi_level_member_access()
def A_members(a: A) def A_members(a: A)
a.val1 += 1 a.val1 += 1
a.val2 += 1
a.val3 += 1
enddef enddef
def B_members(b: B) def B_members(b: B)
b.val1 += 1 b.val1 += 1
b.val2 += 1 b.val2 += 1
b.val3 += 1
enddef enddef
def C_members(c: C) def C_members(c: C)
@@ -2630,8 +2624,8 @@ def Test_multi_level_member_access()
B_members(cobj) B_members(cobj)
C_members(cobj) C_members(cobj)
assert_equal(3, cobj.val1) assert_equal(3, cobj.val1)
assert_equal(3, cobj.val2) assert_equal(2, cobj.val2)
assert_equal(3, cobj.val3) assert_equal(1, cobj.val3)
END END
v9.CheckScriptSuccess(lines) v9.CheckScriptSuccess(lines)
enddef enddef
@@ -3545,6 +3539,118 @@ def Test_dup_member_variable()
assert_equal(20, c.val) assert_equal(20, c.val)
END END
v9.CheckScriptSuccess(lines) v9.CheckScriptSuccess(lines)
# Duplicate object member variable in a derived class
lines =<< trim END
vim9script
class A
this.val = 10
endclass
class B extends A
endclass
class C extends B
this.val = 20
endclass
END
v9.CheckScriptFailure(lines, 'E1369: Duplicate member: val')
# Duplicate object private member variable in a derived class
lines =<< trim END
vim9script
class A
this._val = 10
endclass
class B extends A
endclass
class C extends B
this._val = 20
endclass
END
v9.CheckScriptFailure(lines, 'E1369: Duplicate member: _val')
# Duplicate object private member variable in a derived class
lines =<< trim END
vim9script
class A
this.val = 10
endclass
class B extends A
endclass
class C extends B
this._val = 20
endclass
END
v9.CheckScriptFailure(lines, 'E1369: Duplicate member: _val')
# Duplicate object member variable in a derived class
lines =<< trim END
vim9script
class A
this._val = 10
endclass
class B extends A
endclass
class C extends B
this.val = 20
endclass
END
v9.CheckScriptFailure(lines, 'E1369: Duplicate member: val')
# Duplicate class member variable in a derived class
lines =<< trim END
vim9script
class A
static val = 10
endclass
class B extends A
endclass
class C extends B
static val = 20
endclass
END
v9.CheckScriptFailure(lines, 'E1369: Duplicate member: val')
# Duplicate private class member variable in a derived class
lines =<< trim END
vim9script
class A
static _val = 10
endclass
class B extends A
endclass
class C extends B
static _val = 20
endclass
END
v9.CheckScriptFailure(lines, 'E1369: Duplicate member: _val')
# Duplicate private class member variable in a derived class
lines =<< trim END
vim9script
class A
static val = 10
endclass
class B extends A
endclass
class C extends B
static _val = 20
endclass
END
v9.CheckScriptFailure(lines, 'E1369: Duplicate member: _val')
# Duplicate class member variable in a derived class
lines =<< trim END
vim9script
class A
static _val = 10
endclass
class B extends A
endclass
class C extends B
static val = 20
endclass
END
v9.CheckScriptFailure(lines, 'E1369: Duplicate member: val')
enddef enddef
" vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker " vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker

View File

@@ -699,6 +699,8 @@ static char *(features[]) =
static int included_patches[] = static int included_patches[] =
{ /* Add new patch number below this line */ { /* Add new patch number below this line */
/**/
1822,
/**/ /**/
1821, 1821,
/**/ /**/

View File

@@ -248,6 +248,70 @@ validate_extends_class(char_u *extends_name, class_T **extends_clp)
return success; return success;
} }
/*
* Check whether a class/object member variable in "classmembers_gap" /
* "objmembers_gap" is a duplicate of a member in any of the extended parent
* class lineage. Returns TRUE if there are no duplicates.
*/
static int
validate_extends_members(
garray_T *classmembers_gap,
garray_T *objmembers_gap,
class_T *extends_cl)
{
for (int loop = 1; loop <= 2; ++loop)
{
// loop == 1: check class members
// loop == 2: check object members
int member_count = loop == 1 ? classmembers_gap->ga_len
: objmembers_gap->ga_len;
if (member_count == 0)
continue;
ocmember_T *members = (ocmember_T *)(loop == 1
? classmembers_gap->ga_data
: objmembers_gap->ga_data);
// Validate each member variable
for (int c_i = 0; c_i < member_count; c_i++)
{
class_T *p_cl = extends_cl;
ocmember_T *c_m = members + c_i;
char_u *pstr = (*c_m->ocm_name == '_')
? c_m->ocm_name + 1 : c_m->ocm_name;
// Check in all the parent classes in the lineage
while (p_cl != NULL)
{
int p_member_count = loop == 1
? p_cl->class_class_member_count
: p_cl->class_obj_member_count;
if (p_member_count == 0)
continue;
ocmember_T *p_members = (loop == 1
? p_cl->class_class_members
: p_cl->class_obj_members);
// Compare against all the members in the parent class
for (int p_i = 0; p_i < p_member_count; p_i++)
{
ocmember_T *p_m = p_members + p_i;
char_u *qstr = (*p_m->ocm_name == '_')
? p_m->ocm_name + 1 : p_m->ocm_name;
if (STRCMP(pstr, qstr) == 0)
{
semsg(_(e_duplicate_member_str), c_m->ocm_name);
return FALSE;
}
}
p_cl = p_cl->class_extends;
}
}
}
return TRUE;
}
/* /*
* Check the members of the interface class "ifcl" match the class members * Check the members of the interface class "ifcl" match the class members
* ("classmembers_gap") and object members ("objmembers_gap") of a class. * ("classmembers_gap") and object members ("objmembers_gap") of a class.
@@ -260,9 +324,7 @@ validate_interface_members(
garray_T *classmembers_gap, garray_T *classmembers_gap,
garray_T *objmembers_gap) garray_T *objmembers_gap)
{ {
int success = TRUE; for (int loop = 1; loop <= 2; ++loop)
for (int loop = 1; loop <= 2 && success; ++loop)
{ {
// loop == 1: check class members // loop == 1: check class members
// loop == 2: check object members // loop == 2: check object members
@@ -291,9 +353,9 @@ validate_interface_members(
// Ensure the type is matching. // Ensure the type is matching.
where.wt_func_name = (char *)m->ocm_name; where.wt_func_name = (char *)m->ocm_name;
where.wt_kind = WT_MEMBER; where.wt_kind = WT_MEMBER;
if (check_type_maybe(if_ms[if_i].ocm_type, m->ocm_type, TRUE, if (check_type(if_ms[if_i].ocm_type, m->ocm_type, TRUE,
where) != OK) where) == FAIL)
success = FALSE; return FALSE;
break; break;
} }
@@ -301,13 +363,12 @@ validate_interface_members(
{ {
semsg(_(e_member_str_of_interface_str_not_implemented), semsg(_(e_member_str_of_interface_str_not_implemented),
if_ms[if_i].ocm_name, intf_class_name); if_ms[if_i].ocm_name, intf_class_name);
success = FALSE; return FALSE;
break;
} }
} }
} }
return success; return TRUE;
} }
/* /*
@@ -323,9 +384,7 @@ validate_interface_methods(
garray_T *classfunctions_gap, garray_T *classfunctions_gap,
garray_T *objmethods_gap) garray_T *objmethods_gap)
{ {
int success = TRUE; for (int loop = 1; loop <= 2; ++loop)
for (int loop = 1; loop <= 2 && success; ++loop)
{ {
// loop == 1: check class functions // loop == 1: check class functions
// loop == 2: check object methods // loop == 2: check object methods
@@ -354,16 +413,9 @@ validate_interface_methods(
// Ensure the type is matching. // Ensure the type is matching.
where.wt_func_name = (char *)if_name; where.wt_func_name = (char *)if_name;
where.wt_kind = WT_METHOD; where.wt_kind = WT_METHOD;
if (check_type_maybe(if_fp[if_i]->uf_func_type, if (check_type(if_fp[if_i]->uf_func_type,
cl_fp[cl_i]->uf_func_type, TRUE, where) != OK) cl_fp[cl_i]->uf_func_type, TRUE, where) == FAIL)
success = FALSE; return FALSE;
// Ensure the public/private access level is matching.
if (if_fp[if_i]->uf_private != cl_fp[cl_i]->uf_private)
{
semsg(_(e_interface_str_and_class_str_function_access_not_same),
cl_name, if_name);
success = FALSE;
}
break; break;
} }
} }
@@ -371,18 +423,17 @@ validate_interface_methods(
{ {
semsg(_(e_function_str_of_interface_str_not_implemented), semsg(_(e_function_str_of_interface_str_not_implemented),
if_name, intf_class_name); if_name, intf_class_name);
success = FALSE; return FALSE;
break;
} }
} }
} }
return success; return TRUE;
} }
/* /*
* Validate all the "implements" classes when creating a new class. The * Validate all the "implements" classes when creating a new class. The
* classes are returned in "intf_classes". The class functions, class methods, * classes are returned in "intf_classes". The class functions, class members,
* object methods and object members in the new class are in * object methods and object members in the new class are in
* "classfunctions_gap", "classmembers_gap", "objmethods_gap", and * "classfunctions_gap", "classmembers_gap", "objmethods_gap", and
* "objmembers_gap" respectively. * "objmembers_gap" respectively.
@@ -430,8 +481,9 @@ validate_implements_classes(
// check the functions/methods of the interface match the // check the functions/methods of the interface match the
// functions/methods of the class // functions/methods of the class
success = validate_interface_methods(impl, ifcl, classfunctions_gap, if (success)
objmethods_gap); success = validate_interface_methods(impl, ifcl,
classfunctions_gap, objmethods_gap);
clear_tv(&tv); clear_tv(&tv);
} }
@@ -449,18 +501,16 @@ check_func_arg_names(
garray_T *objmethods_gap, garray_T *objmethods_gap,
garray_T *classmembers_gap) garray_T *classmembers_gap)
{ {
int success = TRUE;
// loop 1: class functions, loop 2: object methods // loop 1: class functions, loop 2: object methods
for (int loop = 1; loop <= 2 && success; ++loop) for (int loop = 1; loop <= 2; ++loop)
{ {
garray_T *gap = loop == 1 ? classfunctions_gap : objmethods_gap; garray_T *gap = loop == 1 ? classfunctions_gap : objmethods_gap;
for (int fi = 0; fi < gap->ga_len && success; ++fi) for (int fi = 0; fi < gap->ga_len; ++fi)
{ {
ufunc_T *uf = ((ufunc_T **)gap->ga_data)[fi]; ufunc_T *uf = ((ufunc_T **)gap->ga_data)[fi];
for (int i = 0; i < uf->uf_args.ga_len && success; ++i) for (int i = 0; i < uf->uf_args.ga_len; ++i)
{ {
char_u *aname = ((char_u **)uf->uf_args.ga_data)[i]; char_u *aname = ((char_u **)uf->uf_args.ga_data)[i];
garray_T *mgap = classmembers_gap; garray_T *mgap = classmembers_gap;
@@ -472,21 +522,20 @@ check_func_arg_names(
->ocm_name; ->ocm_name;
if (STRCMP(aname, mname) == 0) if (STRCMP(aname, mname) == 0)
{ {
success = FALSE;
if (uf->uf_script_ctx.sc_sid > 0) if (uf->uf_script_ctx.sc_sid > 0)
SOURCING_LNUM = uf->uf_script_ctx.sc_lnum; SOURCING_LNUM = uf->uf_script_ctx.sc_lnum;
semsg(_(e_argument_already_declared_in_class_str), semsg(_(e_argument_already_declared_in_class_str),
aname); aname);
break;
return FALSE;
} }
} }
} }
} }
} }
return success; return TRUE;
} }
/* /*
@@ -1227,18 +1276,17 @@ early_ret:
? &classfunctions : &objmethods; ? &classfunctions : &objmethods;
// Check the name isn't used already. // Check the name isn't used already.
if (is_duplicate_method(fgap, name)) if (is_duplicate_method(fgap, name))
{
success = FALSE;
func_clear_free(uf, FALSE);
break; break;
}
if (ga_grow(fgap, 1) == OK) if (ga_grow(fgap, 1) == OK)
{ {
if (is_new) if (is_new)
uf->uf_flags |= FC_NEW; uf->uf_flags |= FC_NEW;
// If the method name starts with '_', then it a private
// method.
if (*name == '_')
uf->uf_private = TRUE;
((ufunc_T **)fgap->ga_data)[fgap->ga_len] = uf; ((ufunc_T **)fgap->ga_data)[fgap->ga_len] = uf;
++fgap->ga_len; ++fgap->ga_len;
} }
@@ -1295,6 +1343,12 @@ early_ret:
success = validate_extends_class(extends, &extends_cl); success = validate_extends_class(extends, &extends_cl);
VIM_CLEAR(extends); VIM_CLEAR(extends);
// Check the new class members and object members doesn't duplicate the
// members in the extended class lineage.
if (success && extends_cl != NULL)
success = validate_extends_members(&classmembers, &objmembers,
extends_cl);
class_T **intf_classes = NULL; class_T **intf_classes = NULL;
// Check all "implements" entries are valid. // Check all "implements" entries are valid.
@@ -1605,7 +1659,7 @@ class_object_index(
typval_T argvars[MAX_FUNC_ARGS + 1]; typval_T argvars[MAX_FUNC_ARGS + 1];
int argcount = 0; int argcount = 0;
if (fp->uf_private) if (*ufname == '_')
{ {
// Cannot access a private method outside of a class // Cannot access a private method outside of a class
semsg(_(e_cannot_access_private_method_str), name); semsg(_(e_cannot_access_private_method_str), name);

View File

@@ -372,7 +372,7 @@ compile_class_object_index(cctx_T *cctx, char_u **arg, type_T *type)
return FAIL; return FAIL;
} }
if (ufunc->uf_private && !inside_class_hierarchy(cctx, cl)) if (*ufunc->uf_name == '_' && !inside_class_hierarchy(cctx, cl))
{ {
semsg(_(e_cannot_access_private_method_str), name); semsg(_(e_cannot_access_private_method_str), name);
return FAIL; return FAIL;