https://github.com/python/cpython/commit/333071231d3a46cccc32d7f44b99328c3299d0b1
commit: 333071231d3a46cccc32d7f44b99328c3299d0b1
branch: main
author: Victor Stinner <[email protected]>
committer: vstinner <[email protected]>
date: 2026-09-28T19:18:28Z
summary:

gh-157649: Fix Py_CLEAR()/Py_SETREF() on C++ (#158070)

On C++, do not use decltype() in _Py_TYPEOF since it produces invalid
code in Py_CLEAR() and Py_SETREF(). Instead, implement Py_CLEAR(),
Py_SETREF() and Py_XSETREF() using "auto" on C++11 and newer.

Add Py_CLEAR(), Py_SETREF() and Py_XSETREF() tests on an array.

files:
A Misc/NEWS.d/next/C_API/2026-09-24-11-47-32.gh-issue-157649.3HSU2h.rst
M Include/cpython/object.h
M Include/pyport.h
M Include/refcount.h
M Lib/test/test_cext/extension.c

diff --git a/Include/cpython/object.h b/Include/cpython/object.h
index c0fcfc363ef90c9..5d35f983d2833c3 100644
--- a/Include/cpython/object.h
+++ b/Include/cpython/object.h
@@ -342,9 +342,9 @@ PyAPI_FUNC(PyObject *) _PyObject_FunctionStr(PyObject *);
  * `dst` points to a valid object.
  *
  * Temporary variables are used to only evaluate macro arguments once and so
- * avoid the duplication of side effects. _Py_TYPEOF() or memcpy() is used to
- * avoid a miscompilation caused by type punning. See Py_CLEAR() comment for
- * implementation details about type punning.
+ * avoid the duplication of side effects. _Py_TYPEOF(), C++ auto, or memcpy()
+ * is used to avoid a miscompilation caused by type punning. See Py_CLEAR()
+ * comment for implementation details about type punning.
  *
  * The memcpy() implementation does not emit a compiler warning if 'src' has
  * not the same type than 'src': any pointer type is accepted for 'src'.
@@ -357,6 +357,14 @@ PyAPI_FUNC(PyObject *) _PyObject_FunctionStr(PyObject *);
         *_tmp_dst_ptr = (src); \
         Py_DECREF(_tmp_old_dst); \
     } while (0)
+#elif defined(__cplusplus) && (__cplusplus >= 201103L ||  _MSVC_LANG >= 
201103L)
+#define Py_SETREF(dst, src) \
+    do { \
+        auto _tmp_dst_ptr = &(dst); \
+        auto _tmp_old_dst = (*_tmp_dst_ptr); \
+        *_tmp_dst_ptr = (src); \
+        Py_DECREF(_tmp_old_dst); \
+    } while (0)
 #else
 #define Py_SETREF(dst, src) \
     do { \
@@ -379,6 +387,14 @@ PyAPI_FUNC(PyObject *) _PyObject_FunctionStr(PyObject *);
         *_tmp_dst_ptr = (src); \
         Py_XDECREF(_tmp_old_dst); \
     } while (0)
+#elif defined(__cplusplus) && (__cplusplus >= 201103L ||  _MSVC_LANG >= 
201103L)
+#define Py_XSETREF(dst, src) \
+    do { \
+        auto _tmp_dst_ptr = &(dst); \
+        auto _tmp_old_dst = (*_tmp_dst_ptr); \
+        *_tmp_dst_ptr = (src); \
+        Py_XDECREF(_tmp_old_dst); \
+    } while (0)
 #else
 #define Py_XSETREF(dst, src) \
     do { \
diff --git a/Include/pyport.h b/Include/pyport.h
index 744bae6c57e299e..9cfdd09689d5c81 100644
--- a/Include/pyport.h
+++ b/Include/pyport.h
@@ -538,17 +538,15 @@ extern "C" {
 //
 // Example: _Py_TYPEOF(x) x_copy = (x);
 //
-// On C23, use typeof(). On C++11, use decltype(). Otherwise, use __typeof__()
+// On C23, use typeof(). Otherwise, use __typeof__()
 // if on GCC, clang or MSVC 17.9 and newer.
 //
-// On MSVC, check also _MSVC_LANG since __cplusplus is 199711L unless
-// the /Zc:__cplusplus flag is used.
+// gh-157649: Do not use decltype() on C++, since it produces invalid code in
+// Py_CLEAR()/Py_SETREF().
 #if defined (__STDC_VERSION__) && __STDC_VERSION__ >= 202311L
 #  define _Py_TYPEOF(expr) typeof(expr)
-#elif defined(__cplusplus) && (__cplusplus >= 201103L ||  _MSVC_LANG >= 
201103L)
-#  define _Py_TYPEOF(expr) decltype(expr)
-#elif defined(__GNUC__) || defined(__clang__) || \
-    (defined(_MSC_VER) && _MSC_VER >= 1939)
+#elif (defined(__GNUC__) || defined(__clang__) \
+       || (defined(_MSC_VER) && _MSC_VER >= 1939 && !defined(__cplusplus)))
 #  define _Py_TYPEOF(expr) __typeof__(expr)
 #endif
 
