]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-116946: fully implement GC protocol for `_curses_panel.panel` (#138333)
authorBénédikt Tran <10796600+picnixz@users.noreply.github.com>
Wed, 3 Sep 2025 08:17:17 +0000 (10:17 +0200)
committerGitHub <noreply@github.com>
Wed, 3 Sep 2025 08:17:17 +0000 (10:17 +0200)
This commit fixes possible reference loops via `panel.set_userptr`
by implementing `tp_clear` and `tp_traverse` for panel objects.

Modules/_curses_panel.c

index 3408a5055755804f622d5fa99056961dfccf2ed7..66a8c40953da8c9973850916d538bf31d1327214 100644 (file)
@@ -410,8 +410,11 @@ static PyObject *
 PyCursesPanel_New(_curses_panel_state *state, PANEL *pan,
                   PyCursesWindowObject *wo)
 {
-    PyCursesPanelObject *po = PyObject_New(PyCursesPanelObject,
-                                           state->PyCursesPanel_Type);
+    assert(state != NULL);
+    PyTypeObject *type = state->PyCursesPanel_Type;
+    assert(type != NULL);
+    assert(type->tp_alloc != NULL);
+    PyCursesPanelObject *po = (PyCursesPanelObject *)type->tp_alloc(type, 0);
     if (po == NULL) {
         return NULL;
     }
@@ -426,20 +429,31 @@ PyCursesPanel_New(_curses_panel_state *state, PANEL *pan,
     return (PyObject *)po;
 }
 
+static int
+PyCursesPanel_Clear(PyObject *op)
+{
+    PyCursesPanelObject *self = _PyCursesPanelObject_CAST(op);
+    PyObject *extra = (PyObject *)panel_userptr(self->pan);
+    if (extra != NULL) {
+        Py_DECREF(extra);
+        if (set_panel_userptr(self->pan, NULL) == ERR) {
+            curses_panel_panel_set_error(self, "set_panel_userptr", NULL);
+            return -1;
+        }
+    }
+    // self->wo should not be cleared because an associated WINDOW may exist
+    return 0;
+}
+
 static void
 PyCursesPanel_Dealloc(PyObject *self)
 {
-    PyObject *tp, *obj;
-    PyCursesPanelObject *po = _PyCursesPanelObject_CAST(self);
+    PyTypeObject *tp = Py_TYPE(self);
+    PyObject_GC_UnTrack(self);
 
-    tp = (PyObject *) Py_TYPE(po);
-    obj = (PyObject *) panel_userptr(po->pan);
-    if (obj) {
-        Py_DECREF(obj);
-        if (set_panel_userptr(po->pan, NULL) == ERR) {
-            curses_panel_panel_set_error(po, "set_panel_userptr", "__del__");
-            PyErr_FormatUnraisable("Exception ignored in PyCursesPanel_Dealloc()");
-        }
+    PyCursesPanelObject *po = _PyCursesPanelObject_CAST(self);
+    if (PyCursesPanel_Clear(self) < 0) {
+        PyErr_FormatUnraisable("Exception ignored in PyCursesPanel_Dealloc()");
     }
     if (del_panel(po->pan) == ERR && !PyErr_Occurred()) {
         curses_panel_panel_set_error(po, "del_panel", "__del__");
@@ -452,10 +466,20 @@ PyCursesPanel_Dealloc(PyObject *self)
             PyErr_FormatUnraisable("Exception ignored in PyCursesPanel_Dealloc()");
         }
     }
-    PyObject_Free(po);
+    tp->tp_free(po);
     Py_DECREF(tp);
 }
 
+static int
+PyCursesPanel_Traverse(PyObject *op, visitproc visit, void *arg)
+{
+    PyCursesPanelObject *self = _PyCursesPanelObject_CAST(op);
+    Py_VISIT(Py_TYPE(op));
+    Py_VISIT(panel_userptr(self->pan));
+    Py_VISIT(self->wo);
+    return 0;
+}
+
 /* panel_above(NULL) returns the bottom panel in the stack. To get
    this behaviour we use curses.panel.bottom_panel(). */
 /*[clinic input]
@@ -647,7 +671,9 @@ static PyMethodDef PyCursesPanel_Methods[] = {
 /* -------------------------------------------------------*/
 
 static PyType_Slot PyCursesPanel_Type_slots[] = {
+    {Py_tp_clear, PyCursesPanel_Clear},
     {Py_tp_dealloc, PyCursesPanel_Dealloc},
+    {Py_tp_traverse, PyCursesPanel_Traverse},
     {Py_tp_methods, PyCursesPanel_Methods},
     {0, 0},
 };
@@ -655,7 +681,11 @@ static PyType_Slot PyCursesPanel_Type_slots[] = {
 static PyType_Spec PyCursesPanel_Type_spec = {
     .name = "_curses_panel.panel",
     .basicsize = sizeof(PyCursesPanelObject),
-    .flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_DISALLOW_INSTANTIATION,
+    .flags = (
+        Py_TPFLAGS_DEFAULT
+        | Py_TPFLAGS_DISALLOW_INSTANTIATION
+        | Py_TPFLAGS_HAVE_GC
+    ),
     .slots = PyCursesPanel_Type_slots
 };