8000 gh-107073: Make PyObject_VisitManagedDict() public by vstinner · Pull Request #108763 · python/cpython · GitHub
[go: up one dir, main page]

Skip to content
< 8000 h1 class="gh-header-title mb-2 lh-condensed f1 mr-0 flex-auto wb-break-word"> gh-107073: Make PyObject_VisitManagedDict() public #108763
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Oct 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions Doc/c-api/object.rst
Original file line number Diff line number Diff line change
Expand Up @@ -470,3 +470,21 @@ Object Protocol
:c:macro:`Py_TPFLAGS_ITEMS_AT_END` set.

.. versionadded:: 3.12

.. c:function:: int PyObject_VisitManagedDict(PyObject *obj, visitproc visit, void *arg)

Visit the managed dictionary of *obj*.

This function must only be called in a traverse function of the type which
has the :c:macro:`Py_TPFLAGS_MANAGED_DICT` flag set.

.. versionadded:: 3.13

.. c:function:: void PyObject_ClearManagedDict(PyObject *obj)

Clear the managed dictionary of *obj*.

This function must only be called in a traverse function of the type which
has the :c:macro:`Py_TPFLAGS_MANAGED_DICT` flag set.

.. versionadded:: 3.13
28 changes: 27 additions & 1 deletion Doc/c-api/typeobj.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1131,6 +1131,9 @@ and :c:data:`PyType_Type` effectively act as defaults.)

If this flag is set, :c:macro:`Py_TPFLAGS_HAVE_GC` should also be set.

The type traverse function must call :c:func:`PyObject_VisitManagedDict`
and its clear function must call :c:func:`PyObject_ClearManagedDict`.

.. versionadded:: 3.12

**Inheritance:**
Expand Down Expand Up @@ -1368,6 +1371,23 @@ and :c:data:`PyType_Type` effectively act as defaults.)
debugging aid you may want to visit it anyway just so the :mod:`gc` module's
:func:`~gc.get_referents` function will include it.

Heap types (:c:macro:`Py_TPFLAGS_HEAPTYPE`) must visit their type with::
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't seem relevant PyObject_VisitManagedDict, but no harm in adding it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doc was forgotten since Python 3.8, it requires to fix an important bug.


Py_VISIT(Py_TYPE(self));

It is only needed since Python 3.9. To support Python 3.8 and older, this
line must be conditionnal::

#if PY_VERSION_HEX >= 0x03090000
Py_VISIT(Py_TYPE(self));
#endif

If the :c:macro:`Py_TPFLAGS_MANAGED_DICT` bit is set in the
:c:member:`~PyTypeObject.tp_flags` field, the traverse function must call
:c:func:`PyObject_VisitManagedDict` like this::

PyObject_VisitManagedDict((PyObject*)self, visit, arg);

