summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBram Moolenaar <Bram@vim.org>2023-06-05 16:53:25 +0100
committerBram Moolenaar <Bram@vim.org>2023-06-05 16:53:25 +0100
commitf7ca56f7193f8b383be43f1f6b3a6c6ca77b4233 (patch)
treece30cd88fa64abf5b4a73366dd4a463f0d141ce8
parent5c606846b9a43c7e6b94c7e96838f7532792f557 (diff)
patch 9.0.1609: crash when an object indirectly references itselfv9.0.1609
Problem: Crash when an object indirectly references itself. Solution: Avoid clearing an object while it is already being cleared. (closes #12494)
-rw-r--r--src/testdir/test_vim9_class.vim27
-rw-r--r--src/version.c2
-rw-r--r--src/vim9class.c15
3 files changed, 41 insertions, 3 deletions
diff --git a/src/testdir/test_vim9_class.vim b/src/testdir/test_vim9_class.vim
index 52812eac73..1d0d77b298 100644
--- a/src/testdir/test_vim9_class.vim
+++ b/src/testdir/test_vim9_class.vim
@@ -925,6 +925,33 @@ func Test_class_garbagecollect()
echo Point.pl Point.pd
END
call v9.CheckScriptSuccess(lines)
+
+ let lines =<< trim END
+ vim9script
+
+ interface View
+ endinterface
+
+ class Widget
+ this.view: View
+ endclass
+
+ class MyView implements View
+ this.widget: Widget
+
+ def new()
+ # this will result in a circular reference to this object
+ this.widget = Widget.new(this)
+ enddef
+ endclass
+
+ var view = MyView.new()
+
+ # overwrite "view", will be garbage-collected next
+ view = MyView.new()
+ test_garbagecollect_now()
+ END
+ call v9.CheckScriptSuccess(lines)
endfunc
def Test_class_function()
diff --git a/src/version.c b/src/version.c
index 1a8dd0e604..1288490670 100644
--- a/src/version.c
+++ b/src/version.c
@@ -696,6 +696,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
+ 1609,
+/**/
1608,
/**/
1607,
diff --git a/src/vim9class.c b/src/vim9class.c
index 734967a802..12b09677b0 100644
--- a/src/vim9class.c
+++ b/src/vim9class.c
@@ -1497,6 +1497,9 @@ copy_object(typval_T *from, typval_T *to)
static void
object_clear(object_T *obj)
{
+ // Avoid a recursive call, it can happen if "obj" has a circular reference.
+ obj->obj_refcount = INT_MAX;
+
class_T *cl = obj->obj_class;
// the member values are just after the object structure
@@ -1619,6 +1622,8 @@ object_created(object_T *obj)
first_object = obj;
}
+static object_T *next_nonref_obj = NULL;
+
/*
* Call this function when an object has been cleared and is about to be freed.
* It is removed from the list headed by "first_object".
@@ -1632,6 +1637,10 @@ object_cleared(object_T *obj)
obj->obj_prev_used->obj_next_used = obj->obj_next_used;
else if (first_object == obj)
first_object = obj->obj_next_used;
+
+ // update the next object to check if needed
+ if (obj == next_nonref_obj)
+ next_nonref_obj = obj->obj_next_used;
}
/*
@@ -1641,11 +1650,10 @@ object_cleared(object_T *obj)
object_free_nonref(int copyID)
{
int did_free = FALSE;
- object_T *next_obj;
- for (object_T *obj = first_object; obj != NULL; obj = next_obj)
+ for (object_T *obj = first_object; obj != NULL; obj = next_nonref_obj)
{
- next_obj = obj->obj_next_used;
+ next_nonref_obj = obj->obj_next_used;
if ((obj->obj_copyID & COPYID_MASK) != (copyID & COPYID_MASK))
{
// Free the object and items it contains.
@@ -1654,6 +1662,7 @@ object_free_nonref(int copyID)
}
}
+ next_nonref_obj = NULL;
return did_free;
}