https://github.com/python/cpython/commit/ff03e45625a8912caf0cbdf116b7b1efe4e6e93f commit: ff03e45625a8912caf0cbdf116b7b1efe4e6e93f branch: 3.13 author: Miss Islington (bot) <[email protected]> committer: ZeroIntensity <[email protected]> date: 2026-09-27T03:17:41Z summary:
[3.13] gh-158254: Detect keys changing during reverse dict iteration (GH-158255) (GH-158269) A reverse dict iterator only checked that the dict's size was unchanged, so after the keys were replaced with the same number of keys it kept yielding entries for the new keys. It could yield more items than the dict had when it was created, and __length_hint__() then wrapped around. Add the check the forward iterators already have: finding an entry after the expected number of items raises "dictionary keys changed during iteration". (cherry picked from commit 042a7c150552a4ddb5a90cf65a6e5b9e7ba3f7dc) Co-authored-by: Joshua Bronson <[email protected]> Co-authored-by: Claude Opus 5.5 <[email protected]> files: A Misc/NEWS.d/next/Core_and_Builtins/2026-09-26-15-39-32.gh-issue-158254.qT7vRk.rst M Lib/test/test_dict.py M Objects/dictobject.c diff --git a/Lib/test/test_dict.py b/Lib/test/test_dict.py index 455aa6239824cf1..8ddbe9dd542d731 100644 --- a/Lib/test/test_dict.py +++ b/Lib/test/test_dict.py @@ -1517,6 +1517,31 @@ def test_reversed_dict_after_clear_and_restore(self): for it in iterators: self.assertEqual(list(it), []) + def test_reversed_dict_keys_changed_during_iteration(self): + d = dict.fromkeys(range(10)) + for i in range(7): + del d[i] + + iterators = ( + reversed(d), + reversed(d.keys()), + reversed(d.values()), + reversed(d.items()), + ) + for it in iterators: + next(it) + + # Same size as before, but with different keys below + # the iterators' current position. + d.clear() + d.update(dict.fromkeys(range(10))) + for i in range(3, 10): + del d[i] + + for it in iterators: + with self.assertRaisesRegex(RuntimeError, 'keys changed'): + list(it) + def test_dict_copy_order(self): # bpo-34320 od = collections.OrderedDict([('a', 1), ('b', 2)]) diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-09-26-15-39-32.gh-issue-158254.qT7vRk.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-09-26-15-39-32.gh-issue-158254.qT7vRk.rst new file mode 100644 index 000000000000000..21908eea4a8236b --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-09-26-15-39-32.gh-issue-158254.qT7vRk.rst @@ -0,0 +1,3 @@ +Reverse iterators over a :class:`dict` and its views now raise +:exc:`RuntimeError` if the dictionary's keys change during iteration, like +forward iterators, instead of yielding entries for the new keys. diff --git a/Objects/dictobject.c b/Objects/dictobject.c index 56f6393bdb61594..9e442353f1c5dbb 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -5717,6 +5717,12 @@ dictreviter_iter_lock_held(PyDictObject *d, PyObject *self) value = entry_ptr->me_value; } } + // We found an element, but did not expect it + if (di->len == 0) { + PyErr_SetString(PyExc_RuntimeError, + "dictionary keys changed during iteration"); + goto fail; + } di->di_pos = i-1; di->len--; _______________________________________________ Python-checkins mailing list -- [email protected] To unsubscribe send an email to [email protected] https://mail.python.org/mailman3//lists/python-checkins.python.org Member address: [email protected]
