]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.13] gh-132617: Fix `dict.update()` mutation check (gh-134815) (gh-135582)
authorSam Gross <colesbury@gmail.com>
Mon, 16 Jun 2025 17:30:52 +0000 (13:30 -0400)
committerGitHub <noreply@github.com>
Mon, 16 Jun 2025 17:30:52 +0000 (17:30 +0000)
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 d8994b0a77cc9821772d05db00a6ab23382fa17d)

Lib/test/test_dict.py
Misc/NEWS.d/next/Core and Builtins/2025-05-27-20-29-00.gh-issue-132617.EmUfQQ.rst [new file with mode: 0644]
Objects/dictobject.c

index 4729132c5a5f84edf9df7ed47bdb96c2d85aced2..4c095464cbbc549c19da9bbe40016471c900c17c 100644 (file)
@@ -265,6 +265,39 @@ class DictTest(unittest.TestCase):
 
         self.assertRaises(ValueError, {}.update, [(1, 2, 3)])
 
+    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})
         d = {}
diff --git a/Misc/NEWS.d/next/Core and Builtins/2025-05-27-20-29-00.gh-issue-132617.EmUfQQ.rst b/Misc/NEWS.d/next/Core and Builtins/2025-05-27-20-29-00.gh-issue-132617.EmUfQQ.rst
new file mode 100644 (file)
index 0000000..53aef54
--- /dev/null
@@ -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.
index d135ea5f3db81b82c7383ea4b701392bcc53a500..baac54911f0371fb6123208f1ea9cbf87a891398 100644 (file)
@@ -3782,7 +3782,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;
@@ -3816,7 +3816,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;