0
0
mirror of https://github.com/vim/vim.git synced 2025-07-24 10:45:12 -04:00

patch 9.0.1737: Calling a base class method through an extended class fails

Problem: Calling a base class method through an extended class fails
Solution: Create lookup table for member index in the interface to
          to the member class implementing the interface

Create additional tests for Vim9 classes.  Fix unconvered memory leaks
and crashes found by the new tests.

closes: #12848
closes: #12089

Signed-off-by: Christian Brabandt <cb@256bit.org>
Co-authored-by: Yegappan Lakshmanan <yegappan@yahoo.com>author
This commit is contained in:
Yegappan Lakshmanan 2023-08-19 11:26:42 +02:00 committed by Christian Brabandt
parent e27578aee6
commit b102728c20
No known key found for this signature in database
GPG Key ID: F3F92DA383FDDE09
3 changed files with 511 additions and 69 deletions

View File

@ -135,6 +135,16 @@ def Test_class_basic()
END
v9.CheckScriptFailure(lines, 'E1069:')
# Test for unsupported comment specifier
lines =<< trim END
vim9script
class Something
# comment
#{
endclass
END
v9.CheckScriptFailure(lines, 'E1170:')
lines =<< trim END
vim9script
@ -162,6 +172,44 @@ def Test_class_basic()
assert_equal('object<TextPosition>', typename(pos))
END
v9.CheckScriptSuccess(lines)
# When referencing object methods, space cannot be used after a "."
lines =<< trim END
vim9script
class A
def Foo(): number
return 10
enddef
endclass
var a = A.new()
var v = a. Foo()
END
v9.CheckScriptFailure(lines, 'E1202:')
# Using an object without specifying a method or a member variable
lines =<< trim END
vim9script
class A
def Foo(): number
return 10
enddef
endclass
var a = A.new()
var v = a.
END
v9.CheckScriptFailure(lines, 'E15:')
# Error when parsing the arguments of an object method.
lines =<< trim END
vim9script
class A
def Foo()
enddef
endclass
var a = A.new()
var v = a.Foo(,)
END
v9.CheckScriptFailure(lines, 'E15:')
enddef
def Test_class_defined_twice()
@ -628,6 +676,15 @@ def Test_class_object_member_inits()
endclass
END
v9.CheckScriptFailure(lines, 'E1330:')
# Test for initializing an object member with an unknown variable/type
lines =<< trim END
vim9script
class A
this.value = init_val
endclass
END
v9.CheckScriptFailureList(lines, ['E121:', 'E1329:'])
enddef
def Test_class_object_member_access()
@ -658,6 +715,15 @@ def Test_class_object_member_access()
END
v9.CheckScriptSuccess(lines)
# Test for a public member variable name beginning with an underscore
lines =<< trim END
vim9script
class A
public this._val = 10
endclass
END
v9.CheckScriptFailure(lines, 'E1332:')
lines =<< trim END
vim9script
@ -735,6 +801,33 @@ def Test_class_object_member_access()
.x
END
v9.CheckScriptSuccess(lines)
# Test for "public" cannot be abbreviated
lines =<< trim END
vim9script
class Something
pub this.val = 1
endclass
END
v9.CheckScriptFailure(lines, 'E1065:')
# Test for "public" keyword must be followed by "this" or "static".
lines =<< trim END
vim9script
class Something
public val = 1
endclass
END
v9.CheckScriptFailure(lines, 'E1331:')
# Test for "static" cannot be abbreviated
lines =<< trim END
vim9script
class Something
stat this.val = 1
endclass
END
v9.CheckScriptFailure(lines, 'E1065:')
enddef
def Test_class_object_compare()
@ -1002,6 +1095,42 @@ def Test_class_member()
s.Method(7)
END
v9.CheckScriptFailure(lines, 'E1341: Variable already declared in the class: count')
# Test for using an invalid type for a member variable
lines =<< trim END
vim9script
class A
this.val: xxx
endclass
END
v9.CheckScriptFailure(lines, 'E1010:')
# Test for no space before or after the '=' when initializing a member
# variable
lines =<< trim END
vim9script
class A
this.val: number= 10
endclass
END
v9.CheckScriptFailure(lines, 'E1004:')
lines =<< trim END
vim9script
class A
this.val: number =10
endclass
END
v9.CheckScriptFailure(lines, 'E1004:')
# Access a non-existing member
lines =<< trim END
vim9script
class A
endclass
var a = A.new()
var v = a.bar
END
v9.CheckScriptFailure(lines, 'E1326:')
enddef
func Test_class_garbagecollect()
@ -1072,6 +1201,18 @@ def Test_class_function()
assert_equal(2, Value.GetCount())
END
v9.CheckScriptSuccess(lines)
# Test for cleaning up after a class definition failure when using class
# functions.
lines =<< trim END
vim9script
class A
static def Foo()
enddef
aaa
endclass
END
v9.CheckScriptFailure(lines, 'E1318:')
enddef
def Test_class_defcompile()
@ -1100,6 +1241,40 @@ def Test_class_defcompile()
defcompile C.Fc
END
v9.CheckScriptFailure(lines, 'E1012: Type mismatch; expected number but got string')
# Trying to compile a function using a non-existing class variable
lines =<< trim END
vim9script
defcompile x.Foo()
END
v9.CheckScriptFailure(lines, 'E475:')
# Trying to compile a function using a variable which is not a class
lines =<< trim END
vim9script
var x: number
defcompile x.Foo()
END
v9.CheckScriptFailure(lines, 'E475:')
# Trying to compile a function without specifying the name
lines =<< trim END
vim9script
class A
endclass
defcompile A.
END
v9.CheckScriptFailure(lines, 'E475:')
# Trying to compile a non-existing class object member function
lines =<< trim END
vim9script
class A
endclass
var a = A.new()
defcompile a.Foo()
END
v9.CheckScriptFailureList(lines, ['E1334:', 'E475:'])
enddef
def Test_class_object_to_string()
@ -1345,6 +1520,62 @@ def Test_class_implements_interface()
Test()
END
v9.CheckScriptSuccess(lines)
# Interface name after "extends" doesn't end in a space or NUL character
lines =<< trim END
vim9script
interface A
endinterface
class B extends A"
endclass
END
v9.CheckScriptFailure(lines, 'E1315:')
# Trailing characters after a class name
lines =<< trim END
vim9script
class A bbb
endclass
END
v9.CheckScriptFailure(lines, 'E488:')
# using "implements" with a non-existing class
lines =<< trim END
vim9script
class A implements B
endclass
END
v9.CheckScriptFailure(lines, 'E1346:')
# using "implements" with a regular class
lines =<< trim END
vim9script
class A
endclass
class B implements A
endclass
END
v9.CheckScriptFailure(lines, 'E1347:')
# using "implements" with a variable
lines =<< trim END
vim9script
var T: number = 10
class A implements T
endclass
END
v9.CheckScriptFailure(lines, 'E1347:')
# all the class methods in an "interface" should be implemented
lines =<< trim END
vim9script
interface A
static def Foo()
endinterface
class B implements A
endclass
END
v9.CheckScriptFailure(lines, 'E1349:')
enddef
def Test_call_interface_method()
@ -1717,6 +1948,16 @@ def Test_class_extends()
assert_equal("object of Success {success: true, value: 'asdf'}", string(v))
END
v9.CheckScriptSuccess(lines)
# class name after "extends" doesn't end in a space or NUL character
lines =<< trim END
vim9script
class A
endclass
class B extends A"
endclass
END
v9.CheckScriptFailure(lines, 'E1315:')
enddef
def Test_using_base_class()
@ -1841,6 +2082,16 @@ def Test_abstract_class()
endclass
END
v9.CheckScriptFailure(lines, 'E1316:')
# Abstract class cannot have a "new" function
lines =<< trim END
vim9script
abstract class Base
def new()
enddef
endclass
END
v9.CheckScriptFailure(lines, 'E1359:')
enddef
def Test_closure_in_class()
@ -2063,4 +2314,127 @@ def Test_call_method_in_parent_class()
v9.CheckScriptSuccess(lines)
enddef
" Test for calling methods from three levels of classes
def Test_multi_level_method_call()
var lines =<< trim END
vim9script
var A_func1: number = 0
var A_func2: number = 0
var A_func3: number = 0
var B_func2: number = 0
var B_func3: number = 0
var C_func3: number = 0
class A
def Func1()
A_func1 += 1
enddef
def Func2()
A_func2 += 1
enddef
def Func3()
A_func3 += 1
enddef
endclass
class B extends A
def Func2()
B_func2 += 1
enddef
def Func3()
B_func3 += 1
enddef
endclass
class C extends B
def Func3()
C_func3 += 1
enddef
endclass
def A_CallFuncs(a: A)
a.Func1()
a.Func2()
a.Func3()
enddef
def B_CallFuncs(b: B)
b.Func1()
b.Func2()
b.Func3()
enddef
def C_CallFuncs(c: C)
c.Func1()
c.Func2()
c.Func3()
enddef
var cobj = C.new()
A_CallFuncs(cobj)
B_CallFuncs(cobj)
C_CallFuncs(cobj)
assert_equal(3, A_func1)
assert_equal(0, A_func2)
assert_equal(0, A_func3)
assert_equal(3, B_func2)
assert_equal(0, B_func3)
assert_equal(3, C_func3)
END
v9.CheckScriptSuccess(lines)
enddef
" Test for using members from three levels of classes
def Test_multi_level_member_access()
var lines =<< trim END
vim9script
class A
this.val1: number = 0
this.val2: number = 0
this.val3: number = 0
endclass
class B extends A
this.val2: number = 0
this.val3: number = 0
endclass
class C extends B
this.val3: number = 0
endclass
def A_members(a: A)
a.val1 += 1
a.val2 += 1
a.val3 += 1
enddef
def B_members(b: B)
b.val1 += 1
b.val2 += 1
b.val3 += 1
enddef
def C_members(c: C)
c.val1 += 1
c.val2 += 1
c.val3 += 1
enddef
var cobj = C.new()
A_members(cobj)
B_members(cobj)
C_members(cobj)
assert_equal(3, cobj.val1)
assert_equal(3, cobj.val2)
assert_equal(3, cobj.val3)
END
v9.CheckScriptSuccess(lines)
enddef
" vim: ts=8 sw=2 sts=2 expandtab tw=80 fdm=marker