diff --git a/Include/refcount.h b/Include/refcount.h
index d96c75421aef337..39661680e85a183 100644
--- a/Include/refcount.h
+++ b/Include/refcount.h
@@ -478,6 +478,9 @@ static inline Py_ALWAYS_INLINE void Py_DECREF(PyObject *op)
  * and so avoid type punning. Otherwise, use memcpy() which causes type erasure
  * and so prevents the compiler to reuse an old cached 'op' value after
  * Py_CLEAR().
+ *
+ * On C++11 and newer, use "auto". On MSVC, check also _MSVC_LANG since
+ * __cplusplus is 199711L unless the /Zc:__cplusplus flag is used.
  */
 #ifdef _Py_TYPEOF
 #define Py_CLEAR(op) \
@@ -489,6 +492,16 @@ static inline Py_ALWAYS_INLINE void Py_DECREF(PyObject *op)
             Py_DECREF(_tmp_old_op); \
         } \
     } while (0)
+#elif defined(__cplusplus) && (__cplusplus >= 201103L ||  _MSVC_LANG >= 
201103L)
+#define Py_CLEAR(op) \
+    do { \
+        auto _tmp_op_ptr = &(op); \
+        auto _tmp_old_op = (*_tmp_op_ptr); \
+        if (_tmp_old_op != _Py_NULL) { \
+            *_tmp_op_ptr = _Py_NULL; \
+            Py_DECREF(_tmp_old_op); \
+        } \
+    } while (0)
 #else
 #define Py_CLEAR(op) \
     do { \
diff --git a/Lib/test/test_cext/extension.c b/Lib/test/test_cext/extension.c
index b56c4dbe78a3d85..ea7a1ca3c38a84d 100644
--- a/Lib/test/test_cext/extension.c
+++ b/Lib/test/test_cext/extension.c
@@ -82,6 +82,7 @@ static PyObject *
 test_macros(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
 {
     PyObject *obj, *dict;
+    PyObject *slots[1];
 
     // test Py_BUILD_ASSERT() and Py_BUILD_ASSERT_EXPR()
     Py_BUILD_ASSERT(sizeof(int) == sizeof(unsigned int));
@@ -97,16 +98,31 @@ test_macros(PyObject *Py_UNUSED(module), PyObject 
*Py_UNUSED(args))
     Py_CLEAR(obj);
     assert(obj == _Py_NULL);
 
+    // gh-157649: Test Py_CLEAR() on an array
+    slots[0] = Py_None;
+    Py_CLEAR(slots[0]);
+    assert(slots[0] == _Py_NULL);
+
 #ifndef Py_LIMITED_API
     // Test Py_SETREF(): use typeof()/__typeof__() if available, or memcpy()
     obj = Py_None;
     Py_SETREF(obj, _Py_NULL);
     assert(obj == _Py_NULL);
 
+    // gh-157649: Test Py_SETREF() on an array
+    slots[0] = Py_None;
+    Py_SETREF(slots[0], _Py_NULL);
+    assert(slots[0] == _Py_NULL);
+
     // Test Py_XSETREF(): use typeof()/__typeof__() if available, or memcpy()
     obj = Py_None;
     Py_XSETREF(obj, _Py_NULL);
     assert(obj == _Py_NULL);
+
+    // gh-157649: Test Py_XSETREF() on an array
+    slots[0] = Py_None;
+    Py_XSETREF(slots[0], _Py_NULL);
+    assert(slots[0] == _Py_NULL);
 #endif
 
     // Test that Py_BEGIN_CRITICAL_SECTION is available
diff --git 
a/Misc/NEWS.d/next/C_API/2026-09-24-11-47-32.gh-issue-157649.3HSU2h.rst 
b/Misc/NEWS.d/next/C_API/2026-09-24-11-47-32.gh-issue-157649.3HSU2h.rst
new file mode 100644
index 000000000000000..92eafbdbe39641c
--- /dev/null
+++ b/Misc/NEWS.d/next/C_API/2026-09-24-11-47-32.gh-issue-157649.3HSU2h.rst
@@ -0,0 +1,4 @@
+Fix :c:macro:`Py_CLEAR` and :c:macro:`Py_SETREF` macros on C++: implement
+them using ``auto`` instead of ``decltype()``. Using  ``decltype()``
+produced invalid code when clearing/setting an array item. Patch by Victor
+Stinner.

_______________________________________________
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