Skip to content

Commit 5954b5b

Browse files
[3.14] pythongh-140551: Fix dict crash if clear is called at lookup stage (pythonGH-140558) (python#140743)
pythongh-140551: Fix `dict` crash if `clear` is called at `lookup` stage (python#140558) Co-authored-by: Inada Naoki <[email protected]>
1 parent e595d99 commit 5954b5b

File tree

3 files changed

+54
-50
lines changed

3 files changed

+54
-50
lines changed

Lib/test/test_dict.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1601,6 +1601,26 @@ def __hash__(self):
16011601
with self.assertRaises(KeyError):
16021602
d.get(key2)
16031603

1604+
def test_clear_at_lookup(self):
1605+
class X:
1606+
def __hash__(self):
1607+
return 1
1608+
def __eq__(self, other):
1609+
nonlocal d
1610+
d.clear()
1611+
1612+
d = {}
1613+
for _ in range(10):
1614+
d[X()] = None
1615+
1616+
self.assertEqual(len(d), 1)
1617+
1618+
d = {}
1619+
for _ in range(10):
1620+
d.setdefault(X(), None)
1621+
1622+
self.assertEqual(len(d), 1)
1623+
16041624

16051625
class CAPITest(unittest.TestCase):
16061626

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: 32 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1723,6 +1723,14 @@ static inline int
17231723
insert_combined_dict(PyInterpreterState *interp, PyDictObject *mp,
17241724
Py_hash_t hash, PyObject *key, PyObject *value)
17251725
{
1726+
// gh-140551: If dict was cleared in _Py_dict_lookup,
1727+
// we have to resize one more time to force general key kind.
1728+
if (DK_IS_UNICODE(mp->ma_keys) && !PyUnicode_CheckExact(key)) {
1729+
if (insertion_resize(interp, mp, 0) < 0)
1730+
return -1;
1731+
assert(mp->ma_keys->dk_kind == DICT_KEYS_GENERAL);
1732+
}
1733+
17261734
if (mp->ma_keys->dk_usable <= 0) {
17271735
/* Need to resize. */
17281736
if (insertion_resize(interp, mp, 1) < 0) {
@@ -1819,38 +1827,31 @@ insertdict(PyInterpreterState *interp, PyDictObject *mp,
18191827
PyObject *key, Py_hash_t hash, PyObject *value)
18201828
{
18211829
PyObject *old_value;
1830+
Py_ssize_t ix;
18221831

18231832
ASSERT_DICT_LOCKED(mp);
18241833

1825-
if (DK_IS_UNICODE(mp->ma_keys) && !PyUnicode_CheckExact(key)) {
1826-
if (insertion_resize(interp, mp, 0) < 0)
1827-
goto Fail;
1828-
assert(mp->ma_keys->dk_kind == DICT_KEYS_GENERAL);
1829-
}
1830-
1831-
if (_PyDict_HasSplitTable(mp)) {
1832-
Py_ssize_t ix = insert_split_key(mp->ma_keys, key, hash);
1834+
if (_PyDict_HasSplitTable(mp) && PyUnicode_CheckExact(key)) {
1835+
ix = insert_split_key(mp->ma_keys, key, hash);
18331836
if (ix != DKIX_EMPTY) {
18341837
insert_split_value(interp, mp, key, value, ix);
18351838
Py_DECREF(key);
18361839
Py_DECREF(value);
18371840
return 0;
18381841
}
1839-
1840-
/* No space in shared keys. Resize and continue below. */
1841-
if (insertion_resize(interp, mp, 1) < 0) {
1842+
// No space in shared keys. Go to insert_combined_dict() below.
1843+
}
1844+
else {
1845+
ix = _Py_dict_lookup(mp, key, hash, &old_value);
1846+
if (ix == DKIX_ERROR)
18421847
goto Fail;
1843-
}
18441848
}
18451849

1846-
Py_ssize_t ix = _Py_dict_lookup(mp, key, hash, &old_value);
1847-
if (ix == DKIX_ERROR)
1848-
goto Fail;
1849-
18501850
if (ix == DKIX_EMPTY) {
1851-
assert(!_PyDict_HasSplitTable(mp));
1852-
/* Insert into new slot. */
1853-
assert(old_value == NULL);
1851+
// insert_combined_dict() will convert from non DICT_KEYS_GENERAL table
1852+
// into DICT_KEYS_GENERAL table if key is not Unicode.
1853+
// We don't convert it before _Py_dict_lookup because non-Unicode key
1854+
// may change generic table into Unicode table.
18541855
if (insert_combined_dict(interp, mp, hash, key, value) < 0) {
18551856
goto Fail;
18561857
}
@@ -4326,6 +4327,7 @@ dict_setdefault_ref_lock_held(PyObject *d, PyObject *key, PyObject *default_valu
43264327
PyDictObject *mp = (PyDictObject *)d;
43274328
PyObject *value;
43284329
Py_hash_t hash;
4330+
Py_ssize_t ix;
43294331
PyInterpreterState *interp = _PyInterpreterState_GET();
43304332

43314333
ASSERT_DICT_LOCKED(d);
@@ -4361,17 +4363,8 @@ dict_setdefault_ref_lock_held(PyObject *d, PyObject *key, PyObject *default_valu
43614363
return 0;
43624364
}
43634365

4364-
if (!PyUnicode_CheckExact(key) && DK_IS_UNICODE(mp->ma_keys)) {
4365-
if (insertion_resize(interp, mp, 0) < 0) {
4366-
if (result) {
4367-
*result = NULL;
4368-
}
4369-
return -1;
4370-
}
4371-
}
4372-
4373-
if (_PyDict_HasSplitTable(mp)) {
4374-
Py_ssize_t ix = insert_split_key(mp->ma_keys, key, hash);
4366+
if (_PyDict_HasSplitTable(mp) && PyUnicode_CheckExact(key)) {
4367+
ix = insert_split_key(mp->ma_keys, key, hash);
43754368
if (ix != DKIX_EMPTY) {
43764369
PyObject *value = mp->ma_values->values[ix];
43774370
int already_present = value != NULL;
@@ -4384,27 +4377,22 @@ dict_setdefault_ref_lock_held(PyObject *d, PyObject *key, PyObject *default_valu
43844377
}
43854378
return already_present;
43864379
}
4387-
4388-
/* No space in shared keys. Resize and continue below. */
4389-
if (insertion_resize(interp, mp, 1) < 0) {
4390-
goto error;
4391-
}
4380+
// No space in shared keys. Go to insert_combined_dict() below.
43924381
}
4393-
4394-
assert(!_PyDict_HasSplitTable(mp));
4395-
4396-
Py_ssize_t ix = _Py_dict_lookup(mp, key, hash, &value);
4397-
if (ix == DKIX_ERROR) {
4398-
if (result) {
4399-
*result = NULL;
4382+
else {
4383+
ix = _Py_dict_lookup(mp, key, hash, &value);
4384+
if (ix == DKIX_ERROR) {
4385+
if (result) {
4386+
*result = NULL;
4387+
}
4388+
return -1;
44004389
}
4401-
return -1;
44024390
}
44034391

44044392
if (ix == DKIX_EMPTY) {
4405-
assert(!_PyDict_HasSplitTable(mp));
44064393
value = default_value;
44074394

4395+
// See comment to this function in insertdict.
44084396
if (insert_combined_dict(interp, mp, hash, Py_NewRef(key), Py_NewRef(value)) < 0) {
44094397
Py_DECREF(key);
44104398
Py_DECREF(value);
@@ -4429,12 +4417,6 @@ dict_setdefault_ref_lock_held(PyObject *d, PyObject *key, PyObject *default_valu
44294417
*result = incref_result ? Py_NewRef(value) : value;
44304418
}
44314419
return 1;
4432-
4433-
error:
4434-
if (result) {
4435-
*result = NULL;
4436-
}
4437-
return -1;
44384420
}
44394421

44404422
int

0 commit comments

Comments
 (0)