https://github.com/python/cpython/commit/e178a13f2f79aa8c1cd9c940fb1962c65cee3e24
commit: e178a13f2f79aa8c1cd9c940fb1962c65cee3e24
branch: 3.13
author: Serhiy Storchaka <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2026-08-21T16:00:39Z
summary:

[3.13] gh-156196: Fix a failed assignment to a PyMemberDef attribute changing 
its value (GH-156197)

PyMember_SetOne() stored the result of the conversion before checking it
for an error.

files:
A 
Misc/NEWS.d/next/Core_and_Builtins/2026-08-21-18-00-00.gh-issue-156196.Mv7kBq.rst
M Lib/test/test_capi/test_structmembers.py
M Python/structmember.c

diff --git a/Lib/test/test_capi/test_structmembers.py 
b/Lib/test/test_capi/test_structmembers.py
index 08ca1f828529cf2..0a4e4d935658155 100644
--- a/Lib/test/test_capi/test_structmembers.py
+++ b/Lib/test/test_capi/test_structmembers.py
@@ -60,7 +60,10 @@ def _test_warn(self, name, value, expected=None):
 
     def _test_overflow(self, name, value):
         ts = self.ts
+        oldvalue = getattr(ts, name)
         self.assertRaises(OverflowError, setattr, ts, name, value)
+        # a failed assignment does not change the value
+        self.assertEqual(getattr(ts, name), oldvalue)
 
     def _test_int_range(self, name, minval, maxval, *, hardlimit=None,
                         indexlimit=None):
@@ -154,8 +157,11 @@ def test_bad_assignments(self):
         # issue8014: this produced 'bad argument to internal function'
         # internal error
         for nonint in None, 3.2j, "full of eels", {}, []:
-            for attr in integer_attributes:
+            for attr in integer_attributes + ['T_FLOAT', 'T_DOUBLE']:
+                oldvalue = getattr(ts, attr)
                 self.assertRaises(TypeError, setattr, ts, attr, nonint)
+                # a failed assignment does not change the value
+                self.assertEqual(getattr(ts, attr), oldvalue)
 
     def test_inplace_string(self):
         ts = self.ts
diff --git 
a/Misc/NEWS.d/next/Core_and_Builtins/2026-08-21-18-00-00.gh-issue-156196.Mv7kBq.rst
 
b/Misc/NEWS.d/next/Core_and_Builtins/2026-08-21-18-00-00.gh-issue-156196.Mv7kBq.rst
new file mode 100644
index 000000000000000..65d824884eccaaf
--- /dev/null
+++ 
b/Misc/NEWS.d/next/Core_and_Builtins/2026-08-21-18-00-00.gh-issue-156196.Mv7kBq.rst
@@ -0,0 +1,3 @@
+A failed assignment to an attribute defined with :c:type:`PyMemberDef` of
+type ``Py_T_LONG``, ``Py_T_LONGLONG``, ``Py_T_PYSSIZET`` or ``Py_T_DOUBLE``
+no longer changes its value.
diff --git a/Python/structmember.c b/Python/structmember.c
index d5e7ab83093dc86..58caa03c31d3029 100644
--- a/Python/structmember.c
+++ b/Python/structmember.c
@@ -251,9 +251,10 @@ PyMember_SetOne(char *addr, PyMemberDef *l, PyObject *v)
         break;
     }
     case Py_T_LONG:{
-        *(long*)addr = PyLong_AsLong(v);
-        if ((*(long*)addr == -1) && PyErr_Occurred())
+        long long_val = PyLong_AsLong(v);
+        if ((long_val == -1) && PyErr_Occurred())
             return -1;
+        *(long*)addr = long_val;
         break;
         }
     case Py_T_ULONG: {
@@ -283,10 +284,10 @@ PyMember_SetOne(char *addr, PyMemberDef *l, PyObject *v)
         break;
     }
     case Py_T_PYSSIZET:{
-        *(Py_ssize_t*)addr = PyLong_AsSsize_t(v);
-        if ((*(Py_ssize_t*)addr == (Py_ssize_t)-1)
-            && PyErr_Occurred())
-                        return -1;
+        Py_ssize_t ssize_val = PyLong_AsSsize_t(v);
+        if ((ssize_val == (Py_ssize_t)-1) && PyErr_Occurred())
+            return -1;
+        *(Py_ssize_t*)addr = ssize_val;
         break;
         }
     case Py_T_FLOAT:{
@@ -296,11 +297,13 @@ PyMember_SetOne(char *addr, PyMemberDef *l, PyObject *v)
         *(float*)addr = (float)double_val;
         break;
         }
-    case Py_T_DOUBLE:
-        *(double*)addr = PyFloat_AsDouble(v);
-        if ((*(double*)addr == -1) && PyErr_Occurred())
+    case Py_T_DOUBLE:{
+        double double_val = PyFloat_AsDouble(v);
+        if ((double_val == -1) && PyErr_Occurred())
             return -1;
+        *(double*)addr = double_val;
         break;
+        }
     case _Py_T_OBJECT:
     case Py_T_OBJECT_EX:
         Py_BEGIN_CRITICAL_SECTION(obj);
@@ -326,10 +329,10 @@ PyMember_SetOne(char *addr, PyMemberDef *l, PyObject *v)
         PyErr_SetString(PyExc_TypeError, "readonly attribute");
         return -1;
     case Py_T_LONGLONG:{
-        long long value;
-        *(long long*)addr = value = PyLong_AsLongLong(v);
+        long long value = PyLong_AsLongLong(v);
         if ((value == -1) && PyErr_Occurred())
             return -1;
+        *(long long*)addr = value;
         break;
         }
     case Py_T_ULONGLONG: {

_______________________________________________
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