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

serhiy-storchaka <[email protected]>
Newsgroups gmane.comp.python.cvs
Message-ID <[email protected]>
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]
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.