Skip to content

Commit 572df47

Browse files
authored
gh-116946: fully implement GC protocol for _curses_panel.panel (#138333)
This commit fixes possible reference loops via `panel.set_userptr` by implementing `tp_clear` and `tp_traverse` for panel objects.
1 parent 51244ba commit 572df47

File tree

1 file changed

+44
-14
lines changed

1 file changed

+44
-14
lines changed

Modules/_curses_panel.c

Lines changed: 44 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -410,8 +410,11 @@ static PyObject *
410410
PyCursesPanel_New(_curses_panel_state *state, PANEL *pan,
411411
PyCursesWindowObject *wo)
412412
{
413-
PyCursesPanelObject *po = PyObject_New(PyCursesPanelObject,
414-
state->PyCursesPanel_Type);
413+
assert(state != NULL);
414+
PyTypeObject *type = state->PyCursesPanel_Type;
415+
assert(type != NULL);
416+
assert(type->tp_alloc != NULL);
417+
PyCursesPanelObject *po = (PyCursesPanelObject *)type->tp_alloc(type, 0);
415418
if (po == NULL) {
416419
return NULL;
417420
}
@@ -426,20 +429,31 @@ PyCursesPanel_New(_curses_panel_state *state, PANEL *pan,
426429
return (PyObject *)po;
427430
}
428431

432+
static int
433+
PyCursesPanel_Clear(PyObject *op)
434+
{
435+
PyCursesPanelObject *self = _PyCursesPanelObject_CAST(op);
436+
PyObject *extra = (PyObject *)panel_userptr(self->pan);
437+
if (extra != NULL) {
438+
Py_DECREF(extra);
439+
if (set_panel_userptr(self->pan, NULL) == ERR) {
440+
curses_panel_panel_set_error(self, "set_panel_userptr", NULL);
441+
return -1;
442+
}
443+
}
444+
// self->wo should not be cleared because an associated WINDOW may exist
445+
return 0;
446+
}
447+
429448
static void
430449
PyCursesPanel_Dealloc(PyObject *self)
431450
{
432-
PyObject *tp, *obj;
433-
PyCursesPanelObject *po = _PyCursesPanelObject_CAST(self);
451+
PyTypeObject *tp = Py_TYPE(self);
452+
PyObject_GC_UnTrack(self);
434453

435-
tp = (PyObject *) Py_TYPE(po);
436-
obj = (PyObject *) panel_userptr(po->pan);
437-
if (obj) {
438-
Py_DECREF(obj);
439-
if (set_panel_userptr(po->pan, NULL) == ERR) {
440-
curses_panel_panel_set_error(po, "set_panel_userptr", "__del__");
441-
PyErr_FormatUnraisable("Exception ignored in PyCursesPanel_Dealloc()");
442-
}
454+
PyCursesPanelObject *po = _PyCursesPanelObject_CAST(self);
455+
if (PyCursesPanel_Clear(self) < 0) {
456+
PyErr_FormatUnraisable("Exception ignored in PyCursesPanel_Dealloc()");
443457
}
444458
if (del_panel(po->pan) == ERR && !PyErr_Occurred()) {
445459
curses_panel_panel_set_error(po, "del_panel", "__del__");
@@ -452,10 +466,20 @@ PyCursesPanel_Dealloc(PyObject *self)
452466
PyErr_FormatUnraisable("Exception ignored in PyCursesPanel_Dealloc()");
453467
}
454468
}
455-
PyObject_Free(po);
469+
tp->tp_free(po);
456470
Py_DECREF(tp);
457471
}
458472

473+
static int
474+
PyCursesPanel_Traverse(PyObject *op, visitproc visit, void *arg)
475+
{
476+
PyCursesPanelObject *self = _PyCursesPanelObject_CAST(op);
477+
Py_VISIT(Py_TYPE(op));
478+
Py_VISIT(panel_userptr(self->pan));
479+
Py_VISIT(self->wo);
480+
return 0;
481+
}
482+
459483
/* panel_above(NULL) returns the bottom panel in the stack. To get
460484
this behaviour we use curses.panel.bottom_panel(). */
461485
/*[clinic input]
@@ -647,15 +671,21 @@ static PyMethodDef PyCursesPanel_Methods[] = {
647671
/* -------------------------------------------------------*/
648672

649673
static PyType_Slot PyCursesPanel_Type_slots[] = {
674+
{Py_tp_clear, PyCursesPanel_Clear},
650675
{Py_tp_dealloc, PyCursesPanel_Dealloc},
676+
{Py_tp_traverse, PyCursesPanel_Traverse},
651677
{Py_tp_methods, PyCursesPanel_Methods},
652678
{0, 0},
653679
};
654680

655681
static PyType_Spec PyCursesPanel_Type_spec = {
656682
.name = "_curses_panel.panel",
657683
.basicsize = sizeof(PyCursesPanelObject),
658-
.flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_DISALLOW_INSTANTIATION,
684+
.flags = (
685+
Py_TPFLAGS_DEFAULT
686+
| Py_TPFLAGS_DISALLOW_INSTANTIATION
687+
| Py_TPFLAGS_HAVE_GC
688+
),
659689
.slots = PyCursesPanel_Type_slots
660690
};
661691

0 commit comments

Comments
 (0)