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/internal/pycore_immutability.h b/Include/internal/pycore_immutability.h index 7a7d37ce0dd07c..e8ef450410994b 100644 --- a/Include/internal/pycore_immutability.h +++ b/Include/internal/pycore_immutability.h @@ -8,11 +8,14 @@ extern "C" { # error "Py_BUILD_CORE must be defined to include this header" #endif +typedef struct _Py_hashtable_t _Py_hashtable_t; + struct _Py_immutability_state { PyObject *module_locks; PyObject *blocking_on; PyObject *freezable_types; PyObject *destroy_cb; + _Py_hashtable_t *warned_types; #ifdef Py_DEBUG PyObject *traceback_func; // For debugging purposes, can be NULL #endif 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/Lib/test/test_freeze/test_reachable_warnings.py b/Lib/test/test_freeze/test_reachable_warnings.py new file mode 100644 index 00000000000000..e6be39fefe325f --- /dev/null +++ b/Lib/test/test_freeze/test_reachable_warnings.py @@ -0,0 +1,102 @@ +"""Tests for freeze warnings when tp_reachable is missing.""" +import subprocess +import sys +import textwrap +import unittest + + +class TestReachableWarnings(unittest.TestCase): + """Test that freeze logs warnings when tp_reachable is missing.""" + + def _run_code(self, code): + """Run code in a subprocess and return (stdout, stderr).""" + result = subprocess.run( + [sys.executable, "-c", textwrap.dedent(code)], + capture_output=True, + text=True, + ) + self.assertEqual(result.returncode, 0, result.stderr) + return result.stdout, result.stderr + + def test_warn_tp_traverse_no_tp_reachable(self): + """Warn once when a type has tp_traverse but no tp_reachable.""" + stdout, stderr = self._run_code("""\ + import _immutable + + class MyClass: + def __init__(self): + self.x = 1 + + _immutable.register_freezable(MyClass) + _immutable._clear_tp_reachable(MyClass) + + obj = MyClass() + _immutable.freeze(obj) + """) + self.assertIn( + "freeze: type 'MyClass' has tp_traverse but no tp_reachable", + stderr, + ) + + def test_warn_only_once_per_type(self): + """A type should only produce the warning on the first freeze.""" + stdout, stderr = self._run_code("""\ + import _immutable + + class MyClass: + def __init__(self): + self.x = 1 + + _immutable.register_freezable(MyClass) + _immutable._clear_tp_reachable(MyClass) + + _immutable.freeze(MyClass()) + _immutable.freeze(MyClass()) + _immutable.freeze(MyClass()) + """) + count = stderr.count( + "freeze: type 'MyClass' has tp_traverse but no tp_reachable" + ) + self.assertEqual(count, 1, f"Expected 1 warning, got {count}:\n{stderr}") + + def test_warn_different_types_separately(self): + """Different types should each produce their own warning.""" + stdout, stderr = self._run_code("""\ + import _immutable + + class ClassA: + pass + + class ClassB: + pass + + _immutable.register_freezable(ClassA) + _immutable.register_freezable(ClassB) + _immutable._clear_tp_reachable(ClassA) + _immutable._clear_tp_reachable(ClassB) + + _immutable.freeze(ClassA()) + _immutable.freeze(ClassB()) + """) + self.assertIn("type 'ClassA'", stderr) + self.assertIn("type 'ClassB'", stderr) + + def test_no_warning_with_tp_reachable(self): + """No warning should appear when tp_reachable is present.""" + stdout, stderr = self._run_code("""\ + import _immutable + + class MyClass: + def __init__(self): + self.x = 1 + + _immutable.register_freezable(MyClass) + # Do NOT clear tp_reachable + _immutable.freeze(MyClass()) + """) + self.assertNotIn("tp_reachable", stderr) + self.assertNotIn("tp_traverse", stderr) + + +if __name__ == "__main__": + unittest.main() diff --git a/Modules/_immutablemodule.c b/Modules/_immutablemodule.c index 8cb949173b939b..47739fd8daafdd 100644 --- a/Modules/_immutablemodule.c +++ b/Modules/_immutablemodule.c @@ -125,6 +125,22 @@ PyType_Spec not_freezable_error_spec = { .slots = not_freezable_error_slots, }; +/* + * Test helpers + */ + +static PyObject * +immutable_clear_tp_reachable(PyObject *module, PyObject *obj) +{ + if (!PyType_Check(obj)) { + PyErr_SetString(PyExc_TypeError, "Expected a type"); + return NULL; + } + PyTypeObject *tp = (PyTypeObject *)obj; + tp->tp_reachable = NULL; + Py_RETURN_NONE; +} + /* * MODULE */ @@ -143,6 +159,8 @@ static struct PyMethodDef immutable_methods[] = { IMMUTABLE_REGISTER_FREEZABLE_METHODDEF IMMUTABLE_FREEZE_METHODDEF IMMUTABLE_ISFROZEN_METHODDEF + {"_clear_tp_reachable", immutable_clear_tp_reachable, METH_O, + "Clear tp_reachable on a type (test helper)."}, { NULL, NULL } }; diff --git a/Objects/bytearrayobject.c b/Objects/bytearrayobject.c index c519485c1cc74c..2ef2fc6c1b1f54 100644 --- a/Objects/bytearrayobject.c +++ b/Objects/bytearrayobject.c @@ -2740,6 +2740,13 @@ Construct a mutable bytearray object from:\n\ - any object implementing the buffer API.\n\ - an integer"); +static int +bytearray_reachable(PyObject *self, visitproc visit, void *arg) +{ + Py_VISIT(_PyObject_CAST(Py_TYPE(self))); + return 0; +} + static PyObject *bytearray_iter(PyObject *seq); @@ -2784,6 +2791,7 @@ PyTypeObject PyByteArray_Type = { PyType_GenericAlloc, /* tp_alloc */ PyType_GenericNew, /* tp_new */ PyObject_Free, /* tp_free */ + .tp_reachable = bytearray_reachable, .tp_version_tag = _Py_TYPE_VERSION_BYTEARRAY, }; @@ -2918,6 +2926,13 @@ static PyMethodDef bytearrayiter_methods[] = { {NULL, NULL} /* sentinel */ }; +static int +bytearrayiter_reachable(PyObject *self, visitproc visit, void *arg) +{ + Py_VISIT(_PyObject_CAST(Py_TYPE(self))); + return bytearrayiter_traverse(self, visit, arg); +} + PyTypeObject PyByteArrayIter_Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0) "bytearray_iterator", /* tp_name */ @@ -2949,6 +2964,7 @@ PyTypeObject PyByteArrayIter_Type = { bytearrayiter_next, /* tp_iternext */ bytearrayiter_methods, /* tp_methods */ 0, + .tp_reachable = bytearrayiter_reachable, }; static PyObject * diff --git a/Objects/bytesobject.c b/Objects/bytesobject.c index de8ab26db1e966..ed6d89e791a85c 100644 --- a/Objects/bytesobject.c +++ b/Objects/bytesobject.c @@ -1707,6 +1707,13 @@ bytes_subscript(PyObject *op, PyObject* item) } } +static int +bytes_reachable(PyObject *self, visitproc visit, void *arg) +{ + Py_VISIT(_PyObject_CAST(Py_TYPE(self))); + return 0; +} + static int bytes_buffer_getbuffer(PyObject *op, Py_buffer *view, int flags) { @@ -3157,6 +3164,7 @@ PyTypeObject PyBytes_Type = { bytes_alloc, /* tp_alloc */ bytes_new, /* tp_new */ PyObject_Free, /* tp_free */ + .tp_reachable = bytes_reachable, .tp_version_tag = _Py_TYPE_VERSION_BYTES, }; @@ -3399,6 +3407,13 @@ static PyMethodDef striter_methods[] = { {NULL, NULL} /* sentinel */ }; +static int +bytesiter_reachable(PyObject *self, visitproc visit, void *arg) +{ + Py_VISIT(_PyObject_CAST(Py_TYPE(self))); + return striter_traverse(self, visit, arg); +} + PyTypeObject PyBytesIter_Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0) "bytes_iterator", /* tp_name */ @@ -3430,6 +3445,7 @@ PyTypeObject PyBytesIter_Type = { striter_next, /* tp_iternext */ striter_methods, /* tp_methods */ 0, + .tp_reachable = bytesiter_reachable, }; static PyObject * diff --git a/Objects/capsule.c b/Objects/capsule.c index 16ae65905ef5ac..843fed3ff46ac4 100644 --- a/Objects/capsule.c +++ b/Objects/capsule.c @@ -328,6 +328,13 @@ capsule_traverse(PyObject *self, visitproc visit, void *arg) return 0; } +static int +capsule_reachable(PyObject *self, visitproc visit, void *arg) +{ + Py_VISIT(_PyObject_CAST(Py_TYPE(self))); + return capsule_traverse(self, visit, arg); +} + static int capsule_clear(PyObject *self) @@ -361,6 +368,7 @@ PyTypeObject PyCapsule_Type = { .tp_doc = PyCapsule_Type__doc__, .tp_traverse = capsule_traverse, .tp_clear = capsule_clear, + .tp_reachable = capsule_reachable, }; diff --git a/Objects/cellobject.c b/Objects/cellobject.c index 95d5cdcd82dd9f..4dbd05bd4f058a 100644 --- a/Objects/cellobject.c +++ b/Objects/cellobject.c @@ -135,6 +135,13 @@ cell_traverse(PyObject *self, visitproc visit, void *arg) return 0; } +static int +cell_reachable(PyObject *self, visitproc visit, void *arg) +{ + Py_VISIT(Py_TYPE(self)); + return cell_traverse(self, visit, arg); +} + static int cell_clear(PyObject *self) { @@ -218,4 +225,5 @@ PyTypeObject PyCell_Type = { 0, /* tp_alloc */ cell_new, /* tp_new */ 0, /* tp_free */ + .tp_reachable = cell_reachable, }; diff --git a/Objects/classobject.c b/Objects/classobject.c index e71f301f2efd77..a9afae07b693d8 100644 --- a/Objects/classobject.c +++ b/Objects/classobject.c @@ -335,6 +335,13 @@ method_traverse(PyObject *self, visitproc visit, void *arg) return 0; } +static int +method_reachable(PyObject *self, visitproc visit, void *arg) +{ + Py_VISIT(_PyObject_CAST(Py_TYPE(self))); + return method_traverse(self, visit, arg); +} + static PyObject * method_descr_get(PyObject *meth, PyObject *obj, PyObject *cls) { @@ -357,6 +364,7 @@ PyTypeObject PyMethod_Type = { Py_TPFLAGS_HAVE_VECTORCALL, .tp_doc = method_new__doc__, .tp_traverse = method_traverse, + .tp_reachable = method_reachable, .tp_richcompare = method_richcompare, .tp_weaklistoffset = offsetof(PyMethodObject, im_weakreflist), .tp_methods = method_methods, @@ -456,6 +464,12 @@ instancemethod_traverse(PyObject *self, visitproc visit, void *arg) { return 0; } +static int +instancemethod_reachable(PyObject *self, visitproc visit, void *arg) { + Py_VISIT(_PyObject_CAST(Py_TYPE(self))); + return instancemethod_traverse(self, visit, arg); +} + static PyObject * instancemethod_call(PyObject *self, PyObject *arg, PyObject *kw) { @@ -557,6 +571,7 @@ PyTypeObject PyInstanceMethod_Type = { .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, .tp_doc = instancemethod_new__doc__, .tp_traverse = instancemethod_traverse, + .tp_reachable = instancemethod_reachable, .tp_richcompare = instancemethod_richcompare, .tp_members = instancemethod_memberlist, .tp_getset = instancemethod_getset, diff --git a/Objects/codeobject.c b/Objects/codeobject.c index 0d264a6e346f95..23a3631ba099bc 100644 --- a/Objects/codeobject.c +++ b/Objects/codeobject.c @@ -1365,6 +1365,15 @@ lineiter_dealloc(PyObject *self) Py_TYPE(li)->tp_free(li); } +static int +lineiter_reachable(PyObject *self, visitproc visit, void *arg) +{ + lineiterator *li = (lineiterator*)self; + Py_VISIT(Py_TYPE(self)); + Py_VISIT(li->li_code); + return 0; +} + static PyObject * _source_offset_converter(void *arg) { int *value = (int*)arg; @@ -1436,6 +1445,7 @@ PyTypeObject _PyLineIterator = { 0, /* tp_alloc */ 0, /* tp_new */ PyObject_Free, /* tp_free */ + .tp_reachable = lineiter_reachable, }; static lineiterator * @@ -1469,6 +1479,15 @@ positionsiter_dealloc(PyObject *self) Py_TYPE(pi)->tp_free(pi); } +static int +positionsiter_reachable(PyObject *self, visitproc visit, void *arg) +{ + positionsiterator *pi = (positionsiterator*)self; + Py_VISIT(Py_TYPE(self)); + Py_VISIT(pi->pi_code); + return 0; +} + static PyObject* positionsiter_next(PyObject *self) { @@ -1529,6 +1548,7 @@ PyTypeObject _PyPositionsIterator = { 0, /* tp_alloc */ 0, /* tp_new */ PyObject_Free, /* tp_free */ + .tp_reachable = positionsiter_reachable, }; static PyObject* @@ -2465,6 +2485,29 @@ code_traverse(PyObject *self, visitproc visit, void *arg) } #endif +static int +code_reachable(PyObject *self, visitproc visit, void *arg) +{ + PyCodeObject *co = _PyCodeObject_CAST(self); + Py_VISIT(Py_TYPE(self)); + Py_VISIT(co->co_consts); + Py_VISIT(co->co_names); + Py_VISIT(co->co_exceptiontable); + Py_VISIT(co->co_localsplusnames); + Py_VISIT(co->co_localspluskinds); + Py_VISIT(co->co_filename); + Py_VISIT(co->co_name); + Py_VISIT(co->co_qualname); + Py_VISIT(co->co_linetable); + if (co->_co_cached != NULL) { + Py_VISIT(co->_co_cached->_co_code); + Py_VISIT(co->_co_cached->_co_varnames); + Py_VISIT(co->_co_cached->_co_cellvars); + Py_VISIT(co->_co_cached->_co_freevars); + } + return 0; +} + static PyObject * code_repr(PyObject *self) { @@ -2922,6 +2965,7 @@ PyTypeObject PyCode_Type = { 0, /* tp_init */ 0, /* tp_alloc */ code_new, /* tp_new */ + .tp_reachable = code_reachable, }; diff --git a/Objects/descrobject.c b/Objects/descrobject.c index 5ac4fbd812924c..7a93e1ca26127a 100644 --- a/Objects/descrobject.c +++ b/Objects/descrobject.c @@ -714,6 +714,13 @@ descr_traverse(PyObject *self, visitproc visit, void *arg) return 0; } +static int +descr_reachable(PyObject *self, visitproc visit, void *arg) +{ + Py_VISIT(_PyObject_CAST(Py_TYPE(self))); + return descr_traverse(self, visit, arg); +} + PyTypeObject PyMethodDescr_Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0) "method_descriptor", @@ -751,6 +758,7 @@ PyTypeObject PyMethodDescr_Type = { 0, /* tp_dict */ method_get, /* tp_descr_get */ 0, /* tp_descr_set */ + .tp_reachable = descr_reachable, }; /* This is for METH_CLASS in C, not for "f = classmethod(f)" in Python! */ @@ -789,6 +797,7 @@ PyTypeObject PyClassMethodDescr_Type = { 0, /* tp_dict */ classmethod_get, /* tp_descr_get */ 0, /* tp_descr_set */ + .tp_reachable = descr_reachable, }; PyTypeObject PyMemberDescr_Type = { @@ -826,6 +835,7 @@ PyTypeObject PyMemberDescr_Type = { 0, /* tp_dict */ member_get, /* tp_descr_get */ member_set, /* tp_descr_set */ + .tp_reachable = descr_reachable, }; PyTypeObject PyGetSetDescr_Type = { @@ -863,6 +873,7 @@ PyTypeObject PyGetSetDescr_Type = { 0, /* tp_dict */ getset_get, /* tp_descr_get */ getset_set, /* tp_descr_set */ + .tp_reachable = descr_reachable, }; PyTypeObject PyWrapperDescr_Type = { @@ -901,6 +912,7 @@ PyTypeObject PyWrapperDescr_Type = { 0, /* tp_dict */ wrapperdescr_get, /* tp_descr_get */ 0, /* tp_descr_set */ + .tp_reachable = descr_reachable, }; static PyDescrObject * @@ -1229,6 +1241,13 @@ mappingproxy_traverse(PyObject *self, visitproc visit, void *arg) return 0; } +static int +mappingproxy_reachable(PyObject *self, visitproc visit, void *arg) +{ + Py_VISIT(_PyObject_CAST(Py_TYPE(self))); + return mappingproxy_traverse(self, visit, arg); +} + static PyObject * mappingproxy_richcompare(PyObject *self, PyObject *w, int op) { @@ -1449,6 +1468,13 @@ wrapper_traverse(PyObject *self, visitproc visit, void *arg) return 0; } +static int +wrapper_reachable(PyObject *self, visitproc visit, void *arg) +{ + Py_VISIT(_PyObject_CAST(Py_TYPE(self))); + return wrapper_traverse(self, visit, arg); +} + PyTypeObject _PyMethodWrapper_Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0) "method-wrapper", /* tp_name */ @@ -1485,6 +1511,7 @@ PyTypeObject _PyMethodWrapper_Type = { 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ + .tp_reachable = wrapper_reachable, }; PyObject * @@ -1987,6 +2014,13 @@ property_traverse(PyObject *self, visitproc visit, void *arg) return 0; } +static int +property_reachable(PyObject *self, visitproc visit, void *arg) +{ + Py_VISIT(_PyObject_CAST(Py_TYPE(self))); + return property_traverse(self, visit, arg); +} + static int property_clear(PyObject *self) { @@ -2038,6 +2072,7 @@ PyTypeObject PyDictProxy_Type = { 0, /* tp_init */ 0, /* tp_alloc */ mappingproxy_new, /* tp_new */ + .tp_reachable = mappingproxy_reachable, }; PyTypeObject PyProperty_Type = { @@ -2082,4 +2117,5 @@ PyTypeObject PyProperty_Type = { PyType_GenericAlloc, /* tp_alloc */ PyType_GenericNew, /* tp_new */ PyObject_GC_Del, /* tp_free */ + .tp_reachable = property_reachable, }; diff --git a/Objects/dictobject.c b/Objects/dictobject.c index 6b55f3edbd913b..5dce83f90a0731 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 */ @@ -5226,6 +5275,13 @@ dictiter_traverse(PyObject *self, visitproc visit, void *arg) return 0; } +static int +dictiter_reachable(PyObject *self, visitproc visit, void *arg) +{ + Py_VISIT(_PyObject_CAST(Py_TYPE(self))); + return dictiter_traverse(self, visit, arg); +} + static PyObject * dictiter_len(PyObject *self, PyObject *Py_UNUSED(ignored)) { @@ -5381,6 +5437,7 @@ PyTypeObject PyDictIterKey_Type = { dictiter_iternextkey, /* tp_iternext */ dictiter_methods, /* tp_methods */ 0, + .tp_reachable = dictiter_reachable, }; #ifndef Py_GIL_DISABLED @@ -5504,6 +5561,7 @@ PyTypeObject PyDictIterValue_Type = { dictiter_iternextvalue, /* tp_iternext */ dictiter_methods, /* tp_methods */ 0, + .tp_reachable = dictiter_reachable, }; static int @@ -5813,6 +5871,7 @@ PyTypeObject PyDictIterItem_Type = { dictiter_iternextitem, /* tp_iternext */ dictiter_methods, /* tp_methods */ 0, + .tp_reachable = dictiter_reachable, }; @@ -5939,7 +5998,8 @@ PyTypeObject PyDictRevIterKey_Type = { .tp_traverse = dictiter_traverse, .tp_iter = PyObject_SelfIter, .tp_iternext = dictreviter_iternext, - .tp_methods = dictiter_methods + .tp_methods = dictiter_methods, + .tp_reachable = dictiter_reachable, }; @@ -5981,7 +6041,8 @@ PyTypeObject PyDictRevIterItem_Type = { .tp_traverse = dictiter_traverse, .tp_iter = PyObject_SelfIter, .tp_iternext = dictreviter_iternext, - .tp_methods = dictiter_methods + .tp_methods = dictiter_methods, + .tp_reachable = dictiter_reachable, }; PyTypeObject PyDictRevIterValue_Type = { @@ -5993,7 +6054,8 @@ PyTypeObject PyDictRevIterValue_Type = { .tp_traverse = dictiter_traverse, .tp_iter = PyObject_SelfIter, .tp_iternext = dictreviter_iternext, - .tp_methods = dictiter_methods + .tp_methods = dictiter_methods, + .tp_reachable = dictiter_reachable, }; /***********************************************/ @@ -6020,6 +6082,13 @@ dictview_traverse(PyObject *self, visitproc visit, void *arg) return 0; } +static int +dictview_reachable(PyObject *self, visitproc visit, void *arg) +{ + Py_VISIT(_PyObject_CAST(Py_TYPE(self))); + return dictview_traverse(self, visit, arg); +} + static Py_ssize_t dictview_len(PyObject *self) { @@ -6596,6 +6665,7 @@ PyTypeObject PyDictKeys_Type = { 0, /* tp_iternext */ dictkeys_methods, /* tp_methods */ .tp_getset = dictview_getset, + .tp_reachable = dictview_reachable, }; /*[clinic input] @@ -6708,6 +6778,7 @@ PyTypeObject PyDictItems_Type = { 0, /* tp_iternext */ dictitems_methods, /* tp_methods */ .tp_getset = dictview_getset, + .tp_reachable = dictview_reachable, }; /*[clinic input] @@ -6798,6 +6869,7 @@ PyTypeObject PyDictValues_Type = { 0, /* tp_iternext */ dictvalues_methods, /* tp_methods */ .tp_getset = dictview_getset, + .tp_reachable = dictview_reachable, }; /*[clinic input] diff --git a/Objects/enumobject.c b/Objects/enumobject.c index 814ce4f919514b..97e7dacc6ded74 100644 --- a/Objects/enumobject.c +++ b/Objects/enumobject.c @@ -173,6 +173,13 @@ enum_traverse(PyObject *op, visitproc visit, void *arg) return 0; } +static int +enum_reachable(PyObject *op, visitproc visit, void *arg) +{ + Py_VISIT(_PyObject_CAST(Py_TYPE(op))); + return enum_traverse(op, visit, arg); +} + // increment en_longindex with lock held, return the next index to be used // or NULL on error static inline PyObject * @@ -349,6 +356,7 @@ PyTypeObject PyEnum_Type = { 0, /* tp_init */ PyType_GenericAlloc, /* tp_alloc */ enum_new, /* tp_new */ + .tp_reachable = enum_reachable, PyObject_GC_Del, /* tp_free */ .tp_vectorcall = enumerate_vectorcall }; @@ -450,6 +458,13 @@ reversed_traverse(PyObject *op, visitproc visit, void *arg) return 0; } +static int +reversed_reachable(PyObject *op, visitproc visit, void *arg) +{ + Py_VISIT(_PyObject_CAST(Py_TYPE(op))); + return reversed_traverse(op, visit, arg); +} + static PyObject * reversed_next(PyObject *op) { @@ -582,4 +597,5 @@ PyTypeObject PyReversed_Type = { reversed_new, /* tp_new */ PyObject_GC_Del, /* tp_free */ .tp_vectorcall = reversed_vectorcall, + .tp_reachable = reversed_reachable, }; diff --git a/Objects/exceptions.c b/Objects/exceptions.c index 244d8f39e2bae5..6efa5db11c5eb2 100644 --- a/Objects/exceptions.c +++ b/Objects/exceptions.c @@ -166,6 +166,13 @@ BaseException_traverse(PyObject *op, visitproc visit, void *arg) return 0; } +static int +BaseException_reachable(PyObject *op, visitproc visit, void *arg) +{ + Py_VISIT(_PyObject_CAST(Py_TYPE(op))); + return BaseException_traverse(op, visit, arg); +} + static PyObject * BaseException_str(PyObject *op) { @@ -649,6 +656,22 @@ static PyTypeObject _PyExc_BaseException = { 0, /* tp_alloc */ BaseException_new, /* tp_new */ .tp_vectorcall = BaseException_vectorcall, + .tp_reachable = BaseException_reachable, + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + BaseException_methods, /* tp_methods */ + BaseException_members, /* tp_members */ + BaseException_getset, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + offsetof(PyBaseExceptionObject, dict), /* tp_dictoffset */ + BaseException_init, /* tp_init */ + 0, /* tp_alloc */ + BaseException_new, /* tp_new */ + .tp_vectorcall = BaseException_vectorcall, }; /* the CPython API expects exceptions to be (PyObject *) - both a hold-over from the previous implementation and also allowing Python objects to be used @@ -1805,6 +1828,13 @@ ImportError_traverse(PyObject *op, visitproc visit, void *arg) return BaseException_traverse(op, visit, arg); } +static int +ImportError_reachable(PyObject *op, visitproc visit, void *arg) +{ + Py_VISIT(_PyObject_CAST(Py_TYPE(op))); + return ImportError_traverse(op, visit, arg); +} + static PyObject * ImportError_str(PyObject *op) { @@ -1949,6 +1979,7 @@ static PyTypeObject _PyExc_ImportError = { "or can't find name in module."), .tp_traverse = ImportError_traverse, .tp_clear = ImportError_clear, + .tp_reachable = ImportError_reachable, .tp_methods = ImportError_methods, .tp_members = ImportError_members, .tp_base = &_PyExc_Exception, diff --git a/Objects/frameobject.c b/Objects/frameobject.c index 0cae3703d1d0c6..266026e5635995 100644 --- a/Objects/frameobject.c +++ b/Objects/frameobject.c @@ -463,6 +463,13 @@ framelocalsproxy_visit(PyObject *self, visitproc visit, void *arg) return 0; } +static int +framelocalsproxy_reachable(PyObject *self, visitproc visit, void *arg) +{ + Py_VISIT(_PyObject_CAST(Py_TYPE(self))); + return framelocalsproxy_visit(self, visit, arg); +} + static PyObject * framelocalsproxy_iter(PyObject *self) { @@ -936,6 +943,7 @@ PyTypeObject PyFrameLocalsProxy_Type = { .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_MAPPING, .tp_traverse = framelocalsproxy_visit, .tp_clear = framelocalsproxy_tp_clear, + .tp_reachable = framelocalsproxy_reachable, .tp_richcompare = framelocalsproxy_richcompare, .tp_iter = framelocalsproxy_iter, .tp_methods = framelocalsproxy_methods, @@ -2073,6 +2081,13 @@ static PyMethodDef frame_methods[] = { {NULL, NULL} /* sentinel */ }; +static int +frame_reachable(PyObject *self, visitproc visit, void *arg) +{ + Py_VISIT(_PyObject_CAST(Py_TYPE(self))); + return frame_traverse(self, visit, arg); +} + PyTypeObject PyFrame_Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0) "frame", @@ -2107,6 +2122,7 @@ PyTypeObject PyFrame_Type = { frame_getsetlist, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ + .tp_reachable = frame_reachable, }; static void diff --git a/Objects/funcobject.c b/Objects/funcobject.c index b233f61bccaebd..4679bac41cce31 100644 --- a/Objects/funcobject.c +++ b/Objects/funcobject.c @@ -1205,6 +1205,13 @@ func_descr_get(PyObject *func, PyObject *obj, PyObject *type) return PyMethod_New(func, obj); } +static int +func_reachable(PyObject *self, visitproc visit, void *arg) +{ + Py_VISIT(_PyObject_CAST(Py_TYPE(self))); + return func_traverse(self, visit, arg); +} + PyTypeObject PyFunction_Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0) "function", @@ -1246,6 +1253,7 @@ PyTypeObject PyFunction_Type = { 0, /* tp_init */ 0, /* tp_alloc */ func_new, /* tp_new */ + .tp_reachable = func_reachable, }; diff --git a/Objects/genericaliasobject.c b/Objects/genericaliasobject.c index 8b526f43f1e053..cd43f30815f737 100644 --- a/Objects/genericaliasobject.c +++ b/Objects/genericaliasobject.c @@ -51,6 +51,13 @@ ga_traverse(PyObject *self, visitproc visit, void *arg) return 0; } +static int +ga_reachable(PyObject *self, visitproc visit, void *arg) +{ + Py_VISIT(_PyObject_CAST(Py_TYPE(self))); + return ga_traverse(self, visit, arg); +} + static int ga_repr_items_list(PyUnicodeWriter *writer, PyObject *p) { @@ -949,6 +956,13 @@ ga_iter_traverse(PyObject *op, visitproc visit, void *arg) return 0; } +static int +ga_iter_reachable(PyObject *self, visitproc visit, void *arg) +{ + Py_VISIT(_PyObject_CAST(Py_TYPE(self))); + return ga_iter_traverse(self, visit, arg); +} + static int ga_iter_clear(PyObject *self) { @@ -987,6 +1001,7 @@ PyTypeObject _Py_GenericAliasIterType = { .tp_iter = PyObject_SelfIter, .tp_iternext = ga_iternext, .tp_traverse = ga_iter_traverse, + .tp_reachable = ga_iter_reachable, .tp_methods = ga_iter_methods, .tp_dealloc = ga_iter_dealloc, .tp_clear = ga_iter_clear, @@ -1021,6 +1036,7 @@ PyTypeObject Py_GenericAliasType = { .tp_getattro = ga_getattro, .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_VECTORCALL, .tp_traverse = ga_traverse, + .tp_reachable = ga_reachable, .tp_richcompare = ga_richcompare, .tp_weaklistoffset = offsetof(gaobject, weakreflist), .tp_methods = ga_methods, diff --git a/Objects/genobject.c b/Objects/genobject.c index c9ca2f1de51ddc..41d10ae35e2a25 100644 --- a/Objects/genobject.c +++ b/Objects/genobject.c @@ -77,12 +77,24 @@ gen_traverse(PyObject *self, visitproc visit, void *arg) // ensure that it's kept alive if the reference is deferred. _Py_VISIT_STACKREF(gen->gi_iframe.f_executable); } - /* No need to visit cr_origin, because it's just tuples/str/int, so can't - participate in a reference cycle. */ + /* No need to visit gi_exc_state.exc_value for cycles with tuples/str/int. + cr_origin (on coroutines sharing this traversal) is also just + tuples/str/int, so can't participate in a reference cycle. */ Py_VISIT(gen->gi_exc_state.exc_value); return 0; } +static int +gen_reachable(PyObject *self, visitproc visit, void *arg) +{ + PyGenObject *gen = _PyGen_CAST(self); + Py_VISIT(_PyObject_CAST(Py_TYPE(self))); + /* Visit cr_origin which gen_traverse skips because it only contains + tuples/str/int that can't participate in reference cycles. */ + Py_VISIT(gen->gi_origin_or_finalizer); + return gen_traverse(self, visit, arg); +} + void _PyGen_Finalize(PyObject *self) { @@ -917,6 +929,7 @@ PyTypeObject PyGen_Type = { 0, /* tp_del */ 0, /* tp_version_tag */ _PyGen_Finalize, /* tp_finalize */ + .tp_reachable = gen_reachable, }; static PyObject * @@ -1225,6 +1238,28 @@ static PyAsyncMethods coro_as_async = { PyGen_am_send, /* am_send */ }; +static int coro_traverse(PyObject *, visitproc, void *); + +static int +coro_reachable(PyObject *self, visitproc visit, void *arg) +{ + PyGenObject *gen = _PyGen_CAST(self); + Py_VISIT(_PyObject_CAST(Py_TYPE(self))); + /* Visit cr_origin_or_finalizer which coro_traverse skips because it only + contains tuples/str/int that can't participate in reference cycles. */ + Py_VISIT(gen->gi_origin_or_finalizer); + return coro_traverse(self, visit, arg); +} + +/* Coroutine traversal. Currently identical to gen_traverse, but kept + separate so that coro_reachable stays correct if PyCoro_Type ever + gains its own tp_traverse (e.g. to visit cr_origin_or_finalizer). */ +static int +coro_traverse(PyObject *self, visitproc visit, void *arg) +{ + return gen_traverse(self, visit, arg); +} + PyTypeObject PyCoro_Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0) "coroutine", /* tp_name */ @@ -1248,7 +1283,7 @@ PyTypeObject PyCoro_Type = { 0, /* tp_as_buffer */ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, /* tp_flags */ 0, /* tp_doc */ - gen_traverse, /* tp_traverse */ + coro_traverse, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ offsetof(PyCoroObject, cr_weakreflist), /* tp_weaklistoffset */ @@ -1275,6 +1310,7 @@ PyTypeObject PyCoro_Type = { 0, /* tp_del */ 0, /* tp_version_tag */ _PyGen_Finalize, /* tp_finalize */ + .tp_reachable = coro_reachable, }; static void @@ -1490,6 +1526,13 @@ async_gen_traverse(PyObject *self, visitproc visit, void *arg) return gen_traverse((PyObject*)ag, visit, arg); } +static int +async_gen_reachable(PyObject *self, visitproc visit, void *arg) +{ + Py_VISIT(_PyObject_CAST(Py_TYPE(self))); + return async_gen_traverse(self, visit, arg); +} + static PyObject * async_gen_repr(PyObject *self) @@ -1711,6 +1754,7 @@ PyTypeObject PyAsyncGen_Type = { 0, /* tp_del */ 0, /* tp_version_tag */ _PyGen_Finalize, /* tp_finalize */ + .tp_reachable = async_gen_reachable, }; diff --git a/Objects/interpolationobject.c b/Objects/interpolationobject.c index b58adb693f0cae..e93c4b730d3c2c 100644 --- a/Objects/interpolationobject.c +++ b/Objects/interpolationobject.c @@ -108,6 +108,13 @@ interpolation_traverse(PyObject *op, visitproc visit, void *arg) return 0; } +static int +interpolation_reachable(PyObject *self, visitproc visit, void *arg) +{ + Py_VISIT(_PyObject_CAST(Py_TYPE(self))); + return interpolation_traverse(self, visit, arg); +} + static PyObject * interpolation_repr(PyObject *op) { @@ -158,6 +165,7 @@ PyTypeObject _PyInterpolation_Type = { .tp_members = interpolation_members, .tp_methods = interpolation_methods, .tp_traverse = interpolation_traverse, + .tp_reachable = interpolation_reachable, }; PyStatus diff --git a/Objects/iterobject.c b/Objects/iterobject.c index e323987601d5d4..e58500aeeb5f4f 100644 --- a/Objects/iterobject.c +++ b/Objects/iterobject.c @@ -49,6 +49,13 @@ iter_traverse(PyObject *op, visitproc visit, void *arg) return 0; } +static int +iter_reachable(PyObject *op, visitproc visit, void *arg) +{ + Py_VISIT(Py_TYPE(op)); + return iter_traverse(op, visit, arg); +} + static PyObject * iter_iternext(PyObject *iterator) { @@ -179,6 +186,7 @@ PyTypeObject PySeqIter_Type = { iter_iternext, /* tp_iternext */ seqiter_methods, /* tp_methods */ 0, /* tp_members */ + .tp_reachable = iter_reachable, }; /* -------------------------------------- */ @@ -220,6 +228,13 @@ calliter_traverse(PyObject *op, visitproc visit, void *arg) return 0; } +static int +calliter_reachable(PyObject *op, visitproc visit, void *arg) +{ + Py_VISIT(Py_TYPE(op)); + return calliter_traverse(op, visit, arg); +} + static PyObject * calliter_iternext(PyObject *op) { @@ -304,6 +319,7 @@ PyTypeObject PyCallIter_Type = { PyObject_SelfIter, /* tp_iter */ calliter_iternext, /* tp_iternext */ calliter_methods, /* tp_methods */ + .tp_reachable = calliter_reachable, }; @@ -336,6 +352,13 @@ anextawaitable_traverse(PyObject *op, visitproc visit, void *arg) return 0; } +static int +anextawaitable_reachable(PyObject *op, visitproc visit, void *arg) +{ + Py_VISIT(Py_TYPE(op)); + return anextawaitable_traverse(op, visit, arg); +} + static PyObject * anextawaitable_getiter(anextawaitableobject *obj) { @@ -525,6 +548,7 @@ PyTypeObject _PyAnextAwaitable_Type = { PyObject_SelfIter, /* tp_iter */ anextawaitable_iternext, /* tp_iternext */ anextawaitable_methods, /* tp_methods */ + .tp_reachable = anextawaitable_reachable, }; PyObject * 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/memoryobject.c b/Objects/memoryobject.c index 14ceb9bd951a95..45d823a25da63f 100644 --- a/Objects/memoryobject.c +++ b/Objects/memoryobject.c @@ -143,6 +143,13 @@ mbuf_traverse(PyObject *_self, visitproc visit, void *arg) return 0; } +static int +mbuf_reachable(PyObject *_self, visitproc visit, void *arg) +{ + Py_VISIT(_PyObject_CAST(Py_TYPE(_self))); + return mbuf_traverse(_self, visit, arg); +} + static int mbuf_clear(PyObject *_self) { @@ -175,7 +182,8 @@ PyTypeObject _PyManagedBuffer_Type = { Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, /* tp_flags */ 0, /* tp_doc */ mbuf_traverse, /* tp_traverse */ - mbuf_clear /* tp_clear */ + mbuf_clear, /* tp_clear */ + .tp_reachable = mbuf_reachable, }; @@ -3554,6 +3562,20 @@ memory_iter(PyObject *seq) return (PyObject *)it; } +static int +memoryiter_reachable(PyObject *self, visitproc visit, void *arg) +{ + Py_VISIT(_PyObject_CAST(Py_TYPE(self))); + return memoryiter_traverse(self, visit, arg); +} + +static int +memory_reachable(PyObject *self, visitproc visit, void *arg) +{ + Py_VISIT(_PyObject_CAST(Py_TYPE(self))); + return memory_traverse(self, visit, arg); +} + PyTypeObject _PyMemoryIter_Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0) .tp_name = "memory_iterator", @@ -3565,6 +3587,7 @@ PyTypeObject _PyMemoryIter_Type = { .tp_traverse = memoryiter_traverse, .tp_iter = PyObject_SelfIter, .tp_iternext = memoryiter_next, + .tp_reachable = memoryiter_reachable, }; PyTypeObject PyMemoryView_Type = { @@ -3607,4 +3630,5 @@ PyTypeObject PyMemoryView_Type = { 0, /* tp_init */ 0, /* tp_alloc */ memoryview, /* tp_new */ + .tp_reachable = memory_reachable, }; diff --git a/Objects/methodobject.c b/Objects/methodobject.c index e6e469ca270ac9..51da6dd6a1d98d 100644 --- a/Objects/methodobject.c +++ b/Objects/methodobject.c @@ -272,6 +272,13 @@ meth_traverse(PyObject *self, visitproc visit, void *arg) return 0; } +static int +meth_reachable(PyObject *self, visitproc visit, void *arg) +{ + Py_VISIT(Py_TYPE(self)); + return meth_traverse(self, visit, arg); +} + static PyObject * meth_get__self__(PyObject *meth, void *closure) { @@ -387,6 +394,7 @@ PyTypeObject PyCFunction_Type = { meth_getsets, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ + .tp_reachable = meth_reachable, }; PyTypeObject PyCMethod_Type = { diff --git a/Objects/moduleobject.c b/Objects/moduleobject.c index 1f8cd8eb3b3794..f65a1c2f12fdcf 100644 --- a/Objects/moduleobject.c +++ b/Objects/moduleobject.c @@ -1547,6 +1547,13 @@ static PyGetSetDef module_getsets[] = { {NULL} }; +static int +module_reachable(PyObject *self, visitproc visit, void *arg) +{ + Py_VISIT(_PyObject_CAST(Py_TYPE(self))); + return module_traverse(self, visit, arg); +} + PyTypeObject PyModule_Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0) "module", /* tp_name */ @@ -1597,6 +1604,7 @@ PyTypeObject PyModule_Type = { 0, /* tp_alloc */ new_module, /* tp_new */ PyObject_GC_Del, /* tp_free */ + .tp_reachable = module_reachable, }; PyTypeObject PyImmModule_Type = { @@ -1642,4 +1650,5 @@ PyTypeObject PyImmModule_Type = { // TODO: Custom new for direct immutable new_module, /* tp_new */ PyObject_GC_Del, /* tp_free */ + .tp_reachable = module_reachable, }; diff --git a/Objects/namespaceobject.c b/Objects/namespaceobject.c index 201cb8a7df8da1..a80bf4b70552a2 100644 --- a/Objects/namespaceobject.c +++ b/Objects/namespaceobject.c @@ -181,6 +181,13 @@ namespace_traverse(PyObject *op, visitproc visit, void *arg) return 0; } +static int +namespace_reachable(PyObject *op, visitproc visit, void *arg) +{ + Py_VISIT(Py_TYPE(op)); + return namespace_traverse(op, visit, arg); +} + static int namespace_clear(PyObject *op) @@ -306,6 +313,7 @@ PyTypeObject _PyNamespace_Type = { PyType_GenericAlloc, /* tp_alloc */ namespace_new, /* tp_new */ PyObject_GC_Del, /* tp_free */ + .tp_reachable = namespace_reachable, }; diff --git a/Objects/odictobject.c b/Objects/odictobject.c index 9a915c5d656914..7542bdef6aca2f 100644 --- a/Objects/odictobject.c +++ b/Objects/odictobject.c @@ -1474,6 +1474,13 @@ odict_traverse(PyObject *op, visitproc visit, void *arg) return PyDict_Type.tp_traverse((PyObject *)od, visit, arg); } +static int +odict_reachable(PyObject *self, visitproc visit, void *arg) +{ + Py_VISIT(_PyObject_CAST(Py_TYPE(self))); + return odict_traverse(self, visit, arg); +} + /* tp_clear */ static int @@ -1614,6 +1621,7 @@ PyTypeObject PyODict_Type = { PyType_GenericAlloc, /* tp_alloc */ 0, /* tp_new */ 0, /* tp_free */ + .tp_reachable = odict_reachable, }; @@ -1728,6 +1736,13 @@ odictiter_traverse(PyObject *op, visitproc visit, void *arg) return 0; } +static int +odictiter_reachable(PyObject *self, visitproc visit, void *arg) +{ + Py_VISIT(_PyObject_CAST(Py_TYPE(self))); + return odictiter_traverse(self, visit, arg); +} + /* In order to protect against modifications during iteration, we track * the current key instead of the current node. */ static PyObject * @@ -1929,6 +1944,7 @@ PyTypeObject PyODictIter_Type = { odictiter_iternext, /* tp_iternext */ odictiter_methods, /* tp_methods */ 0, + .tp_reachable = odictiter_reachable, }; static PyObject * diff --git a/Objects/picklebufobject.c b/Objects/picklebufobject.c index 50f17687bc4365..d1e5ac848117f2 100644 --- a/Objects/picklebufobject.c +++ b/Objects/picklebufobject.c @@ -99,6 +99,16 @@ picklebuf_traverse(PyObject *op, visitproc visit, void *arg) return 0; } +static int +picklebuf_reachable(PyObject *self, visitproc visit, void *arg) +{ + Py_VISIT(_PyObject_CAST(Py_TYPE(self))); + // TODO: weakreflist. I don't think we should follow this, but we + // might need to handle separate threads trying to remove weak references + // from this object? + return picklebuf_traverse(self, visit, arg); +} + static int picklebuf_clear(PyObject *op) { @@ -217,6 +227,7 @@ PyTypeObject PyPickleBuffer_Type = { .tp_new = picklebuf_new, .tp_dealloc = picklebuf_dealloc, .tp_traverse = picklebuf_traverse, + .tp_reachable = picklebuf_reachable, .tp_clear = picklebuf_clear, .tp_weaklistoffset = offsetof(PyPickleBufferObject, weakreflist), .tp_as_buffer = &picklebuf_as_buffer, diff --git a/Objects/rangeobject.c b/Objects/rangeobject.c index f8cdfe68a6435e..c8d24812bd6005 100644 --- a/Objects/rangeobject.c +++ b/Objects/rangeobject.c @@ -778,6 +778,20 @@ static PyMemberDef range_members[] = { {0} }; +static int +range_reachable(PyObject *self, visitproc visit, void *arg) +{ + rangeobject *r = (rangeobject *)self; + + Py_VISIT(_PyObject_CAST(Py_TYPE(self))); + Py_VISIT(r->start); + Py_VISIT(r->stop); + Py_VISIT(r->step); + Py_VISIT(r->length); + + return 0; +} + PyTypeObject PyRange_Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0) "range", /* Name of this type */ @@ -817,6 +831,7 @@ PyTypeObject PyRange_Type = { 0, /* tp_init */ 0, /* tp_alloc */ range_new, /* tp_new */ + .tp_reachable = range_reachable, .tp_vectorcall = range_vectorcall }; diff --git a/Objects/setobject.c b/Objects/setobject.c index faf3bd34da8588..c551939bd4324e 100644 --- a/Objects/setobject.c +++ b/Objects/setobject.c @@ -2589,6 +2589,13 @@ PyDoc_STRVAR(set_doc, \n\ Build an unordered collection of unique elements."); +static int +set_reachable(PyObject *self, visitproc visit, void *arg) +{ + Py_VISIT(_PyObject_CAST(Py_TYPE(self))); + return set_traverse(self, visit, arg); +} + PyTypeObject PySet_Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0) "set", /* tp_name */ @@ -2633,6 +2640,7 @@ PyTypeObject PySet_Type = { set_new, /* tp_new */ PyObject_GC_Del, /* tp_free */ .tp_vectorcall = set_vectorcall, + .tp_reachable = set_reachable, .tp_version_tag = _Py_TYPE_VERSION_SET, }; @@ -2724,6 +2732,7 @@ PyTypeObject PyFrozenSet_Type = { frozenset_new, /* tp_new */ PyObject_GC_Del, /* tp_free */ .tp_vectorcall = frozenset_vectorcall, + .tp_reachable = set_reachable, .tp_version_tag = _Py_TYPE_VERSION_FROZEN_SET, }; diff --git a/Objects/sliceobject.c b/Objects/sliceobject.c index 5186ff4f6f0cf5..117831c0029d06 100644 --- a/Objects/sliceobject.c +++ b/Objects/sliceobject.c @@ -666,6 +666,13 @@ slice_hash(PyObject *op) return acc; } +static int +slice_reachable(PyObject *self, visitproc visit, void *arg) +{ + Py_VISIT(_PyObject_CAST(Py_TYPE(self))); + return slice_traverse(self, visit, arg); +} + PyTypeObject PySlice_Type = { PyVarObject_HEAD_INIT(&PyType_Type, 0) "slice", /* Name of this type */ @@ -705,4 +712,5 @@ PyTypeObject PySlice_Type = { 0, /* tp_init */ 0, /* tp_alloc */ slice_new, /* tp_new */ + .tp_reachable = slice_reachable, }; diff --git a/Objects/structseq.c b/Objects/structseq.c index 7a159338b9ba8a..e08064ce71abaa 100644 --- a/Objects/structseq.c +++ b/Objects/structseq.c @@ -124,6 +124,21 @@ structseq_traverse(PyObject *op, visitproc visit, void *arg) return 0; } +static int +structseq_reachable(PyObject *op, visitproc visit, void *arg) +{ + /* Always visit the type, unlike traverse which only visits for heap types */ + Py_VISIT(_PyObject_CAST(Py_TYPE(op))); + + PyStructSequence *obj = (PyStructSequence *)op; + Py_ssize_t i, size; + size = REAL_SIZE(obj); + for (i = 0; i < size; ++i) { + Py_VISIT(obj->ob_item[i]); + } + return 0; +} + static void structseq_dealloc(PyObject *op) { @@ -583,6 +598,7 @@ initialize_static_fields(PyTypeObject *type, PyStructSequence_Desc *desc, type->tp_new = structseq_new; type->tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | tp_flags; type->tp_traverse = structseq_traverse; + type->tp_reachable = structseq_reachable; type->tp_members = tp_members; } @@ -740,7 +756,7 @@ _PyStructSequence_NewType(PyStructSequence_Desc *desc, unsigned long tp_flags) { PyMemberDef *members; PyTypeObject *type; - PyType_Slot slots[8]; + PyType_Slot slots[9]; PyType_Spec spec; Py_ssize_t n_members, n_unnamed_members; @@ -759,7 +775,8 @@ _PyStructSequence_NewType(PyStructSequence_Desc *desc, unsigned long tp_flags) slots[4] = (PyType_Slot){Py_tp_new, structseq_new}; slots[5] = (PyType_Slot){Py_tp_members, members}; slots[6] = (PyType_Slot){Py_tp_traverse, structseq_traverse}; - slots[7] = (PyType_Slot){0, 0}; + slots[7] = (PyType_Slot){Py_tp_reachable, structseq_reachable}; + slots[8] = (PyType_Slot){0, 0}; /* Initialize Spec */ /* The name in this PyType_Spec is statically allocated so it is */ diff --git a/Objects/templateobject.c b/Objects/templateobject.c index ac38e4de435d5d..6721efdfd0a1a7 100644 --- a/Objects/templateobject.c +++ b/Objects/templateobject.c @@ -64,6 +64,13 @@ templateiter_traverse(PyObject *op, visitproc visit, void *arg) return 0; } +static int +templateiter_reachable(PyObject *self, visitproc visit, void *arg) +{ + Py_VISIT(_PyObject_CAST(Py_TYPE(self))); + return templateiter_traverse(self, visit, arg); +} + PyTypeObject _PyTemplateIter_Type = { PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "string.templatelib.TemplateIter", @@ -76,6 +83,7 @@ PyTypeObject _PyTemplateIter_Type = { .tp_clear = templateiter_clear, .tp_free = PyObject_GC_Del, .tp_traverse = templateiter_traverse, + .tp_reachable = templateiter_reachable, .tp_iter = PyObject_SelfIter, .tp_iternext = templateiter_next, }; @@ -207,6 +215,13 @@ template_traverse(PyObject *op, visitproc visit, void *arg) return 0; } +static int +template_reachable(PyObject *self, visitproc visit, void *arg) +{ + Py_VISIT(_PyObject_CAST(Py_TYPE(self))); + return template_traverse(self, visit, arg); +} + static PyObject * template_repr(PyObject *op) { @@ -394,6 +409,7 @@ PyTypeObject _PyTemplate_Type = { .tp_getset = template_getset, .tp_iter = template_iter, .tp_traverse = template_traverse, + .tp_reachable = template_reachable, }; PyObject * 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..cc0f394df0acf2 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -214,6 +214,12 @@ 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 int +object_reachable(PyObject *self, visitproc visit, void *arg); + static inline PyTypeObject * type_from_ref(PyObject *ref) { @@ -6975,6 +6981,41 @@ 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(_PyObject_CAST(Py_TYPE(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); + + /* Do NOT visit tp_subclasses or tp_weaklist here. + * + * tp_subclasses is a dict of weak references to subclasses — following + * it would pull every subclass into the freeze graph when freezing a + * base type, which is almost never desirable. + * + * tp_weaklist is the head of the weak-reference list *to* this type + * object; the type does not own those weak-reference holders, so they + * must not be dragged into the freeze graph either. + */ + + 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 +7120,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 +8380,38 @@ PyTypeObject PyBaseObject_Type = { PyType_GenericAlloc, /* tp_alloc */ object_new, /* tp_new */ PyObject_Free, /* tp_free */ + .tp_reachable = object_reachable, }; +/* + * TODO: We should discuss this. I am a little confused about the + * correct way to do this. This gives a good level of compatibility, + * but is perhaps too much. + * + * Default tp_reachable for types that inherit from object but don't + * define their own. Visits Py_TYPE(self), then delegates to the + * type's tp_traverse for any additional references. + * + * Invariant: for any given object only ONE of the following should + * run during a freeze traversal: + * - a type-specific tp_reachable (which must visit its own type), OR + * - this fallback (object_reachable) inherited via inherit_special. + * Both paths visit the type then call tp_traverse, so double-visiting + * cannot occur as long as traverse_freeze uses tp_reachable exclusively. + */ +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 +8575,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; @@ -10956,6 +11032,13 @@ bufferwrapper_traverse(PyObject *op, visitproc visit, void *arg) return 0; } +static int +bufferwrapper_reachable(PyObject *self, visitproc visit, void *arg) +{ + Py_VISIT(_PyObject_CAST(Py_TYPE(self))); + return bufferwrapper_traverse(self, visit, arg); +} + static void bufferwrapper_dealloc(PyObject *self) { @@ -11006,6 +11089,7 @@ PyTypeObject _PyBufferWrapper_Type = { .tp_alloc = PyType_GenericAlloc, .tp_free = PyObject_GC_Del, .tp_traverse = bufferwrapper_traverse, + .tp_reachable = bufferwrapper_reachable, .tp_dealloc = bufferwrapper_dealloc, .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, .tp_as_buffer = &bufferwrapper_as_buffer, 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/typevarobject.c b/Objects/typevarobject.c index 4fe46e9fccb939..5e4b2d7f132738 100644 --- a/Objects/typevarobject.c +++ b/Objects/typevarobject.c @@ -152,6 +152,13 @@ constevaluator_traverse(PyObject *self, visitproc visit, void *arg) return 0; } +static int +constevaluator_reachable(PyObject *self, visitproc visit, void *arg) +{ + Py_VISIT(_PyObject_CAST(Py_TYPE(self))); + return constevaluator_traverse(self, visit, arg); +} + static int constevaluator_clear(PyObject *self) { @@ -242,6 +249,7 @@ static PyType_Slot constevaluator_slots[] = { {Py_tp_doc, (void *)constevaluator_doc}, {Py_tp_dealloc, constevaluator_dealloc}, {Py_tp_traverse, constevaluator_traverse}, + {Py_tp_reachable, constevaluator_reachable}, {Py_tp_clear, constevaluator_clear}, {Py_tp_repr, constevaluator_repr}, {Py_tp_call, constevaluator_call}, @@ -502,6 +510,13 @@ typevar_traverse(PyObject *self, visitproc visit, void *arg) return 0; } +static int +typevar_reachable(PyObject *self, visitproc visit, void *arg) +{ + Py_VISIT(_PyObject_CAST(Py_TYPE(self))); + return typevar_traverse(self, visit, arg); +} + static int typevar_clear(PyObject *op) { @@ -922,6 +937,7 @@ static PyType_Slot typevar_slots[] = { {Py_tp_alloc, PyType_GenericAlloc}, {Py_tp_free, PyObject_GC_Del}, {Py_tp_traverse, typevar_traverse}, + {Py_tp_reachable, typevar_reachable}, {Py_tp_clear, typevar_clear}, {Py_tp_repr, typevar_repr}, {Py_tp_members, typevar_members}, @@ -966,6 +982,13 @@ paramspecattr_traverse(PyObject *self, visitproc visit, void *arg) return 0; } +static int +paramspecattr_reachable(PyObject *self, visitproc visit, void *arg) +{ + Py_VISIT(_PyObject_CAST(Py_TYPE(self))); + return paramspecattr_traverse(self, visit, arg); +} + static int paramspecattr_clear(PyObject *op) { @@ -1070,6 +1093,7 @@ static PyType_Slot paramspecargs_slots[] = { {Py_tp_alloc, PyType_GenericAlloc}, {Py_tp_free, PyObject_GC_Del}, {Py_tp_traverse, paramspecattr_traverse}, + {Py_tp_reachable, paramspecattr_reachable}, {Py_tp_clear, paramspecattr_clear}, {Py_tp_repr, paramspecargs_repr}, {Py_tp_members, paramspecattr_members}, @@ -1150,6 +1174,7 @@ static PyType_Slot paramspeckwargs_slots[] = { {Py_tp_alloc, PyType_GenericAlloc}, {Py_tp_free, PyObject_GC_Del}, {Py_tp_traverse, paramspecattr_traverse}, + {Py_tp_reachable, paramspecattr_reachable}, {Py_tp_clear, paramspecattr_clear}, {Py_tp_repr, paramspeckwargs_repr}, {Py_tp_members, paramspecattr_members}, @@ -1197,6 +1222,13 @@ paramspec_traverse(PyObject *self, visitproc visit, void *arg) return 0; } +static int +paramspec_reachable(PyObject *self, visitproc visit, void *arg) +{ + Py_VISIT(_PyObject_CAST(Py_TYPE(self))); + return paramspec_traverse(self, visit, arg); +} + static int paramspec_clear(PyObject *op) { @@ -1503,6 +1535,7 @@ static PyType_Slot paramspec_slots[] = { {Py_tp_alloc, PyType_GenericAlloc}, {Py_tp_free, PyObject_GC_Del}, {Py_tp_traverse, paramspec_traverse}, + {Py_tp_reachable, paramspec_reachable}, {Py_tp_clear, paramspec_clear}, {Py_tp_repr, paramspec_repr}, {0, 0}, @@ -1694,6 +1727,13 @@ typevartuple_traverse(PyObject *self, visitproc visit, void *arg) return 0; } +static int +typevartuple_reachable(PyObject *self, visitproc visit, void *arg) +{ + Py_VISIT(_PyObject_CAST(Py_TYPE(self))); + return typevartuple_traverse(self, visit, arg); +} + static int typevartuple_clear(PyObject *self) { @@ -1800,6 +1840,7 @@ PyType_Slot typevartuple_slots[] = { {Py_tp_alloc, PyType_GenericAlloc}, {Py_tp_free, PyObject_GC_Del}, {Py_tp_traverse, typevartuple_traverse}, + {Py_tp_reachable, typevartuple_reachable}, {Py_tp_clear, typevartuple_clear}, {0, 0}, }; @@ -2046,6 +2087,13 @@ typealias_traverse(PyObject *op, visitproc visit, void *arg) return 0; } +static int +typealias_reachable(PyObject *op, visitproc visit, void *arg) +{ + Py_VISIT(_PyObject_CAST(Py_TYPE(op))); + return typealias_traverse(op, visit, arg); +} + static int typealias_clear(PyObject *op) { @@ -2175,6 +2223,7 @@ PyTypeObject _PyTypeAlias_Type = { .tp_iter = unpack_iter, .tp_traverse = typealias_traverse, .tp_clear = typealias_clear, + .tp_reachable = typealias_reachable, .tp_repr = typealias_repr, .tp_as_number = &typealias_as_number, .tp_as_mapping = &typealias_as_mapping, diff --git a/Objects/unicodeobject.c b/Objects/unicodeobject.c index d4549b70d4dabc..c4f4fd42350588 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 */ @@ -15114,6 +15124,13 @@ unicodeiter_traverse(PyObject *op, visitproc visit, void *arg) return 0; } +static int +unicodeiter_reachable(PyObject *op, visitproc visit, void *arg) +{ + Py_VISIT(_PyObject_CAST(Py_TYPE(op))); + return unicodeiter_traverse(op, visit, arg); +} + static PyObject * unicodeiter_next(PyObject *op) { @@ -15255,6 +15272,7 @@ PyTypeObject PyUnicodeIter_Type = { unicodeiter_next, /* tp_iternext */ unicodeiter_methods, /* tp_methods */ 0, + .tp_reachable = unicodeiter_reachable, }; PyTypeObject _PyUnicodeASCIIIter_Type = { @@ -15268,6 +15286,7 @@ PyTypeObject _PyUnicodeASCIIIter_Type = { .tp_iter = PyObject_SelfIter, .tp_iternext = unicode_ascii_iter_next, .tp_methods = unicodeiter_methods, + .tp_reachable = unicodeiter_reachable, }; static PyObject * diff --git a/Objects/unionobject.c b/Objects/unionobject.c index c4ece0fe09f018..5205cdeea767d1 100644 --- a/Objects/unionobject.c +++ b/Objects/unionobject.c @@ -42,6 +42,13 @@ union_traverse(PyObject *self, visitproc visit, void *arg) return 0; } +static int +union_reachable(PyObject *self, visitproc visit, void *arg) +{ + Py_VISIT(_PyObject_CAST(Py_TYPE(self))); + return union_traverse(self, visit, arg); +} + static Py_hash_t union_hash(PyObject *self) { @@ -518,6 +525,7 @@ PyTypeObject _PyUnion_Type = { .tp_free = PyObject_GC_Del, .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, .tp_traverse = union_traverse, + .tp_reachable = union_reachable, .tp_hash = union_hash, .tp_getattro = union_getattro, .tp_members = union_members, diff --git a/Objects/weakrefobject.c b/Objects/weakrefobject.c index 61fa3ddad0bfd8..f6af75951ba0b2 100644 --- a/Objects/weakrefobject.c +++ b/Objects/weakrefobject.c @@ -154,6 +154,13 @@ gc_traverse(PyObject *op, visitproc visit, void *arg) return 0; } +static int +weakref_reachable(PyObject *op, visitproc visit, void *arg) +{ + Py_VISIT(_PyObject_CAST(Py_TYPE(op))); + return gc_traverse(op, visit, arg); +} + static int gc_clear(PyObject *op) @@ -508,6 +515,7 @@ _PyWeakref_RefType = { .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_HAVE_VECTORCALL | Py_TPFLAGS_BASETYPE, .tp_traverse = gc_traverse, + .tp_reachable = weakref_reachable, .tp_clear = gc_clear, .tp_richcompare = weakref_richcompare, .tp_methods = weakref_methods, @@ -880,6 +888,7 @@ _PyWeakref_ProxyType = { proxy_iter, /* tp_iter */ proxy_iternext, /* tp_iternext */ proxy_methods, /* tp_methods */ + .tp_reachable = weakref_reachable, }; @@ -913,6 +922,7 @@ _PyWeakref_CallableProxyType = { 0, /* tp_weaklistoffset */ proxy_iter, /* tp_iter */ proxy_iternext, /* tp_iternext */ + .tp_reachable = weakref_reachable, }; PyObject * diff --git a/Python/immutability.c b/Python/immutability.c index dce167b2df1d7f..eaac27fac4d95c 100644 --- a/Python/immutability.c +++ b/Python/immutability.c @@ -150,6 +150,13 @@ int init_state(struct _Py_immutability_state *state) return -1; } + state->warned_types = _Py_hashtable_new( + _Py_hashtable_hash_ptr, + _Py_hashtable_compare_direct); + if(state->warned_types == NULL){ + return -1; + } + return 0; } @@ -1276,6 +1283,7 @@ is_freezable_builtin(PyTypeObject *type) type == &PyMethodDescr_Type || type == &PyClassMethod_Type || // TODO(Immutable): mjp I added this, is it correct? Discuss with maj type == &PyClassMethodDescr_Type || + type == &PyStaticMethod_Type || type == &PyMethod_Type || type == &PyCFunction_Type || type == &PyCapsule_Type || @@ -1514,21 +1522,29 @@ int traverse_freeze(PyObject* obj, struct FreezeState* freeze_state) SUCCEEDS(_Py_module_freeze_hook(obj)); } - if(PyType_Check(obj)){ - // TODO(Immutable): mjp: Special case for types not sure if required. We should review. - PyTypeObject* type = (PyTypeObject*)obj; - - SUCCEEDS(freeze_visit(type->tp_dict, freeze_state)); - SUCCEEDS(freeze_visit(type->tp_mro, freeze_state)); - // We need to freeze the tuple object, even though the types - // within will have been frozen already. - SUCCEEDS(freeze_visit(type->tp_bases, 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) { + PyTypeObject *tp = Py_TYPE(obj); + references = tp->tp_traverse; + struct _Py_immutability_state *imm_state = get_immutable_state(); + if (imm_state != NULL && + _Py_hashtable_get(imm_state->warned_types, (void *)tp) == NULL) + { + _Py_hashtable_set(imm_state->warned_types, (void *)tp, (void *)1); + if (references != NULL) { + PySys_FormatStderr( + "freeze: type '%.100s' has tp_traverse but no tp_reachable\n", + tp->tp_name); + } else { + PySys_FormatStderr( + "freeze: type '%.100s' has no tp_traverse and no tp_reachable\n", + tp->tp_name); + } + } + } + if(references != NULL){ + SUCCEEDS(references(obj, (visitproc)freeze_visit, freeze_state)); } } @@ -1550,13 +1566,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 +1687,4 @@ int _PyImmutability_Freeze(PyObject* obj) deallocate_FreezeState(&freeze_state); TRACE_MERMAID_END(); return result; -} \ No newline at end of file +}