Skip to content

Commit 421ba58

Browse files
gh-132762: Fix underallocation bug in dict.fromkeys()(gh-133627)
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.
1 parent 2d82ab7 commit 421ba58

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
@@ -338,17 +338,34 @@ def __setitem__(self, key, value):
338338
self.assertRaises(Exc, baddict2.fromkeys, [1])
339339

340340
# test fast path for dictionary inputs
341+
res = dict(zip(range(6), [0]*6))
341342
d = dict(zip(range(6), range(6)))
342-
self.assertEqual(dict.fromkeys(d, 0), dict(zip(range(6), [0]*6)))
343-
343+
self.assertEqual(dict.fromkeys(d, 0), res)
344+
# test fast path for set inputs
345+
d = set(range(6))
346+
self.assertEqual(dict.fromkeys(d, 0), res)
347+
# test slow path for other iterable inputs
348+
d = list(range(6))
349+
self.assertEqual(dict.fromkeys(d, 0), res)
350+
351+
# test fast path when object's constructor returns large non-empty dict
344352
class baddict3(dict):
345353
def __new__(cls):
346354
return d
347-
d = {i : i for i in range(10)}
355+
d = {i : i for i in range(1000)}
348356
res = d.copy()
349357
res.update(a=None, b=None, c=None)
350358
self.assertEqual(baddict3.fromkeys({"a", "b", "c"}), res)
351359

360+
# test slow path when object is a proper subclass of dict
361+
class baddict4(dict):
362+
def __init__(self):
363+
dict.__init__(self, d)
364+
d = {i : i for i in range(1000)}
365+
res = d.copy()
366+
res.update(a=None, b=None, c=None)
367+
self.assertEqual(baddict4.fromkeys({"a", "b", "c"}), res)
368+
352369
def test_copy(self):
353370
d = {1: 1, 2: 2, 3: 3}
354371
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
@@ -3178,9 +3178,10 @@ dict_set_fromkeys(PyInterpreterState *interp, PyDictObject *mp,
31783178
Py_ssize_t pos = 0;
31793179
PyObject *key;
31803180
Py_hash_t hash;
3181-
3182-
if (dictresize(interp, mp,
3183-
estimate_log2_keysize(PySet_GET_SIZE(iterable)), 0)) {
3181+
uint8_t new_size = Py_MAX(
3182+
estimate_log2_keysize(PySet_GET_SIZE(iterable)),
3183+
DK_LOG_SIZE(mp->ma_keys));
3184+
if (dictresize(interp, mp, new_size, 0)) {
31843185
Py_DECREF(mp);
31853186
return NULL;
31863187
}

0 commit comments

Comments
 (0)