# HG changeset patch # User Bram Moolenaar # Date 1685980804 -7200 # Node ID 5c4c2d82d75173c1fe314c01766b1c34f75ec98f # Parent 5178a7e130504aa5b88cbaa4396888f6a26f461b patch 9.0.1609: crash when an object indirectly references itself Commit: https://github.com/vim/vim/commit/f7ca56f7193f8b383be43f1f6b3a6c6ca77b4233 Author: Bram Moolenaar Date: Mon Jun 5 16:53:25 2023 +0100 patch 9.0.1609: crash when an object indirectly references itself Problem: Crash when an object indirectly references itself. Solution: Avoid clearing an object while it is already being cleared. (closes #12494) diff --git a/src/testdir/test_vim9_class.vim b/src/testdir/test_vim9_class.vim --- 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 --- 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 --- 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; }