diff --git a/Lib/test/test_dict.py b/Lib/test/test_dict.py index f26586809238f0..4730897f1963b7 100644 --- a/Lib/test/test_dict.py +++ b/Lib/test/test_dict.py @@ -116,6 +116,26 @@ def test_items(self): self.assertRaises(TypeError, d.items, None) self.assertEqual(repr(dict(a=1).items()), "dict_items([('a', 1)])") + @support.cpython_only + def test_item_iterator_oom(self): + import_helper.import_module('_testcapi') + from test.support.script_helper import assert_python_ok + code = """if 1: + import _testcapi + items = {1: 2, 3: 4}.items() + ballast = [(i, i) for i in range(3000)] + held = [] + for start in range(1, 5): + _testcapi.set_nomemory(start) + try: + held.append(iter(items)) + except MemoryError: + pass + finally: + _testcapi.remove_mem_hooks() + """ + assert_python_ok('-c', code) + def test_views_mapping(self): mappingproxy = type(type.__dict__) class Dict(dict): diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-06-24-14-05-00.gh-issue-152107.K3jX9p.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-06-24-14-05-00.gh-issue-152107.K3jX9p.rst new file mode 100644 index 00000000000000..a8f5a99ee06524 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-06-24-14-05-00.gh-issue-152107.K3jX9p.rst @@ -0,0 +1,3 @@ +Fix a crash when creating a :class:`dict` item iterator (for example +``iter(d.items())`` or ``reversed(d.items())``) under a memory-allocation +failure. Patch by Jiucheng Zang. diff --git a/Objects/dictobject.c b/Objects/dictobject.c index 9210398ee551de..f6d2a6e43b6186 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -5507,6 +5507,7 @@ dictiter_new(PyDictObject *dict, PyTypeObject *itertype) used = GET_USED(dict); di->di_used = used; di->len = used; + di->di_result = NULL; if (itertype == &PyDictRevIterKey_Type || itertype == &PyDictRevIterItem_Type || itertype == &PyDictRevIterValue_Type) { @@ -5520,6 +5521,10 @@ dictiter_new(PyDictObject *dict, PyTypeObject *itertype) else { di->di_pos = 0; } + /* gh-152107: track before allocating di_result. A dictiter with a NULL + di_result is a valid state for dictiter_traverse()/dictiter_dealloc(), + so a failure of the allocation below can safely DECREF a tracked di. */ + _PyObject_GC_TRACK(di); if (itertype == &PyDictIterItem_Type || itertype == &PyDictRevIterItem_Type) { di->di_result = _PyTuple_FromPairSteal(Py_None, Py_None); @@ -5528,10 +5533,6 @@ dictiter_new(PyDictObject *dict, PyTypeObject *itertype) return NULL; } } - else { - di->di_result = NULL; - } - _PyObject_GC_TRACK(di); return (PyObject *)di; }