diff src/vim9class.c @ 33160:4ecf54d709b3 v9.0.1862

patch 9.0.1862: Vim9 Garbage Collection issues Commit: https://github.com/vim/vim/commit/e651e110c17656a263dd017b14c85b332163a58d Author: Yegappan Lakshmanan <yegappan@yahoo.com> Date: Mon Sep 4 07:51:01 2023 +0200 patch 9.0.1862: Vim9 Garbage Collection issues Problem: Vim9 Garbage Collection issues Solution: Class members are garbage collected early leading to use-after-free problems. Handle the garbage collection of classes properly. closes: #13019 Signed-off-by: Christian Brabandt <cb@256bit.org> Co-authored-by: Yegappan Lakshmanan <yegappan@yahoo.com>
author Christian Brabandt <cb@256bit.org>
date Mon, 04 Sep 2023 08:00:06 +0200
parents bede81965821
children 9efd99a717c1
line wrap: on
line diff
--- a/src/vim9class.c
+++ b/src/vim9class.c
@@ -21,6 +21,43 @@
 # include "vim9.h"
 #endif
 
+static class_T *first_class = NULL;
+static class_T *next_nonref_class = NULL;
+
+/*
+ * Call this function when a class has been created.  It will be added to the
+ * list headed by "first_class".
+ */
+    static void
+class_created(class_T *cl)
+{
+    if (first_class != NULL)
+    {
+	cl->class_next_used = first_class;
+	first_class->class_prev_used = cl;
+    }
+    first_class = cl;
+}
+
+/*
+ * Call this function when a class has been cleared and is about to be freed.
+ * It is removed from the list headed by "first_class".
+ */
+    static void
+class_cleared(class_T *cl)
+{
+    if (cl->class_next_used != NULL)
+	cl->class_next_used->class_prev_used = cl->class_prev_used;
+    if (cl->class_prev_used != NULL)
+	cl->class_prev_used->class_next_used = cl->class_next_used;
+    else if (first_class == cl)
+	first_class = cl->class_next_used;
+
+    // update the next class to check if needed
+    if (cl == next_nonref_class)
+	next_nonref_class = cl->class_next_used;
+}
+
 /*
  * Parse a member declaration, both object and class member.
  * Returns OK or FAIL.  When OK then "varname_end" is set to just after the
@@ -1470,6 +1507,8 @@ early_ret:
 	cl->class_object_type.tt_class = cl;
 	cl->class_type_list = type_list;
 
+	class_created(cl);
+
 	// TODO:
 	// - Fill hashtab with object members and methods ?
 
@@ -1945,73 +1984,114 @@ copy_class(typval_T *from, typval_T *to)
 }
 
 /*
+ * Free the class "cl" and its contents.
+ */
+    static void
+class_free(class_T *cl)
+{
+    // Freeing what the class contains may recursively come back here.
+    // Clear "class_name" first, if it is NULL the class does not need to
+    // be freed.
+    VIM_CLEAR(cl->class_name);
+
+    class_unref(cl->class_extends);
+
+    for (int i = 0; i < cl->class_interface_count; ++i)
+    {
+	vim_free(((char_u **)cl->class_interfaces)[i]);
+	if (cl->class_interfaces_cl[i] != NULL)
+	    class_unref(cl->class_interfaces_cl[i]);
+    }
+    vim_free(cl->class_interfaces);
+    vim_free(cl->class_interfaces_cl);
+
+    itf2class_T *next;
+    for (itf2class_T *i2c = cl->class_itf2class; i2c != NULL; i2c = next)
+    {
+	next = i2c->i2c_next;
+	vim_free(i2c);
+    }
+
+    for (int i = 0; i < cl->class_class_member_count; ++i)
+    {
+	ocmember_T *m = &cl->class_class_members[i];
+	vim_free(m->ocm_name);
+	vim_free(m->ocm_init);
+	if (cl->class_members_tv != NULL)
+	    clear_tv(&cl->class_members_tv[i]);
+    }
+    vim_free(cl->class_class_members);
+    vim_free(cl->class_members_tv);
+
+    for (int i = 0; i < cl->class_obj_member_count; ++i)
+    {
+	ocmember_T *m = &cl->class_obj_members[i];
+	vim_free(m->ocm_name);
+	vim_free(m->ocm_init);
+    }
+    vim_free(cl->class_obj_members);
+
+    for (int i = 0; i < cl->class_class_function_count; ++i)
+    {
+	ufunc_T *uf = cl->class_class_functions[i];
+	func_clear_free(uf, FALSE);
+    }
+    vim_free(cl->class_class_functions);
+
+    for (int i = 0; i < cl->class_obj_method_count; ++i)
+    {
+	ufunc_T *uf = cl->class_obj_methods[i];
+	func_clear_free(uf, FALSE);
+    }
+    vim_free(cl->class_obj_methods);
+
+    clear_type_list(&cl->class_type_list);
+
+    class_cleared(cl);
+
+    vim_free(cl);
+}
+
+/*
  * Unreference a class.  Free it when the reference count goes down to zero.
  */
     void
 class_unref(class_T *cl)
 {
     if (cl != NULL && --cl->class_refcount <= 0 && cl->class_name != NULL)
-    {
-	// Freeing what the class contains may recursively come back here.
-	// Clear "class_name" first, if it is NULL the class does not need to
-	// be freed.
-	VIM_CLEAR(cl->class_name);
-
-	class_unref(cl->class_extends);
+	class_free(cl);
+}
 
-	for (int i = 0; i < cl->class_interface_count; ++i)
-	{
-	    vim_free(((char_u **)cl->class_interfaces)[i]);
-	    if (cl->class_interfaces_cl[i] != NULL)
-		class_unref(cl->class_interfaces_cl[i]);
-	}
-	vim_free(cl->class_interfaces);
-	vim_free(cl->class_interfaces_cl);
+/*
+ * Go through the list of all classes and free items without "copyID".
+ */
+    int
+class_free_nonref(int copyID)
+{
+    int		did_free = FALSE;
 
-	itf2class_T *next;
-	for (itf2class_T *i2c = cl->class_itf2class; i2c != NULL; i2c = next)
-	{
-	    next = i2c->i2c_next;
-	    vim_free(i2c);
-	}
-
-	for (int i = 0; i < cl->class_class_member_count; ++i)
+    for (class_T *cl = first_class; cl != NULL; cl = next_nonref_class)
+    {
+	next_nonref_class = cl->class_next_used;
+	if ((cl->class_copyID & COPYID_MASK) != (copyID & COPYID_MASK))
 	{
-	    ocmember_T *m = &cl->class_class_members[i];
-	    vim_free(m->ocm_name);
-	    vim_free(m->ocm_init);
-	    if (cl->class_members_tv != NULL)
-		clear_tv(&cl->class_members_tv[i]);
+	    // Free the class and items it contains.
+	    class_free(cl);
+	    did_free = TRUE;
 	}
-	vim_free(cl->class_class_members);
-	vim_free(cl->class_members_tv);
-
-	for (int i = 0; i < cl->class_obj_member_count; ++i)
-	{
-	    ocmember_T *m = &cl->class_obj_members[i];
-	    vim_free(m->ocm_name);
-	    vim_free(m->ocm_init);
-	}
-	vim_free(cl->class_obj_members);
+    }
 
-	for (int i = 0; i < cl->class_class_function_count; ++i)
-	{
-	    ufunc_T *uf = cl->class_class_functions[i];
-	    func_clear_free(uf, FALSE);
-	}
-	vim_free(cl->class_class_functions);
+    next_nonref_class = NULL;
+    return did_free;
+}
 
-	for (int i = 0; i < cl->class_obj_method_count; ++i)
-	{
-	    ufunc_T *uf = cl->class_obj_methods[i];
-	    func_clear_free(uf, FALSE);
-	}
-	vim_free(cl->class_obj_methods);
+    int
+set_ref_in_classes(int copyID)
+{
+    for (class_T *cl = first_class; cl != NULL; cl = cl->class_next_used)
+	set_ref_in_item_class(cl, copyID, NULL, NULL);
 
-	clear_type_list(&cl->class_type_list);
-
-	vim_free(cl);
-    }
+    return FALSE;
 }
 
 static object_T *first_object = NULL;