View File

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

View File

@ -230,6 +230,117 @@ object_index_from_itf_index(class_T *itf, int is_method, int idx, class_T *cl)
return table[idx];
}
/*
* Update the interface class lookup table for the member index on the
* interface to the member index in the class implementing the interface.
* And a lookup table for the object method index on the interface
* to the object method index in the class implementing the interface.
* This is also used for updating the lookup table for the extended class
* hierarchy.
*/
static int
update_member_method_lookup_table(
class_T *ifcl,
class_T *cl,
garray_T *objmethods,
int pobj_method_offset,
int is_interface)
{
if (ifcl == NULL)
return OK;
// Table for members.
itf2class_T *if2cl = alloc_clear(sizeof(itf2class_T)
+ ifcl->class_obj_member_count * sizeof(int));
if (if2cl == NULL)
return FAIL;
if2cl->i2c_next = ifcl->class_itf2class;
ifcl->class_itf2class = if2cl;
if2cl->i2c_class = cl;
if2cl->i2c_is_method = FALSE;
for (int if_i = 0; if_i < ifcl->class_obj_member_count; ++if_i)
for (int cl_i = 0; cl_i < cl->class_obj_member_count; ++cl_i)
{
if (STRCMP(ifcl->class_obj_members[if_i].ocm_name,
cl->class_obj_members[cl_i].ocm_name) == 0)
{
int *table = (int *)(if2cl + 1);
table[if_i] = cl_i;
break;
}
}
// Table for methods.
if2cl = alloc_clear(sizeof(itf2class_T)
+ ifcl->class_obj_method_count * sizeof(int));
if (if2cl == NULL)
return FAIL;
if2cl->i2c_next = ifcl->class_itf2class;
ifcl->class_itf2class = if2cl;
if2cl->i2c_class = cl;
if2cl->i2c_is_method = TRUE;
for (int if_i = 0; if_i < ifcl->class_obj_method_count; ++if_i)
{
int done = FALSE;
for (int cl_i = 0; cl_i < objmethods->ga_len; ++cl_i)
{
if (STRCMP(ifcl->class_obj_methods[if_i]->uf_name,
((ufunc_T **)objmethods->ga_data)[cl_i]->uf_name)
== 0)
{
int *table = (int *)(if2cl + 1);
table[if_i] = cl_i;
done = TRUE;
break;
}
}
// extended class object method is not overridden by the child class.
// Keep the method declared in one of the parent classes in the
// lineage.
if (!done && !is_interface)
{
// If "ifcl" is not the immediate parent of "cl", then search in
// the intermediate parent classes.
if (cl->class_extends != ifcl)
{
class_T *parent = cl->class_extends;
int method_offset = objmethods->ga_len;
while (!done && parent != NULL && parent != ifcl)
{
for (int cl_i = 0;
cl_i < parent->class_obj_method_count_child; ++cl_i)
{
if (STRCMP(ifcl->class_obj_methods[if_i]->uf_name,
parent->class_obj_methods[cl_i]->uf_name)
== 0)
{
int *table = (int *)(if2cl + 1);
table[if_i] = method_offset + cl_i;
done = TRUE;
break;
}
}
method_offset += parent->class_obj_method_count_child;
parent = parent->class_extends;
}
}
if (!done)
{
int *table = (int *)(if2cl + 1);
table[if_i] = pobj_method_offset + if_i;
}
}
}
return OK;
}
/*
* Handle ":class" and ":abstract class" up to ":endclass".
* Handle ":interface" up to ":endinterface".
@ -534,6 +645,7 @@ early_ret:
{
emsg(_(e_cannot_define_new_function_in_abstract_class));
success = FALSE;
func_clear_free(uf, FALSE);
break;
}
garray_T *fgap = has_static || is_new
@ -654,6 +766,8 @@ early_ret:
{
semsg(_(e_not_valid_interface_str), impl);
success = FALSE;
clear_tv(&tv);
break;
}
class_T *ifcl = tv.vval.v_class;
@ -839,83 +953,35 @@ early_ret:
if (cl->class_interface_count > 0 || extends_cl != NULL)
{
// For each interface add a lookuptable for the member index on the
// interface to the member index in this class.
// And a lookuptable for the object method index on the interface
// For each interface add a lookup table for the member index on
// the interface to the member index in this class.
// And a lookup table for the object method index on the interface
// to the object method index in this class.
// Also do this for the extended class, if any.
for (int i = 0; i <= cl->class_interface_count; ++i)
for (int i = 0; i < cl->class_interface_count; ++i)
{
class_T *ifcl = i < cl->class_interface_count
? cl->class_interfaces_cl[i]
: extends_cl;
if (ifcl == NULL)
continue;
class_T *ifcl = cl->class_interfaces_cl[i];
// Table for members.
itf2class_T *if2cl = alloc_clear(sizeof(itf2class_T)
+ ifcl->class_obj_member_count * sizeof(int));
if (if2cl == NULL)
if (update_member_method_lookup_table(ifcl, cl, &objmethods,
0, TRUE) == FAIL)
goto cleanup;
if2cl->i2c_next = ifcl->class_itf2class;
ifcl->class_itf2class = if2cl;
if2cl->i2c_class = cl;
if2cl->i2c_is_method = FALSE;
}
for (int if_i = 0; if_i < ifcl->class_obj_member_count; ++if_i)
for (int cl_i = 0; cl_i < cl->class_obj_member_count;
++cl_i)
{
if (STRCMP(ifcl->class_obj_members[if_i].ocm_name,
cl->class_obj_members[cl_i].ocm_name) == 0)
{
int *table = (int *)(if2cl + 1);
table[if_i] = cl_i;
break;
}
}
// Update the lookup table for the extended class, if nay
if (extends_cl != NULL)
{
class_T *pclass = extends_cl;
int pobj_method_offset = objmethods.ga_len;
// Table for methods.
if2cl = alloc_clear(sizeof(itf2class_T)
+ ifcl->class_obj_method_count * sizeof(int));
if (if2cl == NULL)
goto cleanup;
if2cl->i2c_next = ifcl->class_itf2class;
ifcl->class_itf2class = if2cl;
if2cl->i2c_class = cl;
if2cl->i2c_is_method = TRUE;
for (int if_i = 0; if_i < ifcl->class_obj_method_count; ++if_i)
// Update the entire lineage of extended classes.
while (pclass != NULL)
{
int done = FALSE;
for (int cl_i = 0; cl_i < objmethods.ga_len; ++cl_i)
{
if (STRCMP(ifcl->class_obj_methods[if_i]->uf_name,
((ufunc_T **)objmethods.ga_data)[cl_i]->uf_name)
== 0)
{
int *table = (int *)(if2cl + 1);
table[if_i] = cl_i;
done = TRUE;
break;
}
}
if (update_member_method_lookup_table(pclass, cl,
&objmethods, pobj_method_offset, FALSE) ==
FAIL)
goto cleanup;
if (!done && extends_cl != NULL)
{
for (int cl_i = 0;
cl_i < extends_cl->class_obj_method_count; ++cl_i)
{
if (STRCMP(ifcl->class_obj_methods[if_i]->uf_name,
extends_cl->class_obj_methods[cl_i]->uf_name)
== 0)
{
int *table = (int *)(if2cl + 1);
table[if_i] = objmethods.ga_len + cl_i;
break;
}
}
}
pobj_method_offset += pclass->class_obj_method_count_child;
pclass = pclass->class_extends;
}
}
}