mirror of
https://github.com/python/cpython.git
synced 2025-11-06 00:22:07 +00:00
[3.13] gh-116946: fully implement GC protocol for _curses_panel.panel (GH-138333) (#138428)
[3.14] gh-116946: fully implement GC protocol for `_curses_panel.panel` (GH-138333)
This commit fixes possible reference loops via `panel.set_userptr`
by implementing `tp_clear` and `tp_traverse` for panel objects.
(cherry picked from commit 572df47840)
This commit is contained in:
parent
e7bb98a173
commit
afec2c70eb
1 changed files with 78 additions and 15 deletions
|
|
@ -32,6 +32,8 @@ typedef struct {
|
||||||
PyTypeObject *PyCursesPanel_Type;
|
PyTypeObject *PyCursesPanel_Type;
|
||||||
} _curses_panel_state;
|
} _curses_panel_state;
|
||||||
|
|
||||||
|
typedef struct PyCursesPanelObject PyCursesPanelObject;
|
||||||
|
|
||||||
static inline _curses_panel_state *
|
static inline _curses_panel_state *
|
||||||
get_curses_panel_state(PyObject *module)
|
get_curses_panel_state(PyObject *module)
|
||||||
{
|
{
|
||||||
|
|
@ -40,6 +42,25 @@ get_curses_panel_state(PyObject *module)
|
||||||
return (_curses_panel_state *)state;
|
return (_curses_panel_state *)state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline _curses_panel_state *
|
||||||
|
get_curses_panel_state_by_panel(PyCursesPanelObject *panel)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Note: 'state' may be NULL if Py_TYPE(panel) is not a heap
|
||||||
|
* type associated with this module, but the compiler would
|
||||||
|
* have likely already complained with an "invalid pointer
|
||||||
|
* type" at compile-time.
|
||||||
|
*
|
||||||
|
* To make it more robust, all functions recovering a module's
|
||||||
|
* state from an object should expect to return NULL with an
|
||||||
|
* exception set (in contrast to functions recovering a module's
|
||||||
|
* state from a module itself).
|
||||||
|
*/
|
||||||
|
void *state = PyType_GetModuleState(Py_TYPE(panel));
|
||||||
|
assert(state != NULL);
|
||||||
|
return (_curses_panel_state *)state;
|
||||||
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
_curses_panel_clear(PyObject *mod)
|
_curses_panel_clear(PyObject *mod)
|
||||||
{
|
{
|
||||||
|
|
@ -95,12 +116,14 @@ PyCursesCheckERR(_curses_panel_state *state, int code, const char *fname)
|
||||||
|
|
||||||
/* Definition of the panel object and panel type */
|
/* Definition of the panel object and panel type */
|
||||||
|
|
||||||
typedef struct {
|
typedef struct PyCursesPanelObject {
|
||||||
PyObject_HEAD
|
PyObject_HEAD
|
||||||
PANEL *pan;
|
PANEL *pan;
|
||||||
PyCursesWindowObject *wo; /* for reference counts */
|
PyCursesWindowObject *wo; /* for reference counts */
|
||||||
} PyCursesPanelObject;
|
} PyCursesPanelObject;
|
||||||
|
|
||||||
|
#define _PyCursesPanelObject_CAST(op) ((PyCursesPanelObject *)(op))
|
||||||
|
|
||||||
/* Some helper functions. The problem is that there's always a window
|
/* Some helper functions. The problem is that there's always a window
|
||||||
associated with a panel. To ensure that Python's GC doesn't pull
|
associated with a panel. To ensure that Python's GC doesn't pull
|
||||||
this window from under our feet we need to keep track of references
|
this window from under our feet we need to keep track of references
|
||||||
|
|
@ -260,8 +283,11 @@ static PyObject *
|
||||||
PyCursesPanel_New(_curses_panel_state *state, PANEL *pan,
|
PyCursesPanel_New(_curses_panel_state *state, PANEL *pan,
|
||||||
PyCursesWindowObject *wo)
|
PyCursesWindowObject *wo)
|
||||||
{
|
{
|
||||||
PyCursesPanelObject *po = PyObject_New(PyCursesPanelObject,
|
assert(state != NULL);
|
||||||
state->PyCursesPanel_Type);
|
PyTypeObject *type = state->PyCursesPanel_Type;
|
||||||
|
assert(type != NULL);
|
||||||
|
assert(type->tp_alloc != NULL);
|
||||||
|
PyCursesPanelObject *po = (PyCursesPanelObject *)type->tp_alloc(type, 0);
|
||||||
if (po == NULL) {
|
if (po == NULL) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
@ -276,26 +302,57 @@ PyCursesPanel_New(_curses_panel_state *state, PANEL *pan,
|
||||||
return (PyObject *)po;
|
return (PyObject *)po;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static int
|
||||||
PyCursesPanel_Dealloc(PyCursesPanelObject *po)
|
PyCursesPanel_Clear(PyObject *op)
|
||||||
{
|
{
|
||||||
PyObject *tp, *obj;
|
PyCursesPanelObject *self = _PyCursesPanelObject_CAST(op);
|
||||||
|
PyObject *extra = (PyObject *)panel_userptr(self->pan);
|
||||||
tp = (PyObject *) Py_TYPE(po);
|
if (extra != NULL) {
|
||||||
obj = (PyObject *) panel_userptr(po->pan);
|
Py_DECREF(extra);
|
||||||
if (obj) {
|
if (set_panel_userptr(self->pan, NULL) == ERR) {
|
||||||
(void)set_panel_userptr(po->pan, NULL);
|
_curses_panel_state *state = get_curses_panel_state_by_panel(self);
|
||||||
Py_DECREF(obj);
|
PyErr_SetString(state->PyCursesError,
|
||||||
|
"set_panel_userptr() returned ERR");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// self->wo should not be cleared because an associated WINDOW may exist
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
PyCursesPanel_Dealloc(PyObject *self)
|
||||||
|
{
|
||||||
|
PyTypeObject *tp = Py_TYPE(self);
|
||||||
|
PyObject_GC_UnTrack(self);
|
||||||
|
|
||||||
|
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_state *state = get_curses_panel_state_by_panel(po);
|
||||||
|
PyErr_SetString(state->PyCursesError, "del_panel() returned ERR");
|
||||||
|
PyErr_FormatUnraisable("Exception ignored in PyCursesPanel_Dealloc()");
|
||||||
}
|
}
|
||||||
(void)del_panel(po->pan);
|
|
||||||
if (po->wo != NULL) {
|
if (po->wo != NULL) {
|
||||||
Py_DECREF(po->wo);
|
Py_DECREF(po->wo);
|
||||||
remove_lop(po);
|
remove_lop(po);
|
||||||
}
|
}
|
||||||
PyObject_Free(po);
|
tp->tp_free(po);
|
||||||
Py_DECREF(tp);
|
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
|
/* panel_above(NULL) returns the bottom panel in the stack. To get
|
||||||
this behaviour we use curses.panel.bottom_panel(). */
|
this behaviour we use curses.panel.bottom_panel(). */
|
||||||
/*[clinic input]
|
/*[clinic input]
|
||||||
|
|
@ -517,7 +574,9 @@ static PyMethodDef PyCursesPanel_Methods[] = {
|
||||||
/* -------------------------------------------------------*/
|
/* -------------------------------------------------------*/
|
||||||
|
|
||||||
static PyType_Slot PyCursesPanel_Type_slots[] = {
|
static PyType_Slot PyCursesPanel_Type_slots[] = {
|
||||||
|
{Py_tp_clear, PyCursesPanel_Clear},
|
||||||
{Py_tp_dealloc, PyCursesPanel_Dealloc},
|
{Py_tp_dealloc, PyCursesPanel_Dealloc},
|
||||||
|
{Py_tp_traverse, PyCursesPanel_Traverse},
|
||||||
{Py_tp_methods, PyCursesPanel_Methods},
|
{Py_tp_methods, PyCursesPanel_Methods},
|
||||||
{0, 0},
|
{0, 0},
|
||||||
};
|
};
|
||||||
|
|
@ -525,7 +584,11 @@ static PyType_Slot PyCursesPanel_Type_slots[] = {
|
||||||
static PyType_Spec PyCursesPanel_Type_spec = {
|
static PyType_Spec PyCursesPanel_Type_spec = {
|
||||||
.name = "_curses_panel.panel",
|
.name = "_curses_panel.panel",
|
||||||
.basicsize = sizeof(PyCursesPanelObject),
|
.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
|
.slots = PyCursesPanel_Type_slots
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue