0
0
mirror of https://github.com/vim/vim.git synced 2025-09-25 03:54:15 -04:00

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>
This commit is contained in:
Yegappan Lakshmanan
2023-09-04 07:51:01 +02:00
committed by Christian Brabandt
parent 233f956bd4
commit e651e110c1
7 changed files with 159 additions and 68 deletions

View File

@@ -5305,6 +5305,8 @@ garbage_collect(int testing)
abort = abort || set_ref_in_popups(copyID); abort = abort || set_ref_in_popups(copyID);
#endif #endif
abort = abort || set_ref_in_classes(copyID);
if (!abort) if (!abort)
{ {
/* /*
@@ -5353,6 +5355,9 @@ free_unref_items(int copyID)
// Go through the list of objects and free items without this copyID. // Go through the list of objects and free items without this copyID.
did_free |= object_free_nonref(copyID); did_free |= object_free_nonref(copyID);
// Go through the list of classes and free items without this copyID.
did_free |= class_free_nonref(copyID);
#ifdef FEAT_JOB_CHANNEL #ifdef FEAT_JOB_CHANNEL
// Go through the list of jobs and free items without the copyID. This // Go through the list of jobs and free items without the copyID. This
// must happen before doing channels, because jobs refer to channels, but // must happen before doing channels, because jobs refer to channels, but
@@ -5707,7 +5712,7 @@ set_ref_in_item_channel(
* Mark the class "cl" with "copyID". * Mark the class "cl" with "copyID".
* Also see set_ref_in_item(). * Also see set_ref_in_item().
*/ */
static int int
set_ref_in_item_class( set_ref_in_item_class(
class_T *cl, class_T *cl,
int copyID, int copyID,
@@ -5716,8 +5721,7 @@ set_ref_in_item_class(
{ {
int abort = FALSE; int abort = FALSE;
if (cl == NULL || cl->class_copyID == copyID if (cl == NULL || cl->class_copyID == copyID)
|| (cl->class_flags & CLASS_INTERFACE) != 0)
return FALSE; return FALSE;
cl->class_copyID = copyID; cl->class_copyID = copyID;

View File

@@ -59,6 +59,7 @@ int set_ref_in_dict(dict_T *d, int copyID);
int set_ref_in_list(list_T *ll, int copyID); int set_ref_in_list(list_T *ll, int copyID);
int set_ref_in_list_items(list_T *l, int copyID, ht_stack_T **ht_stack); int set_ref_in_list_items(list_T *l, int copyID, ht_stack_T **ht_stack);
int set_ref_in_callback(callback_T *cb, int copyID); int set_ref_in_callback(callback_T *cb, int copyID);
int set_ref_in_item_class(class_T *cl, int copyID, ht_stack_T **ht_stack, list_stack_T **list_stack);
int set_ref_in_item(typval_T *tv, int copyID, ht_stack_T **ht_stack, list_stack_T **list_stack); int set_ref_in_item(typval_T *tv, int copyID, ht_stack_T **ht_stack, list_stack_T **list_stack);
char_u *echo_string_core(typval_T *tv, char_u **tofree, char_u *numbuf, int copyID, int echo_style, int restore_copyID, int composite_val); char_u *echo_string_core(typval_T *tv, char_u **tofree, char_u *numbuf, int copyID, int echo_style, int restore_copyID, int composite_val);
char_u *echo_string(typval_T *tv, char_u **tofree, char_u *numbuf, int copyID); char_u *echo_string(typval_T *tv, char_u **tofree, char_u *numbuf, int copyID);

View File

@@ -12,6 +12,8 @@ void copy_object(typval_T *from, typval_T *to);
void object_unref(object_T *obj); void object_unref(object_T *obj);
void copy_class(typval_T *from, typval_T *to); void copy_class(typval_T *from, typval_T *to);
void class_unref(class_T *cl); void class_unref(class_T *cl);
int class_free_nonref(int copyID);
int set_ref_in_classes(int copyID);
void object_created(object_T *obj); void object_created(object_T *obj);
void object_cleared(object_T *obj); void object_cleared(object_T *obj);
int object_free_nonref(int copyID); int object_free_nonref(int copyID);

View File

@@ -1525,6 +1525,8 @@ struct class_S
int class_refcount; int class_refcount;
int class_copyID; // used by garbage collection int class_copyID; // used by garbage collection
class_T *class_next_used; // for list headed by "first_class"
class_T *class_prev_used; // for list headed by "first_class"
class_T *class_extends; // parent class or NULL class_T *class_extends; // parent class or NULL

View File

@@ -3789,17 +3789,17 @@ def Test_modify_class_member_from_def_function()
vim9script vim9script
class A class A
this.var1: number = 10 this.var1: number = 10
public static var2 = 20 public static var2: list<number> = [1, 2]
public static var3 = 30 public static var3: dict<number> = {a: 1, b: 2}
static _priv_var4: number = 40 static _priv_var4: number = 40
endclass endclass
def T() def T()
assert_equal(20, A.var2) assert_equal([1, 2], A.var2)
assert_equal(30, A.var3) assert_equal({a: 1, b: 2}, A.var3)
A.var2 = 50 A.var2 = [3, 4]
A.var3 = 60 A.var3 = {c: 3, d: 4}
assert_equal(50, A.var2) assert_equal([3, 4], A.var2)
assert_equal(60, A.var3) assert_equal({c: 3, d: 4}, A.var3)
assert_fails('echo A._priv_var4', 'E1333: Cannot access private member: _priv_var4') assert_fails('echo A._priv_var4', 'E1333: Cannot access private member: _priv_var4')
enddef enddef
T() T()

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 */
/**/
1862,
/**/ /**/
1861, 1861,
/**/ /**/

View File

@@ -21,6 +21,43 @@
# include "vim9.h" # include "vim9.h"
#endif #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. * Parse a member declaration, both object and class member.
* Returns OK or FAIL. When OK then "varname_end" is set to just after the * 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_object_type.tt_class = cl;
cl->class_type_list = type_list; cl->class_type_list = type_list;
class_created(cl);
// TODO: // TODO:
// - Fill hashtab with object members and methods ? // - Fill hashtab with object members and methods ?
@@ -1945,13 +1984,11 @@ copy_class(typval_T *from, typval_T *to)
} }
/* /*
* Unreference a class. Free it when the reference count goes down to zero. * Free the class "cl" and its contents.
*/ */
void static void
class_unref(class_T *cl) class_free(class_T *cl)
{ {
if (cl != NULL && --cl->class_refcount <= 0 && cl->class_name != NULL)
{
// Freeing what the class contains may recursively come back here. // Freeing what the class contains may recursively come back here.
// Clear "class_name" first, if it is NULL the class does not need to // Clear "class_name" first, if it is NULL the class does not need to
// be freed. // be freed.
@@ -2010,8 +2047,51 @@ class_unref(class_T *cl)
clear_type_list(&cl->class_type_list); clear_type_list(&cl->class_type_list);
class_cleared(cl);
vim_free(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)
class_free(cl);
}
/*
* Go through the list of all classes and free items without "copyID".
*/
int
class_free_nonref(int copyID)
{
int did_free = FALSE;
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))
{
// Free the class and items it contains.
class_free(cl);
did_free = TRUE;
} }
}
next_nonref_class = NULL;
return did_free;
}
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);
return FALSE;
} }
static object_T *first_object = NULL; static object_T *first_object = NULL;