diff --git a/Doc/includes/typestruct.h b/Doc/includes/typestruct.h index 0d1d85ce9741b2..a773d3291f3118 100644 --- a/Doc/includes/typestruct.h +++ b/Doc/includes/typestruct.h @@ -92,4 +92,7 @@ typedef struct _typeobject { * Otherwise, limited to MAX_VERSIONS_PER_CLASS (defined elsewhere). */ uint16_t tp_versions_used; + + /* call function for all referenced objects (includes non-cyclic refs) */ + traverseproc tp_reachable; } PyTypeObject; diff --git a/Include/cpython/object.h b/Include/cpython/object.h index 9ad33af3d69a23..7f62f79fb421a4 100644 --- a/Include/cpython/object.h +++ b/Include/cpython/object.h @@ -239,6 +239,9 @@ struct _typeobject { * Otherwise, limited to MAX_VERSIONS_PER_CLASS (defined elsewhere). */ uint16_t tp_versions_used; + + /* call function for all referenced objects (includes non-cyclic refs) */ + traverseproc tp_reachable; }; #define _Py_ATTR_CACHE_UNUSED (30000) // (see tp_versions_used) diff --git a/Include/typeslots.h b/Include/typeslots.h index a7f3017ec02e92..53e8527ad992a7 100644 --- a/Include/typeslots.h +++ b/Include/typeslots.h @@ -94,3 +94,7 @@ /* New in 3.14 */ #define Py_tp_token 83 #endif +#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x030F0000 +/* New in 3.15 */ +#define Py_tp_reachable 84 +#endif diff --git a/Objects/dictobject.c b/Objects/dictobject.c index 6b55f3edbd913b..60c7a54680502f 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -4748,6 +4748,54 @@ dict_traverse(PyObject *op, visitproc visit, void *arg) return 0; } +static int +dict_reachable(PyObject *op, visitproc visit, void *arg) +{ + PyDictObject *mp = (PyDictObject *)op; + PyDictKeysObject *keys = mp->ma_keys; + Py_ssize_t n = keys->dk_nentries; + + Py_VISIT(_PyObject_CAST(Py_TYPE(op))); + + if (DK_IS_UNICODE(keys)) { + PyDictUnicodeEntry *entries = DK_UNICODE_ENTRIES(keys); + if (_PyDict_HasSplitTable(mp)) { + PyObject **values = mp->ma_values->values; + for (Py_ssize_t i = 0; i < n; i++) { + PyObject *value = values[i]; + if (value == NULL) { + continue; + } + Py_VISIT(entries[i].me_key); + Py_VISIT(value); + } + } + else { + for (Py_ssize_t i = 0; i < n; i++) { + PyObject *value = entries[i].me_value; + if (value == NULL) { + continue; + } + Py_VISIT(entries[i].me_key); + Py_VISIT(value); + } + } + } + else { + PyDictKeyEntry *entries = DK_ENTRIES(keys); + for (Py_ssize_t i = 0; i < n; i++) { + PyObject *value = entries[i].me_value; + if (value == NULL) { + continue; + } + Py_VISIT(entries[i].me_key); + Py_VISIT(value); + } + } + + return 0; +} + static int dict_tp_clear(PyObject *op) { @@ -5072,6 +5120,7 @@ PyTypeObject PyDict_Type = { PyObject_GC_Del, /* tp_free */ .tp_vectorcall = dict_vectorcall, .tp_version_tag = _Py_TYPE_VERSION_DICT, + .tp_reachable = dict_reachable, }; /* For backward compatibility with old dictionary interface */ diff --git a/Objects/listobject.c b/Objects/listobject.c index a52eb6e0bb5a1e..10208026981717 100644 --- a/Objects/listobject.c +++ b/Objects/listobject.c @@ -3463,6 +3463,13 @@ list_traverse(PyObject *self, visitproc visit, void *arg) return 0; } +static int +list_reachable(PyObject *self, visitproc visit, void *arg) +{ + Py_VISIT(_PyObject_CAST(Py_TYPE(self))); + return list_traverse(self, visit, arg); +} + static PyObject * list_richcompare_impl(PyObject *v, PyObject *w, int op) { @@ -3985,6 +3992,7 @@ PyTypeObject PyList_Type = { PyObject_GC_Del, /* tp_free */ .tp_vectorcall = list_vectorcall, .tp_version_tag = _Py_TYPE_VERSION_LIST, + .tp_reachable = list_reachable, }; /*********************** List Iterator **************************/ diff --git a/Objects/tupleobject.c b/Objects/tupleobject.c index 2cd8903e493752..e0ac16ddc08a44 100644 --- a/Objects/tupleobject.c +++ b/Objects/tupleobject.c @@ -632,6 +632,13 @@ tuple_traverse(PyObject *self, visitproc visit, void *arg) return 0; } +static int +tuple_reachable(PyObject *self, visitproc visit, void *arg) +{ + Py_VISIT(_PyObject_CAST(Py_TYPE(self))); + return tuple_traverse(self, visit, arg); +} + static PyObject * tuple_richcompare(PyObject *v, PyObject *w, int op) { @@ -911,6 +918,7 @@ PyTypeObject PyTuple_Type = { PyObject_GC_Del, /* tp_free */ .tp_vectorcall = tuple_vectorcall, .tp_version_tag = _Py_TYPE_VERSION_TUPLE, + .tp_reachable = tuple_reachable, }; /* The following function breaks the notion that tuples are immutable: diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 0bdb672056d02b..95a89f9d68556a 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -214,6 +214,9 @@ slot_tp_setattro(PyObject *self, PyObject *name, PyObject *value); static PyObject * slot_tp_call(PyObject *self, PyObject *args, PyObject *kwds); +static int +type_reachable(PyObject *self, visitproc visit, void *arg); + static inline PyTypeObject * type_from_ref(PyObject *ref) { @@ -6944,6 +6947,9 @@ PyDoc_STRVAR(type_doc, "type(object) -> the object's type\n" "type(name, bases, dict, **kwds) -> a new type"); +static int +object_reachable(PyObject *self, visitproc visit, void *arg); + static int type_traverse(PyObject *self, visitproc visit, void *arg) { @@ -6975,6 +6981,30 @@ type_traverse(PyObject *self, visitproc visit, void *arg) return 0; } +static int +type_reachable(PyObject *self, visitproc visit, void *arg) +{ + PyTypeObject *type = PyTypeObject_CAST(self); + + Py_VISIT(lookup_tp_dict(type)); + Py_VISIT(type->tp_cache); + Py_VISIT(lookup_tp_mro(type)); + Py_VISIT(lookup_tp_bases(type)); + Py_VISIT(type->tp_base); + Py_VISIT(lookup_tp_subclasses(type)); + Py_VISIT(type->tp_weaklist); + + if (type->tp_flags & Py_TPFLAGS_HEAPTYPE) { + PyHeapTypeObject *ht = (PyHeapTypeObject *)type; + Py_VISIT(ht->ht_module); + Py_VISIT(ht->ht_name); + Py_VISIT(ht->ht_qualname); + Py_VISIT(ht->ht_slots); + } + + return 0; +} + static int type_clear(PyObject *self) { @@ -7079,6 +7109,7 @@ PyTypeObject PyType_Type = { PyObject_GC_Del, /* tp_free */ type_is_gc, /* tp_is_gc */ .tp_vectorcall = type_vectorcall, + .tp_reachable = type_reachable, }; @@ -8338,8 +8369,22 @@ PyTypeObject PyBaseObject_Type = { PyType_GenericAlloc, /* tp_alloc */ object_new, /* tp_new */ PyObject_Free, /* tp_free */ + .tp_reachable = object_reachable, }; +static int +object_reachable(PyObject *self, visitproc visit, void *arg) +{ + Py_VISIT(_PyObject_CAST(Py_TYPE(self))); + + traverseproc traverse = Py_TYPE(self)->tp_traverse; + if (traverse != NULL) { + return traverse(self, visit, arg); + } + + return 0; +} + static int type_add_method(PyTypeObject *type, PyMethodDef *meth) @@ -8503,6 +8548,10 @@ inherit_special(PyTypeObject *type, PyTypeObject *base) #undef COPYVAL + if (type->tp_reachable == NULL) { + type->tp_reachable = base->tp_reachable; + } + /* Setup fast subclass flags */ PyObject *mro = lookup_tp_mro(base); unsigned long flags = 0; diff --git a/Objects/typeslots.inc b/Objects/typeslots.inc index 642160fe0bd8bc..17af795308538d 100644 --- a/Objects/typeslots.inc +++ b/Objects/typeslots.inc @@ -82,3 +82,4 @@ {offsetof(PyAsyncMethods, am_send), offsetof(PyTypeObject, tp_as_async)}, {-1, offsetof(PyTypeObject, tp_vectorcall)}, {-1, offsetof(PyHeapTypeObject, ht_token)}, +{-1, offsetof(PyTypeObject, tp_reachable)}, diff --git a/Objects/unicodeobject.c b/Objects/unicodeobject.c index d4549b70d4dabc..b51b278397cab6 100644 --- a/Objects/unicodeobject.c +++ b/Objects/unicodeobject.c @@ -14629,6 +14629,15 @@ errors defaults to 'strict'."); static PyObject *unicode_iter(PyObject *seq); +static int +unicode_reachable(PyObject *self, visitproc visit, void *arg) +{ + // Strings do not own references to other PyObjects, but we still + // report reachability to the type object. + Py_VISIT(_PyObject_CAST(Py_TYPE(self))); + return 0; +} + PyTypeObject PyUnicode_Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0) "str", /* tp_name */ @@ -14673,6 +14682,7 @@ PyTypeObject PyUnicode_Type = { unicode_new, /* tp_new */ PyObject_Free, /* tp_free */ .tp_vectorcall = unicode_vectorcall, + .tp_reachable = unicode_reachable, }; /* Initialize the Unicode implementation */ diff --git a/Python/immutability.c b/Python/immutability.c index dce167b2df1d7f..50ee13ade2a9cc 100644 --- a/Python/immutability.c +++ b/Python/immutability.c @@ -1526,9 +1526,12 @@ int traverse_freeze(PyObject* obj, struct FreezeState* freeze_state) } else { - traverseproc traverse = Py_TYPE(obj)->tp_traverse; - if(traverse != NULL){ - SUCCEEDS(traverse(obj, (visitproc)freeze_visit, freeze_state)); + traverseproc references = Py_TYPE(obj)->tp_reachable; + if (references == NULL) { + references = Py_TYPE(obj)->tp_traverse; + } + if(references != NULL){ + SUCCEEDS(references(obj, (visitproc)freeze_visit, freeze_state)); } } @@ -1550,13 +1553,6 @@ int traverse_freeze(PyObject* obj, struct FreezeState* freeze_state) } } - // The default tp_traverse will not visit the type object if it is - // not heap allocated, so we need to do that manually here to freeze - // the statically allocated types that are reachable. - if (!(Py_TYPE(obj)->tp_flags & Py_TPFLAGS_HEAPTYPE)) { - SUCCEEDS(freeze_visit(_PyObject_CAST(Py_TYPE(obj)), freeze_state)); - } - return 0; error: @@ -1678,4 +1674,4 @@ int _PyImmutability_Freeze(PyObject* obj) deallocate_FreezeState(&freeze_state); TRACE_MERMAID_END(); return result; -} \ No newline at end of file +}