https://github.com/python/cpython/commit/0bb9ef9695d30c9ca90a8773e40b05ff5073cc17
commit: 0bb9ef9695d30c9ca90a8773e40b05ff5073cc17
branch: main
author: Neil Schemenauer <[email protected]>
committer: nascheme <[email protected]>
date: 2026-11-08T14:37:43-07:00
summary:

Propagate errors from slot updates in type_setattro() (gh-161587)

Handle failures of update_slot(), since it can fail in the
default build due to non-str keys.

files:
M Lib/test/test_descr.py
M Objects/typeobject.c

diff --git a/Lib/test/test_descr.py b/Lib/test/test_descr.py
index c4c4d0858ec00b..2f5953ba096d43 100644
--- a/Lib/test/test_descr.py
+++ b/Lib/test/test_descr.py
@@ -4181,6 +4181,28 @@ def test_pow_wrapper_error_messages(self):
                                'expected 1 or 2 arguments, got 3',
                                int().__rpow__, 1, 2, 3)
 
+    def test_setattr_slot_update_error(self):
+        # An error raised while updating the slots of subclasses must be
+        # propagated by the attribute assignment.
+        class K:
+            def __hash__(self):
+                return hash('__repr__')
+            def __eq__(self, other):
+                raise ZeroDivisionError
+
+        class A:
+            pass
+
+        with self.assertWarns(RuntimeWarning):
+            B = type('B', (A,), {K(): 1})
+        if support.Py_GIL_DISABLED:
+            # The slot update skips non-str keys without comparing them.
+            A.__repr__ = lambda self: 'A'
+        else:
+            with self.assertRaises(ZeroDivisionError):
+                A.__repr__ = lambda self: 'A'
+        self.assertEqual(repr(A()), 'A')
+
     def test_mutable_bases(self):
         # Testing mutable bases...
 
diff --git a/Objects/typeobject.c b/Objects/typeobject.c
index 9833c338679d33..ee8c2090e6397d 100644
--- a/Objects/typeobject.c
+++ b/Objects/typeobject.c
@@ -6671,11 +6671,11 @@ type_update_dict(PyTypeObject *type, PyDictObject 
*dict, PyObject *name,
     return 0;
 }
 
-static void
+static int
 update_slot_after_setattr(PyTypeObject *type, PyObject *name)
 {
     types_stop_world();
-    update_slot(type, name);
+    int res = update_slot(type, name);
     types_start_world();
 #ifdef Py_GIL_DISABLED
     // Invalidate again, after the slots are updated and while TYPE_LOCK is
@@ -6686,6 +6686,7 @@ update_slot_after_setattr(PyTypeObject *type, PyObject 
*name)
     ASSERT_TYPE_LOCK_HELD();
     _PyType_Modified_Unlocked(type);
 #endif
+    return res;
 }
 
 static int
@@ -6774,7 +6775,7 @@ type_setattro(PyObject *self, PyObject *name, PyObject 
*value)
     if (res == 0) {
         if (is_dunder_name(name) && has_slotdef(name)) {
             // The name corresponds to a type slot.
-            update_slot_after_setattr(type, name);
+            res = update_slot_after_setattr(type, name);
         }
     }
     END_TYPE_DICT_LOCK();

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to