[3.14] gh-132617: Fix dict.update() mutation check (gh-134815) (gh-135581)

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 d8994b0a77)

Co-authored-by: Sam Gross <colesbury@gmail.com>
This commit is contained in:
Miss Islington (bot) 2025-06-16 19:19:58 +02:00 committed by GitHub
parent 027f58473c
commit 15f7bd4295
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 37 additions and 2 deletions

View file

@ -290,6 +290,38 @@ def badgen():
['Cannot convert dictionary update sequence element #0 to a sequence'],
)
def test_update_shared_keys(self):
class MyClass: pass
# Subclass str to enable us to create an object during the
# dict.update() call.
class MyStr(str):
def __hash__(self):
return super().__hash__()
def __eq__(self, other):
# Create an object that shares the same PyDictKeysObject as
# obj.__dict__.
obj2 = MyClass()
obj2.a = "a"
obj2.b = "b"
obj2.c = "c"
return super().__eq__(other)
obj = MyClass()
obj.a = "a"
obj.b = "b"
x = {}
x[MyStr("a")] = MyStr("a")
# gh-132617: this previously raised "dict mutated during update" error
x.update(obj.__dict__)
self.assertEqual(x, {
MyStr("a"): "a",
"b": "b",
})
def test_fromkeys(self):
self.assertEqual(dict.fromkeys('abc'), {'a':None, 'b':None, 'c':None})

View file

@ -0,0 +1,3 @@
Fix :meth:`dict.update` modification check that could incorrectly raise a
"dict mutated during update" error when a different dictionary was modified
that happens to share the same underlying keys object.

View file

@ -3858,7 +3858,7 @@ dict_dict_merge(PyInterpreterState *interp, PyDictObject *mp, PyDictObject *othe
}
}
Py_ssize_t orig_size = other->ma_keys->dk_nentries;
Py_ssize_t orig_size = other->ma_used;
Py_ssize_t pos = 0;
Py_hash_t hash;
PyObject *key, *value;
@ -3892,7 +3892,7 @@ dict_dict_merge(PyInterpreterState *interp, PyDictObject *mp, PyDictObject *othe
if (err != 0)
return -1;
if (orig_size != other->ma_keys->dk_nentries) {
if (orig_size != other->ma_used) {
PyErr_SetString(PyExc_RuntimeError,
"dict mutated during update");
return -1;