.. warning::
When implementing :c:member:`~PyTypeObject.tp_traverse`, only the
members that the instance *owns* (by having :term:`strong references
Expand Down Expand Up @@ -1451,6 +1471,12 @@ and :c:data:`PyType_Type` effectively act as defaults.)
so that *self* knows the contained object can no longer be used. The
:c:func:`Py_CLEAR` macro performs the operations in a safe order.

If the :c:macro:`Py_TPFLAGS_MANAGED_DICT` bit is set in the
:c:member:`~PyTypeObject.tp_flags` field, the traverse function must call
:c:func:`PyObject_ClearManagedDict` like this::

PyObject_ClearManagedDict((PyObject*)self);

Note that :c:member:`~PyTypeObject.tp_clear` is not *always* called
before an instance is deallocated. For example, when reference counting
is enough to determine that an object is no longer used, the cyclic garbage
Expand Down Expand Up @@ -1801,7 +1827,7 @@ and :c:data:`PyType_Type` effectively act as defaults.)
field is ``NULL`` then no :attr:`~object.__dict__` gets created for instances.

If the :c:macro:`Py_TPFLAGS_MANAGED_DICT` bit is set in the
:c:member:`~PyTypeObject.tp_dict` field, then
:c:member:`~PyTypeObject.tp_flags` field, then
:c:member:`~PyTypeObject.tp_dictoffset` will be set to ``-1``, to indicate
that it is unsafe to use this field.

Expand Down
2 changes: 1 addition & 1 deletion Doc/whatsnew/3.12.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1941,7 +1941,7 @@ Porting to Python 3.12
The use of ``tp_dictoffset`` and ``tp_weaklistoffset`` is still
supported, but does not fully support multiple inheritance
(:gh:`95589`), and performance may be worse.
Classes declaring :c:macro:`Py_TPFLAGS_MANAGED_DICT` should call
Classes declaring :c:macro:`Py_TPFLAGS_MANAGED_DICT` must call
:c:func:`!_PyObject_VisitManagedDict` and :c:func:`!_PyObject_ClearManagedDict`
to traverse and clear their instance's dictionaries.
To clear weakrefs, call :c:func:`PyObject_ClearWeakRefs`, as before.
Expand Down
6 changes: 6 additions & 0 deletions Doc/whatsnew/3.13.rst
Original file line number Diff line number Diff line change
Expand Up @@ -941,6 +941,12 @@ New Features
references) now supports the :ref:`Limited API <limited-c-api>`.
(Contributed by Victor Stinner in :gh:`108634`.)

* Add :c:func:`PyObject_VisitManagedDict` and
:c:func:`PyObject_ClearManagedDict` functions which must be called by the
traverse and clear functions of a type using
:c:macro:`Py_TPFLAGS_MANAGED_DICT` flag.
(Contributed by Victor Stinner in :gh:`107073`.)

Porting to Python 3.13
----------------------

Expand Down
4 changes: 2 additions & 2 deletions Include/cpython/object.h
Original file line number Diff line number Diff line change
Expand Up @@ -444,8 +444,8 @@ PyAPI_FUNC(int) _PyTrash_cond(PyObject *op, destructor dealloc);

PyAPI_FUNC(void *) PyObject_GetItemData(PyObject *obj);

PyAPI_FUNC(int) _PyObject_VisitManagedDict(PyObject *obj, visitproc visit, void *arg);
PyAPI_FUNC(void) _PyObject_ClearManagedDict(PyObject *obj);
PyAPI_FUNC(int) PyObject_VisitManagedDict(PyObject *obj, visitproc visit, void *arg);
PyAPI_FUNC(void) PyObject_ClearManagedDict(PyObject *obj);

#define TYPE_MAX_WATCHERS 8

Expand Down
3 changes: 3 additions & 0 deletions Misc/NEWS.d/next/C API/2023-09-01-15-35-05.gh-issue-107073.zCz0iN.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Add :c:func:`PyObject_VisitManagedDict` and :c:func:`PyObject_ClearManagedDict`
functions which must be called by the traverse and clear functions of a type
using :c:macro:`Py_TPFLAGS_MANAGED_DICT` flag. Patch by Victor Stinner.
6 changes: 3 additions & 3 deletions Modules/_asynciomodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -816,7 +816,7 @@ FutureObj_clear(FutureObj *fut)
Py_CLEAR(fut->fut_source_tb);
Py_CLEAR(fut->fut_cancel_msg);
Py_CLEAR(fut->fut_cancelled_exc);
_PyObject_ClearManagedDict((PyObject *)fut);
PyObject_ClearManagedDict((PyObject *)fut);
return 0;
}

Expand All @@ -834,7 +834,7 @@ FutureObj_traverse(FutureObj *fut, visitproc visit, void *arg)
Py_VISIT(fut->fut_source_tb);
Py_VISIT(fut->fut_cancel_msg);
Py_VISIT(fut->fut_cancelled_exc);
_PyObject_VisitManagedDict((PyObject *)fut, visit, arg);
PyObject_VisitManagedDict((PyObject *)fut, visit, arg);
return 0;
}

Expand Down Expand Up @@ -2181,7 +2181,7 @@ TaskObj_traverse(TaskObj *task, visitproc visit, void *arg)
Py_VISIT(fut->fut_source_tb);
Py_VISIT(fut->fut_cancel_msg);
Py_VISIT(fut->fut_cancelled_exc);
_PyObject_VisitManagedDict((PyObject *)fut, visit, arg);
PyObject_VisitManagedDict((PyObject *)fut, visit, arg);
return 0;
}

Expand Down
6 changes: 3 additions & 3 deletions Modules/_testcapi/heaptype.c
Original file line number Diff line number Diff line change
Expand Up @@ -805,21 +805,21 @@ static int
heapmanaged_traverse(HeapCTypeObject *self, visitproc visit, void *arg)
{
Py_VISIT(Py_TYPE(self));
return _PyObject_VisitManagedDict((PyObject *)self, visit, arg);
return PyObject_VisitManagedDict((PyObject *)self, visit, arg);
}

static int
heapmanaged_clear(HeapCTypeObject *self)
{
_PyObject_ClearManagedDict((PyObject *)self);
PyObject_ClearManagedDict((PyObject *)self);
return 0;
}

static void
heapmanaged_dealloc(HeapCTypeObject *self)
{
PyTypeObject *tp = Py_TYPE(self);
_PyObject_ClearManagedDict((PyObject *)self);
PyObject_ClearManagedDict((PyObject *)self);
PyObject_GC_UnTrack(self);
PyObject_GC_Del(self);
Py_DECREF(tp);
Expand Down
2 changes: 1 addition & 1 deletion Modules/_testcapimodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -2916,7 +2916,7 @@ settrace_to_error(PyObject *self, PyObject *list)
static PyObject *
clear_managed_dict(PyObject *self, PyObject *obj)
{
_PyObject_ClearManagedDict(obj);
PyObject_ClearManagedDict(obj);
Py_RETURN_NONE;
}

Expand Down
4 changes: 2 additions & 2 deletions Objects/dictobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -5650,7 +5650,7 @@ _PyObject_FreeInstanceAttributes(PyObject *self)
}

int
_PyObject_VisitManagedDict(PyObject *obj, visitproc visit, void *arg)
PyObject_VisitManagedDict(PyObject *obj, visitproc visit, void *arg)
{
PyTypeObject *tp = Py_TYPE(obj);
if((tp->tp_flags & Py_TPFLAGS_MANAGED_DICT) == 0) {
Expand All @@ -5673,7 +5673,7 @@ _PyObject_VisitManagedDict(PyObject *obj, visitproc visit, void *arg)
}

void
_PyObject_ClearManagedDict(PyObject *obj)
PyObject_ClearManagedDict(PyObject *obj)
{
PyTypeObject *tp = Py_TYPE(obj);
if((tp->tp_flags & Py_TPFLAGS_MANAGED_DICT) == 0) {
Expand Down
4 changes: 2 additions & 2 deletions Objects/typeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -1835,7 +1835,7 @@ subtype_traverse(PyObject *self, visitproc visit, void *arg)
assert(base->tp_dictoffset == 0);
if (type->tp_flags & Py_TPFLAGS_MANAGED_DICT) {
assert(type->tp_dictoffset == -1);
int err = _PyObject_VisitManagedDict(self, visit, arg);
int err = PyObject_VisitManagedDict(self, visit, arg);
if (err) {
return err;
}
Expand Down Expand Up @@ -1905,7 +1905,7 @@ subtype_clear(PyObject *self)
__dict__ slots (as in the case 'self.__dict__ is self'). */
if (type->tp_flags & Py_TPFLAGS_MANAGED_DICT) {
if ((base->tp_flags & Py_TPFLAGS_MANAGED_DICT) == 0) {
_PyObject_ClearManagedDict(self);
PyObject_ClearManagedDict(self);
}
}
else if (type->tp_dictoffset != base->tp_dictoffset) {
Expand Down
18 changes: 9 additions & 9 deletions Objects/typevarobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ typevar_dealloc(PyObject *self)
Py_XDECREF(tv->evaluate_bound);
Py_XDECREF(tv->constraints);
Py_XDECREF(tv->evaluate_constraints);
_PyObject_ClearManagedDict(self);
PyObject_ClearManagedDict(self);
PyObject_ClearWeakRefs(self);

Py_TYPE(self)->tp_free(self);
Expand All @@ -216,7 +216,7 @@ typevar_traverse(PyObject *self, visitproc visit, void *arg)
Py_VISIT(tv->evaluate_bound);
Py_VISIT(tv->constraints);
Py_VISIT(tv->evaluate_constraints);
_PyObject_VisitManagedDict(self, visit, arg);
PyObject_VisitManagedDict(self, visit, arg);
return 0;
}

Expand All @@ -227,7 +227,7 @@ typevar_clear(typevarobject *self)
Py_CLEAR(self->evaluate_bound);
Py_CLEAR(self->constraints);
Py_CLEAR(self->evaluate_constraints);
_PyObject_ClearManagedDict((PyObject *)self);
PyObject_ClearManagedDict((PyObject *)self);
return 0;
}

Expand Down Expand Up @@ -744,7 +744,7 @@ paramspec_dealloc(PyObject *self)

Py_DECREF(ps->name);
Py_XDECREF(ps->bound);
_PyObject_ClearManagedDict(self);
PyObject_ClearManagedDict(self);
PyObject_ClearWeakRefs(self);

Py_TYPE(self)->tp_free(self);
Expand All @@ -757,15 +757,15 @@ paramspec_traverse(PyObject *self, visitproc visit, void *arg)
Py_VISIT(Py_TYPE(self));
paramspecobject *ps = (paramspecobject *)self;
Py_VISIT(ps->bound);
_PyObject_VisitManagedDict(self, visit, arg);
PyObject_VisitManagedDict(self, visit, arg);
return 0;
}

static int
paramspec_clear(paramspecobject *self)
{
Py_CLEAR(self->bound);
_PyObject_ClearManagedDict((PyObject *)self);
PyObject_ClearManagedDict((PyObject *)self);
return 0;
}

Expand Down Expand Up @@ -1026,7 +1026,7 @@ typevartuple_dealloc(PyObject *self)
typevartupleobject *tvt = (typevartupleobject *)self;

Py_DECREF(tvt->name);
_PyObject_ClearManagedDict(self);
PyObject_ClearManagedDict(self);
PyObject_ClearWeakRefs(self);

Py_TYPE(self)->tp_free(self);
Expand Down Expand Up @@ -1165,14 +1165,14 @@ static int
typevartuple_traverse(PyObject *self, visitproc visit, void *arg)
{
Py_VISIT(Py_TYPE(self));
_PyObject_VisitManagedDict(self, visit, arg);
PyObject_VisitManagedDict(self, visit, arg);
return 0;
}

static int
typevartuple_clear(PyObject *self)
{
_PyObject_ClearManagedDict(self);
PyObject_ClearManagedDict(self);
return 0;
}

Expand Down
0