diff src/vim9class.c @ 32854:5fd9fe58c791 v9.0.1737

patch 9.0.1737: Calling a base class method through an extended class fails Commit: https://github.com/vim/vim/commit/b102728c204430b16a5d9e6720c882e12a687570 Author: Yegappan Lakshmanan <yegappan@yahoo.com> Date: Sat Aug 19 11:26:42 2023 +0200 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
author Christian Brabandt <cb@256bit.org>
date Sat, 19 Aug 2023 11:45:03 +0200
parents 57282f1d9e0f
children a39314fa9495
line wrap: on
line diff
--- a/src/vim9class.c
+++ b/src/vim9class.c
@@ -231,6 +231,117 @@ object_index_from_itf_index(class_T *itf
 }
 
 /*
+ * 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;
-			}
-		    }
+	    }
 
-		// 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;
+	    // 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;
 
-		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;
 		}
 	    }
 	}