Skip to content

Commit 2ece07d

Browse files
gh-140551: Fix dict crash if clear is called at lookup stage (#140558)
Co-authored-by: Inada Naoki <songofacandy@gmail.com>
1 parent f3476c6 commit 2ece07d

3 files changed

Lines changed: 56 additions & 52 deletions

File tree

Lib/test/test_dict.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1654,6 +1654,26 @@ def make_pairs():
16541654
self.assertEqual(d.get(key3_3), 44)
16551655
self.assertGreaterEqual(eq_count, 1)
16561656

1657+
def test_clear_at_lookup(self):
1658+
class X:
1659+
def __hash__(self):
1660+
return 1
1661+
def __eq__(self, other):
1662+
nonlocal d
1663+
d.clear()
1664+
1665+
d = {}
1666+
for _ in range(10):
1667+
d[X()] = None
1668+
1669+
self.assertEqual(len(d), 1)
1670+
1671+
d = {}
1672+
for _ in range(10):
1673+
d.setdefault(X(), None)
1674+
1675+
self.assertEqual(len(d), 1)
1676+
16571677

16581678
class CAPITest(unittest.TestCase):
16591679

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fixed crash in :class:`dict` if :meth:`dict.clear` is called at the lookup
2+
stage. Patch by Mikhail Efimov and Inada Naoki.

Objects/dictobject.c

Lines changed: 34 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1724,6 +1724,14 @@ static inline int
17241724
insert_combined_dict(PyInterpreterState *interp, PyDictObject *mp,
17251725
Py_hash_t hash, PyObject *key, PyObject *value)
17261726
{
1727+
// gh-140551: If dict was cleared in _Py_dict_lookup,
1728+
// we have to resize one more time to force general key kind.
1729+
if (DK_IS_UNICODE(mp->ma_keys) && !PyUnicode_CheckExact(key)) {
1730+
if (insertion_resize(interp, mp, 0) < 0)
1731+
return -1;
1732+
assert(mp->ma_keys->dk_kind == DICT_KEYS_GENERAL);
1733+
}
1734+
17271735
if (mp->ma_keys->dk_usable <= 0) {
17281736
/* Need to resize. */
17291737
if (insertion_resize(interp, mp, 1) < 0) {
@@ -1825,40 +1833,33 @@ insertdict(PyInterpreterState *interp, PyDictObject *mp,
18251833
PyObject *key, Py_hash_t hash, PyObject *value)
18261834
{
18271835
PyObject *old_value;
1836+
Py_ssize_t ix;
18281837

18291838
ASSERT_DICT_LOCKED(mp);
18301839

1831-
if (DK_IS_UNICODE(mp->ma_keys) && !PyUnicode_CheckExact(key)) {
1832-
if (insertion_resize(interp, mp, 0) < 0)
1833-
goto Fail;
1834-
assert(mp->ma_keys->dk_kind == DICT_KEYS_GENERAL);
1835-
}
1836-
1837-
if (_PyDict_HasSplitTable(mp)) {
1838-
Py_ssize_t ix = insert_split_key(mp->ma_keys, key, hash);
1840+
if (_PyDict_HasSplitTable(mp) && PyUnicode_CheckExact(key)) {
1841+
ix = insert_split_key(mp->ma_keys, key, hash);
18391842
if (ix != DKIX_EMPTY) {
18401843
insert_split_value(interp, mp, key, value, ix);
18411844
Py_DECREF(key);
18421845
Py_DECREF(value);
18431846
return 0;
18441847
}
1845-
1846-
/* No space in shared keys. Resize and continue below. */
1847-
if (insertion_resize(interp, mp, 1) < 0) {
1848-
goto Fail;
1849-
}
1848+
// No space in shared keys. Go to insert_combined_dict() below.
18501849
}
1850+
else {
1851+
ix = _Py_dict_lookup(mp, key, hash, &old_value);
1852+
if (ix == DKIX_ERROR)
1853+
goto Fail;
18511854

1852-
Py_ssize_t ix = _Py_dict_lookup(mp, key, hash, &old_value);
1853-
if (ix == DKIX_ERROR)
1854-
goto Fail;
1855-
1856-
MAINTAIN_TRACKING(mp, key, value);
1855+
MAINTAIN_TRACKING(mp, key, value);
1856+
}
18571857

18581858
if (ix == DKIX_EMPTY) {
1859-
assert(!_PyDict_HasSplitTable(mp));
1860-
/* Insert into new slot. */
1861-
assert(old_value == NULL);
1859+
// insert_combined_dict() will convert from non DICT_KEYS_GENERAL table
1860+
// into DICT_KEYS_GENERAL table if key is not Unicode.
1861+
// We don't convert it before _Py_dict_lookup because non-Unicode key
1862+
// may change generic table into Unicode table.
18621863
if (insert_combined_dict(interp, mp, hash, key, value) < 0) {
18631864
goto Fail;
18641865
}
@@ -4256,6 +4257,7 @@ dict_setdefault_ref_lock_held(PyObject *d, PyObject *key, PyObject *default_valu
42564257
PyDictObject *mp = (PyDictObject *)d;
42574258
PyObject *value;
42584259
Py_hash_t hash;
4260+
Py_ssize_t ix;
42594261
PyInterpreterState *interp = _PyInterpreterState_GET();
42604262

42614263
ASSERT_DICT_LOCKED(d);
@@ -4290,17 +4292,8 @@ dict_setdefault_ref_lock_held(PyObject *d, PyObject *key, PyObject *default_valu
42904292
return 0;
42914293
}
42924294

4293-
if (!PyUnicode_CheckExact(key) && DK_IS_UNICODE(mp->ma_keys)) {
4294-
if (insertion_resize(interp, mp, 0) < 0) {
4295-
if (result) {
4296-
*result = NULL;
4297-
}
4298-
return -1;
4299-
}
4300-
}
4301-
4302-
if (_PyDict_HasSplitTable(mp)) {
4303-
Py_ssize_t ix = insert_split_key(mp->ma_keys, key, hash);
4295+
if (_PyDict_HasSplitTable(mp) && PyUnicode_CheckExact(key)) {
4296+
ix = insert_split_key(mp->ma_keys, key, hash);
43044297
if (ix != DKIX_EMPTY) {
43054298
PyObject *value = mp->ma_values->values[ix];
43064299
int already_present = value != NULL;
@@ -4313,27 +4306,22 @@ dict_setdefault_ref_lock_held(PyObject *d, PyObject *key, PyObject *default_valu
43134306
}
43144307
return already_present;
43154308
}
4316-
4317-
/* No space in shared keys. Resize and continue below. */
4318-
if (insertion_resize(interp, mp, 1) < 0) {
4319-
goto error;
4320-
}
4309+
// No space in shared keys. Go to insert_combined_dict() below.
43214310
}
4322-
4323-
assert(!_PyDict_HasSplitTable(mp));
4324-
4325-
Py_ssize_t ix = _Py_dict_lookup(mp, key, hash, &value);
4326-
if (ix == DKIX_ERROR) {
4327-
if (result) {
4328-
*result = NULL;
4311+
else {
4312+
ix = _Py_dict_lookup(mp, key, hash, &value);
4313+
if (ix == DKIX_ERROR) {
4314+
if (result) {
4315+
*result = NULL;
4316+
}
4317+
return -1;
43294318
}
4330-
return -1;
43314319
}
43324320

43334321
if (ix == DKIX_EMPTY) {
4334-
assert(!_PyDict_HasSplitTable(mp));
43354322
value = default_value;
43364323

4324+
// See comment to this function in insertdict.
43374325
if (insert_combined_dict(interp, mp, hash, Py_NewRef(key), Py_NewRef(value)) < 0) {
43384326
Py_DECREF(key);
43394327
Py_DECREF(value);
@@ -4359,12 +4347,6 @@ dict_setdefault_ref_lock_held(PyObject *d, PyObject *key, PyObject *default_valu
43594347
*result = incref_result ? Py_NewRef(value) : value;
43604348
}
43614349
return 1;
4362-
4363-
error:
4364-
if (result) {
4365-
*result = NULL;
4366-
}
4367-
return -1;
43684350
}
43694351

43704352
int

0 commit comments

Comments
 (0)