8000 [3.13] gh-132617: Fix `dict.update()` mutation check (gh-134815) (gh-… · python/cpython@f33a5e8 · GitHub
[go: up one dir, main page]

Skip to content

Commit f33a5e8

Browse files
authored
[3.13] gh-132617: Fix dict.update() mutation check (gh-134815) (gh-135582)
Use `ma_used` instead of `ma_keys->dk_nentries` for modification check so that we only check if the dictionary is modified, not if new keys are added to a different dictionary that shared the same keys object. (cherry picked from commit d8994b0)
1 parent 404e8aa commit f33a5e8

File tree

3 files changed

+38
-2
lines changed

3 files changed

+38
-2
lines changed

Lib/test/test_dict.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,39 @@ def __next__(self):
265265

266266
self.assertRaises(ValueError, {}.update, [(1, 2, 3)])
267267

268+
def test_update_shared_keys(self):
269+
class MyClass: pass
270+
271+
# Subclass str to enable us to create an object during the
272+
# dict.update() call.
273+
class MyStr(str):
274+
def __hash__(self):
275+
return super().__hash__()
276+
277+
def __eq__(self, other):
278+
# Create an object that shares the same PyDictKeysObject as
279+
# obj.__dict__.
280+
obj2 = MyClass()
281+
obj2.a = "a"
282+
obj2.b = "b"
283+
obj2.c = "c"
284+
return super().__eq__(other)
285+
286+
obj = MyClass()
287+
obj.a = "a"
288+
obj.b = "b"
289+
290+
x = {}
291+
x[MyStr("a")] = MyStr("a")
292+
293+
# gh-132617: this previously raised "dict mutated during update" error
294+
x.update(obj.__dict__)
295+
296+
self.assertEqual(x, {
297+
MyStr("a"): "a",
298+
"b": "b",
299+
})
300+
268301
def test_fromkeys(self):
269302
self.assertEqual(dict.fromkeys('abc'), {'a':None, 'b':None, 'c':None})
270303
d = {}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix :meth:`dict.update` modification check that could incorrectly raise a
2+
"dict mutated during update" error when a different dictionary was modified
3+
that happens to share the same underlying keys object.

Objects/dictobject.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3782,7 +3782,7 @@ dict_dict_merge(PyInterpreterState *interp, PyDictObject *mp, PyDictObject *othe
37823782
}
37833783
}
37843784

3785-
Py_ssize_t orig_size = other->ma_keys->dk_nentries;
3785+
Py_ssize_t orig_size = other->ma_used;
37863786
Py_ssize_t pos = 0;
37873787
Py_hash_t hash;
37883788
PyObject *key, *value;
@@ -3816,7 +3816,7 @@ dict_dict_merge(PyInterpreterState *interp, PyDictObject *mp, PyDictObject *othe
38163816
if (err != 0)
38173817
return -1;
38183818

3819-
if (orig_size != other->ma_keys->dk_nentries) {
3819+
if (orig_size != other->ma_used) {
38203820
PyErr_SetString(PyExc_RuntimeError,
38213821
"dict mutated during update");
38223822
return -1;

0 commit comments

Comments
 (0)
0