]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 9.0.1609: crash when an object indirectly references itself v9.0.1609
authorBram Moolenaar <Bram@vim.org>
Mon, 5 Jun 2023 15:53:25 +0000 (16:53 +0100)
committerBram Moolenaar <Bram@vim.org>
Mon, 5 Jun 2023 15:53:25 +0000 (16:53 +0100)
Problem:    Crash when an object indirectly references itself.
Solution:   Avoid clearing an object while it is already being cleared.
            (closes #12494)

src/testdir/test_vim9_class.vim
src/version.c
src/vim9class.c

index 52812eac73896b2c1639391afb0dd1102ca86593..1d0d77b2982def02156f47a68db66c053e42d40a 100644 (file)
@@ -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()
index 1a8dd0e604aec34a6a9ec78198a01b9c97e24fd3..12884906700c156dc1678a82a7baba23944d1c2d 100644 (file)
@@ -695,6 +695,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    1609,
 /**/
     1608,
 /**/
index 734967a8029383f7562b248b57cece4d4b336827..12b09677b0be3fe9a1cef5ddf0bb5f875e2458c0 100644 (file)
@@ -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;
 }