Skip to content

Commit 6ab4a4a

Browse files
[3.13] gh-132762: Fix underallocation bug in dict.fromkeys()(gh-133627) (gh-133686)
The function `dict_set_fromkeys()` adds elements of a set to an existing dictionary. The size of the expanded dictionary was estimated with `PySet_GET_SIZE(iterable)`, which did not take into account the size of the existing dictionary. (cherry picked from commit 421ba58) Co-authored-by: Angela Liss <[email protected]>
1 parent 8e334f4 commit 6ab4a4a

File tree

3 files changed

+25
-6
lines changed

3 files changed

+25
-6
lines changed

Lib/test/test_dict.py

+20-3
Original file line numberDiff line numberDiff line change
@@ -312,17 +312,34 @@ def __setitem__(self, key, value):
312312
self.assertRaises(Exc, baddict2.fromkeys, [1])
313313

314314
# test fast path for dictionary inputs
315+
res = dict(zip(range(6), [0]*6))
315316
d = dict(zip(range(6), range(6)))
316-
self.assertEqual(dict.fromkeys(d, 0), dict(zip(range(6), [0]*6)))
317-
317+
self.assertEqual(dict.fromkeys(d, 0), res)
318+
# test fast path for set inputs
319+
d = set(range(6))
320+
self.assertEqual(dict.fromkeys(d, 0), res)
321+
# test slow path for other iterable inputs
322+
d = list(range(6))
323+
self.assertEqual(dict.fromkeys(d, 0), res)
324+
325+
# test fast path when object's constructor returns large non-empty dict
318326
class baddict3(dict):
319327
def __new__(cls):
320328
return d
321-
d = {i : i for i in range(10)}
329+
d = {i : i for i in range(1000)}
322330
res = d.copy()
323331
res.update(a=None, b=None, c=None)
324332
self.assertEqual(baddict3.fromkeys({"a", "b", "c"}), res)
325333

334+
# test slow path when object is a proper subclass of dict
335+
class baddict4(dict):
336+
def __init__(self):
337+
dict.__init__(self, d)
338+
d = {i : i for i in range(1000)}
339+
res = d.copy()
340+
res.update(a=None, b=None, c=None)
341+
self.assertEqual(baddict4.fromkeys({"a", "b", "c"}), res)
342+
326343
def test_copy(self):
327344
d = {1: 1, 2: 2, 3: 3}
328345
self.assertIsNot(d.copy(), d)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
:meth:`~dict.fromkeys` no longer loops forever when adding a small set of keys to a large base dict. Patch by Angela Liss.

Objects/dictobject.c

+4-3
Original file line numberDiff line numberDiff line change
@@ -3057,9 +3057,10 @@ dict_set_fromkeys(PyInterpreterState *interp, PyDictObject *mp,
30573057
Py_ssize_t pos = 0;
30583058
PyObject *key;
30593059
Py_hash_t hash;
3060-
3061-
if (dictresize(interp, mp,
3062-
estimate_log2_keysize(PySet_GET_SIZE(iterable)), 0)) {
3060+
uint8_t new_size = Py_MAX(
3061+
estimate_log2_keysize(PySet_GET_SIZE(iterable)),
3062+
DK_LOG_SIZE(mp->ma_keys));
3063+
if (dictresize(interp, mp, new_size, 0)) {
30633064
Py_DECREF(mp);
30643065
return NULL;
30653066
}

0 commit comments

Comments
 (0)