Skip to content
Open
Show file tree
Hide file tree
Changes from 6 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
2 changes: 2 additions & 0 deletions Doc/c-api/typeobj.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1278,6 +1278,8 @@ and :c:data:`PyType_Type` effectively act as defaults.)
This bit indicates that instances of the class should be weakly
referenceable.

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

.. versionadded:: 3.12

**Inheritance:**
Expand Down
2 changes: 2 additions & 0 deletions Doc/extending/newtypes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -560,6 +560,8 @@ For an object to be weakly referenceable, the extension type must set the
field. The legacy :c:member:`~PyTypeObject.tp_weaklistoffset` field should
be left as zero.

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

Concretely, here is how the statically declared type object would look::

static PyTypeObject TrivialType = {
Expand Down
8 changes: 8 additions & 0 deletions Doc/whatsnew/3.15.rst
Original file line number Diff line number Diff line change
Expand Up @@ -785,6 +785,14 @@ New features
(Contributed by Victor Stinner in :gh:`129813`.)


Limited C API changes
---------------------

* If the :c:macro:`Py_TPFLAGS_MANAGED_DICT` and :c:macro:`Py_TPFLAGS_MANAGED_WEAKREF`
flags are set then :c:macro:`Py_TPFLAGS_HAVE_GC` should be set too.
(Contributed by Sergey Miryanov in :gh:`134786`)


Porting to Python 3.15
----------------------

Expand Down
2 changes: 2 additions & 0 deletions Include/internal/pycore_object.h
Original file line number Diff line number Diff line change
Expand Up @@ -936,9 +936,11 @@ extern int _PyType_CacheInitForSpecialization(PyHeapTypeObject *type,
#ifdef Py_GIL_DISABLED
# define MANAGED_DICT_OFFSET (((Py_ssize_t)sizeof(PyObject *))*-1)
# define MANAGED_WEAKREF_OFFSET (((Py_ssize_t)sizeof(PyObject *))*-2)
# define MANAGED_WEAKREF_OFFSET_NO_GC (((Py_ssize_t)sizeof(PyObject *))*-2)
#else
# define MANAGED_DICT_OFFSET (((Py_ssize_t)sizeof(PyObject *))*-3)
# define MANAGED_WEAKREF_OFFSET (((Py_ssize_t)sizeof(PyObject *))*-4)
# define MANAGED_WEAKREF_OFFSET_NO_GC (((Py_ssize_t)sizeof(PyObject *))*-2)
#endif

typedef union {
Expand Down
2 changes: 1 addition & 1 deletion Include/object.h
Original file line number Diff line number Diff line change
Expand Up @@ -529,7 +529,7 @@ given type object has a specified feature.
#define Py_TPFLAGS_INLINE_VALUES (1 << 2)

/* Placement of weakref pointers are managed by the VM, not by the type.
* The VM will automatically set tp_weaklistoffset.
* The VM will automatically set tp_weaklistoffset. Implies Py_TPFLAGS_HAVE_GC.
*/
#define Py_TPFLAGS_MANAGED_WEAKREF (1 << 3)

Expand Down
9 changes: 9 additions & 0 deletions Lib/test/test_capi/test_type.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from test.support import import_helper, Py_GIL_DISABLED, refleak_helper
import unittest
import weakref

_testcapi = import_helper.import_module('_testcapi')

Expand Down Expand Up @@ -274,3 +275,11 @@ def test_extension_managed_dict_type(self):
obj.__dict__ = {'bar': 3}
self.assertEqual(obj.__dict__, {'bar': 3})
self.assertEqual(obj.bar, 3)

def test_type_have_weakref_and_no_gc(self):
ManagedWeakrefNoGCType = _testcapi.ManagedWeakrefNoGCType
obj = ManagedWeakrefNoGCType()
wr = weakref.ref(obj)

del obj # shouldn't segfault
del wr
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Force to use :c:macro:`Py_TPFLAGS_HAVE_GC` if
:c:macro:`Py_TPFLAGS_MANAGED_DICT` or :c:macro:`Py_TPFLAGS_MANAGED_WEAKREF`
used.
41 changes: 41 additions & 0 deletions Modules/_testcapimodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -3227,12 +3227,44 @@ static PyType_Spec ManagedDict_spec = {
ManagedDict_slots
};

typedef struct {
PyObject_HEAD
} ManagedWeakrefNoGCObject;

static void
ManagedWeakrefNoGC_dealloc(PyObject *self)
{
PyObject_ClearWeakRefs(self);
PyTypeObject *tp = Py_TYPE(self);
tp->tp_free(self);
Py_DECREF(tp);
}

static PyType_Slot ManagedWeakrefNoGC_slots[] = {
{Py_tp_dealloc, ManagedWeakrefNoGC_dealloc},
{0, 0}
};

static PyType_Spec ManagedWeakrefNoGC_spec = {
.name = "_testcapi.ManagedWeakrefNoGCType",
.basicsize = sizeof(ManagedWeakrefNoGCObject),
.flags = (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_MANAGED_WEAKREF),
.slots = ManagedWeakrefNoGC_slots,
};


static PyObject *
create_managed_dict_type(void)
{
return PyType_FromSpec(&ManagedDict_spec);
}

static PyObject *
create_managed_weakref_no_gc_type(void)
{
return PyType_FromSpec(&ManagedWeakrefNoGC_spec);
}

static int
_testcapi_exec(PyObject *m)
{
Expand Down Expand Up @@ -3362,6 +3394,15 @@ _testcapi_exec(PyObject *m)
return -1;
}

PyObject *managed_weakref_no_gc_type = create_managed_weakref_no_gc_type();
if (managed_weakref_no_gc_type == NULL) {
return -1;
}
if (PyModule_Add(m, "ManagedWeakrefNoGCType", managed_weakref_no_gc_type) < 0) {
return -1;
}


/* Include tests from the _testcapi/ directory */
if (_PyTestCapi_Init_Vectorcall(m) < 0) {
return -1;
Expand Down
37 changes: 34 additions & 3 deletions Objects/typeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -4655,7 +4655,12 @@ type_new_descriptors(const type_new_ctx *ctx, PyTypeObject *type, PyObject *dict
if (ctx->add_weak) {
assert((type->tp_flags & Py_TPFLAGS_MANAGED_WEAKREF) == 0);
type_add_flags(type, Py_TPFLAGS_MANAGED_WEAKREF);
type->tp_weaklistoffset = MANAGED_WEAKREF_OFFSET;
if (_PyType_IS_GC(type)) {
type->tp_weaklistoffset = MANAGED_WEAKREF_OFFSET;
}
else {
type->tp_weaklistoffset = MANAGED_WEAKREF_OFFSET_NO_GC;
}
}
if (ctx->add_dict) {
assert((type->tp_flags & Py_TPFLAGS_MANAGED_DICT) == 0);
Expand Down Expand Up @@ -8500,6 +8505,13 @@ overrides_hash(PyTypeObject *type)
return r;
}

void
PyObject_NoGC_Preheader_Del(void *op)
{
size_t presize = _PyType_PreHeaderSize(Py_TYPE(op));
PyObject_Free(((char *)op) - presize);
}

static int
inherit_slots(PyTypeObject *type, PyTypeObject *base)
{
Expand Down Expand Up @@ -8677,7 +8689,21 @@ inherit_slots(PyTypeObject *type, PyTypeObject *base)
if ((type->tp_flags & Py_TPFLAGS_HAVE_GC) ==
(base->tp_flags & Py_TPFLAGS_HAVE_GC)) {
/* They agree about gc. */
COPYSLOT(tp_free);

if ((type->tp_flags & Py_TPFLAGS_PREHEADER) &&
type->tp_free == NULL &&
base->tp_free == PyObject_Free) {
/* Because type has preheader fields, its
* objects will be allocated with those fields
* and it should be take in account when object
* is freed, so we use special tp_free.
*/
type->tp_free = PyObject_NoGC_Preheader_Del;
}
else {
COPYSLOT(tp_free);
}

}
else if ((type->tp_flags & Py_TPFLAGS_HAVE_GC) &&
type->tp_free == NULL &&
Expand Down Expand Up @@ -8905,7 +8931,12 @@ type_ready_preheader(PyTypeObject *type)
type->tp_name);
return -1;
}
type->tp_weaklistoffset = MANAGED_WEAKREF_OFFSET;
if (_PyType_IS_GC(type)) {
type->tp_weaklistoffset = MANAGED_WEAKREF_OFFSET;
}
else {
type->tp_weaklistoffset = MANAGED_WEAKREF_OFFSET_NO_GC;
}
}
return 0;
}
Expand Down
Loading