Skip to content

Commit 9a2fac1

Browse files
committed
Issue #22653: Fix an assertion failure in debug mode when doing a reentrant dict insertion in debug mode.
1 parent 10ad948 commit 9a2fac1

File tree

3 files changed

+35
-3
lines changed

3 files changed

+35
-3
lines changed

Lib/test/test_dict.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -906,6 +906,35 @@ class Foo: pass
906906
f.a = 'a'
907907
self.assertEqual(f.__dict__, {1:1, 'a':'a'})
908908

909+
def check_reentrant_insertion(self, mutate):
910+
# This object will trigger mutation of the dict when replaced
911+
# by another value. Note this relies on refcounting: the test
912+
# won't achieve its purpose on fully-GCed Python implementations.
913+
class Mutating:
914+
def __del__(self):
915+
mutate(d)
916+
917+
d = {k: Mutating() for k in 'abcdefghijklmnopqr'}
918+
for k in list(d):
919+
d[k] = k
920+
921+
def test_reentrant_insertion(self):
922+
# Reentrant insertion shouldn't crash (see issue #22653)
923+
def mutate(d):
924+
d['b'] = 5
925+
self.check_reentrant_insertion(mutate)
926+
927+
def mutate(d):
928+
d.update(self.__dict__)
929+
d.clear()
930+
self.check_reentrant_insertion(mutate)
931+
932+
def mutate(d):
933+
while d:
934+
d.popitem()
935+
self.check_reentrant_insertion(mutate)
936+
937+
909938
from test import mapping_tests
910939

911940
class GeneralMappingTests(mapping_tests.BasicTestMappingProtocol):

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ Release date: TBA
1111
Core and Builtins
1212
-----------------
1313

14+
- Issue #22653: Fix an assertion failure in debug mode when doing a reentrant
15+
dict insertion in debug mode.
16+
1417
- Issue #22643: Fix integer overflow in Unicode case operations (upper, lower,
1518
title, swapcase, casefold).
1619

Objects/dictobject.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -814,13 +814,14 @@ insertdict(PyDictObject *mp, PyObject *key, Py_hash_t hash, PyObject *value)
814814
if (ep == NULL) {
815815
return -1;
816816
}
817+
assert(PyUnicode_CheckExact(key) || mp->ma_keys->dk_lookup == lookdict);
817818
Py_INCREF(value);
818819
MAINTAIN_TRACKING(mp, key, value);
819820
old_value = *value_addr;
820821
if (old_value != NULL) {
821822
assert(ep->me_key != NULL && ep->me_key != dummy);
822823
*value_addr = value;
823-
Py_DECREF(old_value); /* which **CAN** re-enter */
824+
Py_DECREF(old_value); /* which **CAN** re-enter (see issue #22653) */
824825
}
825826
else {
826827
if (ep->me_key == NULL) {
@@ -851,9 +852,8 @@ insertdict(PyDictObject *mp, PyObject *key, Py_hash_t hash, PyObject *value)
851852
}
852853
mp->ma_used++;
853854
*value_addr = value;
855+
assert(ep->me_key != NULL && ep->me_key != dummy);
854856
}
855-
assert(ep->me_key != NULL && ep->me_key != dummy);
856-
assert(PyUnicode_CheckExact(key) || mp->ma_keys->dk_lookup == lookdict);
857857
return 0;
858858
}
859859

0 commit comments

Comments
 (0)