diff --git a/Lib/test/test_dict.py b/Lib/test/test_dict.py index a38895945ed..98d8a3bd00e 100644 --- a/Lib/test/test_dict.py +++ b/Lib/test/test_dict.py @@ -906,6 +906,35 @@ class DictTest(unittest.TestCase): f.a = 'a' self.assertEqual(f.__dict__, {1:1, 'a':'a'}) + def check_reentrant_insertion(self, mutate): + # This object will trigger mutation of the dict when replaced + # by another value. Note this relies on refcounting: the test + # won't achieve its purpose on fully-GCed Python implementations. + class Mutating: + def __del__(self): + mutate(d) + + d = {k: Mutating() for k in 'abcdefghijklmnopqr'} + for k in list(d): + d[k] = k + + def test_reentrant_insertion(self): + # Reentrant insertion shouldn't crash (see issue #22653) + def mutate(d): + d['b'] = 5 + self.check_reentrant_insertion(mutate) + + def mutate(d): + d.update(self.__dict__) + d.clear() + self.check_reentrant_insertion(mutate) + + def mutate(d): + while d: + d.popitem() + self.check_reentrant_insertion(mutate) + + from test import mapping_tests class GeneralMappingTests(mapping_tests.BasicTestMappingProtocol): diff --git a/Misc/NEWS b/Misc/NEWS index 56fd60babc9..1aca6e7dff5 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -10,6 +10,9 @@ Release date: TBA Core and Builtins ----------------- +- Issue #22653: Fix an assertion failure in debug mode when doing a reentrant + dict insertion in debug mode. + - Issue #22643: Fix integer overflow in Unicode case operations (upper, lower, title, swapcase, casefold). diff --git a/Objects/dictobject.c b/Objects/dictobject.c index 6c78b94ef19..a3219f712ff 100644 --- a/Objects/dictobject.c +++ b/Objects/dictobject.c @@ -814,13 +814,14 @@ insertdict(PyDictObject *mp, PyObject *key, Py_hash_t hash, PyObject *value) if (ep == NULL) { return -1; } + assert(PyUnicode_CheckExact(key) || mp->ma_keys->dk_lookup == lookdict); Py_INCREF(value); MAINTAIN_TRACKING(mp, key, value); old_value = *value_addr; if (old_value != NULL) { assert(ep->me_key != NULL && ep->me_key != dummy); *value_addr = value; - Py_DECREF(old_value); /* which **CAN** re-enter */ + Py_DECREF(old_value); /* which **CAN** re-enter (see issue #22653) */ } else { if (ep->me_key == NULL) { @@ -851,9 +852,8 @@ insertdict(PyDictObject *mp, PyObject *key, Py_hash_t hash, PyObject *value) } mp->ma_used++; *value_addr = value; + assert(ep->me_key != NULL && ep->me_key != dummy); } - assert(ep->me_key != NULL && ep->me_key != dummy); - assert(PyUnicode_CheckExact(key) || mp->ma_keys->dk_lookup == lookdict); return 0; }