0
0
mirror of https://github.com/vim/vim.git synced 2025-09-23 03:43:49 -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);
#endif
abort = abort || set_ref_in_classes(copyID);
if (!abort)
{
/*
@@ -5353,6 +5355,9 @@ free_unref_items(int copyID)
// Go through the list of objects and free items without this 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
// Go through the list of jobs and free items without the copyID. This
// 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".
* Also see set_ref_in_item().
*/
static int
int
set_ref_in_item_class(
class_T *cl,
int copyID,
@@ -5716,8 +5721,7 @@ set_ref_in_item_class(
{
int abort = FALSE;
if (cl == NULL || cl->class_copyID == copyID
|| (cl->class_flags & CLASS_INTERFACE) != 0)
if (cl == NULL || cl->class_copyID == copyID)
return FALSE;
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_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_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);
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);

View File

@@ -12,6 +12,8 @@ void copy_object(typval_T *from, typval_T *to);
void object_unref(object_T *obj);
void copy_class(typval_T *from, typval_T *to);
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_cleared(object_T *obj);
int object_free_nonref(int copyID);

View File

@@ -1525,6 +1525,8 @@ struct class_S
int class_refcount;
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

View File

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

View File

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

View File

@@ -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 ?
@@ -1944,6 +1983,75 @@ 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.
*/
@@ -1951,67 +2059,39 @@ copy_class(typval_T *from, typval_T *to)
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)
{
// 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)
next_nonref_class = cl->class_next_used;
if ((cl->class_copyID & COPYID_MASK) != (copyID & COPYID_MASK))
{
vim_free(((char_u **)cl->class_interfaces)[i]);
if (cl->class_interfaces_cl[i] != NULL)
class_unref(cl->class_interfaces_cl[i]);
// Free the class and items it contains.
class_free(cl);
did_free = TRUE;
}
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);
vim_free(cl);
}
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;