From 255b3219ccdccd6ed9c29029bb5009cca27968a9 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 24 Jun 2026 13:11:51 +0200 Subject: [PATCH] gh-151722: Do not track the dict in the GC in _PyDict_FromKeys() dict_merge() no longer requires the dictionary to be tracked by the GC. Co-authored-by: Donghee Na --- ...-06-24-13-36-31.gh-issue-151722.lWKfE1.rst | 3 + Objects/dictobject.c | 84 ++++++++++++------- 2 files changed, 57 insertions(+), 30 deletions(-) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2026-06-24-13-36-31.gh-issue-151722.lWKfE1.rst diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-06-24-13-36-31.gh-issue-151722.lWKfE1.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-06-24-13-36-31.gh-issue-151722.lWKfE1.rst new file mode 100644 index 00000000000000..6cea69523a2082 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-06-24-13-36-31.gh-issue-151722.lWKfE1.rst @@ -0,0 +1,3 @@ +:meth:`!frozendict.fromkeys` now creates a :class:`frozendict` which is not +tracked by the garbage collector, and only tracks it once the dictionary is +fully initialized. Patch by Donghee Na and Victor Stinner. diff --git a/Objects/dictobject.c b/Objects/dictobject.c index 9210398ee551de..54a3d1513d40ab 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -140,6 +140,7 @@ static PyObject* frozendict_new(PyTypeObject *type, PyObject *args, PyObject *kwds); static PyObject* frozendict_new_untracked(PyTypeObject *type); static PyObject* dict_new(PyTypeObject *type, PyObject *args, PyObject *kwds); +static PyObject* dict_new_untracked(PyTypeObject *type); static int dict_merge(PyObject *a, PyObject *b, int override, PyObject **dupkey); static int dict_contains(PyObject *op, PyObject *key); static int dict_merge_from_seq2(PyObject *d, PyObject *seq2, int override); @@ -3414,40 +3415,49 @@ dict_set_fromkeys(PyDictObject *mp, PyObject *iterable, PyObject *value) PyObject * _PyDict_FromKeys(PyObject *cls, PyObject *iterable, PyObject *value) { - PyObject *it; /* iter(iterable) */ + PyObject *it = NULL; /* iter(iterable) */ PyObject *key; PyObject *d; int status; - d = _PyObject_CallNoArgs(cls); + PyTypeObject *cls_type = _PyType_CAST(cls); + if (PyObject_IsSubclass(cls, (PyObject*)&PyFrozenDict_Type) + && cls_type->tp_new == frozendict_new) + { + // gh-151722: Create a frozendict copy which is not tracked by the GC. + d = frozendict_new_untracked(cls_type); + } + else { + d = _PyObject_CallNoArgs(cls); + } if (d == NULL) { return NULL; } - // If cls is a dict or frozendict subclass with overridden constructor, - // copy the frozendict. - PyTypeObject *cls_type = _PyType_CAST(cls); - if (PyFrozenDict_Check(d) && cls_type->tp_new != frozendict_new) { + // gh-151722: If cls constructor returns a frozendict which is tracked by + // the GC, create a frozendict copy which is not tracked by the GC. + if (PyFrozenDict_Check(d) && _PyObject_GC_IS_TRACKED(d)) { // Subclass-friendly copy PyObject *copy; if (PyObject_IsSubclass(cls, (PyObject*)&PyFrozenDict_Type)) { - copy = frozendict_new(cls_type, NULL, NULL); + copy = frozendict_new_untracked(cls_type); } else { - copy = dict_new(cls_type, NULL, NULL); + copy = dict_new_untracked(cls_type); } if (copy == NULL) { - Py_DECREF(d); - return NULL; + goto Fail; } if (dict_merge(copy, d, 1, NULL) < 0) { - Py_DECREF(d); Py_DECREF(copy); - return NULL; + goto Fail; } Py_SETREF(d, copy); } assert(!PyFrozenDict_Check(d) || can_modify_dict((PyDictObject*)d)); + if (PyFrozenDict_Check(d)) { + assert(!_PyObject_GC_IS_TRACKED(d)); + } if (PyDict_CheckExact(d)) { if (PyDict_CheckExact(iterable)) { @@ -3456,7 +3466,7 @@ _PyDict_FromKeys(PyObject *cls, PyObject *iterable, PyObject *value) Py_BEGIN_CRITICAL_SECTION2(d, iterable); d = (PyObject *)dict_dict_fromkeys(mp, iterable, value); Py_END_CRITICAL_SECTION2(); - return d; + goto Done; } else if (PyFrozenDict_CheckExact(iterable)) { PyDictObject *mp = (PyDictObject *)d; @@ -3464,7 +3474,7 @@ _PyDict_FromKeys(PyObject *cls, PyObject *iterable, PyObject *value) Py_BEGIN_CRITICAL_SECTION(d); d = (PyObject *)dict_dict_fromkeys(mp, iterable, value); Py_END_CRITICAL_SECTION(); - return d; + goto Done; } else if (PyAnySet_CheckExact(iterable)) { PyDictObject *mp = (PyDictObject *)d; @@ -3472,7 +3482,7 @@ _PyDict_FromKeys(PyObject *cls, PyObject *iterable, PyObject *value) Py_BEGIN_CRITICAL_SECTION2(d, iterable); d = (PyObject *)dict_set_fromkeys(mp, iterable, value); Py_END_CRITICAL_SECTION2(); - return d; + goto Done; } } else if (PyFrozenDict_CheckExact(d)) { @@ -3482,12 +3492,12 @@ _PyDict_FromKeys(PyObject *cls, PyObject *iterable, PyObject *value) Py_BEGIN_CRITICAL_SECTION(iterable); d = (PyObject *)dict_dict_fromkeys(mp, iterable, value); Py_END_CRITICAL_SECTION(); - return d; + goto Done; } else if (PyFrozenDict_CheckExact(iterable)) { PyDictObject *mp = (PyDictObject *)d; d = (PyObject *)dict_dict_fromkeys(mp, iterable, value); - return d; + goto Done; } else if (PyAnySet_CheckExact(iterable)) { PyDictObject *mp = (PyDictObject *)d; @@ -3495,14 +3505,13 @@ _PyDict_FromKeys(PyObject *cls, PyObject *iterable, PyObject *value) Py_BEGIN_CRITICAL_SECTION(iterable); d = (PyObject *)dict_set_fromkeys(mp, iterable, value); Py_END_CRITICAL_SECTION(); - return d; + goto Done; } } it = PyObject_GetIter(iterable); if (it == NULL){ - Py_DECREF(d); - return NULL; + goto Fail; } if (PyDict_CheckExact(d)) { @@ -3541,12 +3550,19 @@ dict_iter_exit:; if (PyErr_Occurred()) goto Fail; Py_DECREF(it); - return d; + goto Done; Fail: - Py_DECREF(it); + Py_XDECREF(it); Py_DECREF(d); return NULL; + +Done: + // d can be NULL + if (d != NULL && !_PyObject_GC_IS_TRACKED(d)) { + _PyObject_GC_TRACK(d); + } + return d; } /* Methods */ @@ -4147,9 +4163,6 @@ dict_dict_merge(PyDictObject *mp, PyDictObject *other, int override, PyObject ** set_keys(mp, keys); STORE_USED(mp, other->ma_used); ASSERT_CONSISTENT(mp); - if (PyDict_Check(mp)) { - assert(_PyObject_GC_IS_TRACKED(mp)); - } return 0; } } @@ -4316,7 +4329,12 @@ dict_merge_api(PyObject *a, PyObject *b, int override, PyObject **dupkey) } return -1; } - return dict_merge(a, b, override, dupkey); + + int res = dict_merge(a, b, override, dupkey); + if (PyDict_Check(a)) { + assert(_PyObject_GC_IS_TRACKED(a)); + } + return res; } int @@ -4475,10 +4493,16 @@ copy_lock_held(PyObject *o, int as_frozendict) } if (copy == NULL) return NULL; - if (dict_merge(copy, o, 1, NULL) == 0) - return copy; - Py_DECREF(copy); - return NULL; + if (dict_merge(copy, o, 1, NULL) < 0) { + Py_DECREF(copy); + return NULL; + + } + + if (PyDict_Check(copy)) { + assert(_PyObject_GC_IS_TRACKED(copy)); + } + return copy; } PyObject *