Skip to content

Commit eec9257

Browse files
[3.13] gh-140551: Fix dict crash if clear is called at lookup stage (GH-140558) (#140744)
* gh-140551: Fix `dict` crash if `clear` is called at `lookup` stage (#140558) Co-authored-by: Inada Naoki <songofacandy@gmail.com>
1 parent b0f12b5 commit eec9257

File tree

3 files changed

+56
-51
lines changed

3 files changed

+56
-51
lines changed

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 & 51 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,34 @@ 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-
}
1840+
MAINTAIN_TRACKING(mp, key, value);
18361841

1837-
if (_PyDict_HasSplitTable(mp)) {
1838-
Py_ssize_t ix = insert_split_key(mp->ma_keys, key, hash);
1842+
if (_PyDict_HasSplitTable(mp) && PyUnicode_CheckExact(key)) {
1843+
ix = insert_split_key(mp->ma_keys, key, hash);
18391844
if (ix != DKIX_EMPTY) {
18401845
insert_split_value(interp, mp, key, value, ix);
18411846
Py_DECREF(key);
18421847
Py_DECREF(value);
18431848
return 0;
18441849
}
1845-
1846-
/* No space in shared keys. Resize and continue below. */
1847-
if (insertion_resize(interp, mp, 1) < 0) {
1848-
goto Fail;
1849-
}
1850+
// No space in shared keys. Go to insert_combined_dict() below.
18501851
}
1852+
else {
1853+
ix = _Py_dict_lookup(mp, key, hash, &old_value);
1854+
if (ix == DKIX_ERROR)
1855+
goto Fail;
18511856

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);
1857+
}
18571858

18581859
if (ix == DKIX_EMPTY) {
1859-
assert(!_PyDict_HasSplitTable(mp));
1860-
/* Insert into new slot. */
1861-
assert(old_value == NULL);
1860+
// insert_combined_dict() will convert from non DICT_KEYS_GENERAL table
1861+
// into DICT_KEYS_GENERAL table if key is not Unicode.
1862+
// We don't convert it before _Py_dict_lookup because non-Unicode key
1863+
// may change generic table into Unicode table.
18621864
if (insert_combined_dict(interp, mp, hash, key, value) < 0) {
18631865
goto Fail;
18641866
}
@@ -4256,6 +4258,7 @@ dict_setdefault_ref_lock_held(PyObject *d, PyObject *key, PyObject *default_valu
42564258
PyDictObject *mp = (PyDictObject *)d;
42574259
PyObject *value;
42584260
Py_hash_t hash;
4261+
Py_ssize_t ix;
42594262
PyInterpreterState *interp = _PyInterpreterState_GET();
42604263

42614264
ASSERT_DICT_LOCKED(d);
@@ -4290,17 +4293,8 @@ dict_setdefault_ref_lock_held(PyObject *d, PyObject *key, PyObject *default_valu
42904293
return 0;
42914294
}
42924295

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);
4296+
if (_PyDict_HasSplitTable(mp) && PyUnicode_CheckExact(key)) {
4297+
ix = insert_split_key(mp->ma_keys, key, hash);
43044298
if (ix != DKIX_EMPTY) {
43054299
PyObject *value = mp->ma_values->values[ix];
43064300
int already_present = value != NULL;
@@ -4313,27 +4307,22 @@ dict_setdefault_ref_lock_held(PyObject *d, PyObject *key, PyObject *default_valu
43134307
}
43144308
return already_present;
43154309
}
4316-
4317-
/* No space in shared keys. Resize and continue below. */
4318-
if (insertion_resize(interp, mp, 1) < 0) {
4319-
goto error;
4320-
}
4310+
// No space in shared keys. Go to insert_combined_dict() below.
43214311
}
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;
4312+
else {
4313+
ix = _Py_dict_lookup(mp, key, hash, &value);
4314+
if (ix == DKIX_ERROR) {
4315+
if (result) {
4316+
*result = NULL;
4317+
}
4318+
return -1;
43294319
}
4330-
return -1;
43314320
}
43324321

43334322
if (ix == DKIX_EMPTY) {
4334-
assert(!_PyDict_HasSplitTable(mp));
43354323
value = default_value;
43364324

4325+
// See comment to this function in insertdict.
43374326
if (insert_combined_dict(interp, mp, hash, Py_NewRef(key), Py_NewRef(value)) < 0) {
43384327
Py_DECREF(key);
43394328
Py_DECREF(value);
@@ -4359,12 +4348,6 @@ dict_setdefault_ref_lock_held(PyObject *d, PyObject *key, PyObject *default_valu
43594348
*result = incref_result ? Py_NewRef(value) : value;
43604349
}
43614350
return 1;
4362-
4363-
error:
4364-
if (result) {
4365-
*result = NULL;
4366-
}
4367-
return -1;
43684351
}
43694352

43704353
int

0 commit comments

Comments
 (0)