From d448889b94509784c2c8d2219ad4d63790d9d462 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 24 Sep 2026 11:10:41 +0200 Subject: [PATCH 1/5] gh-157649: Fix Py_CLEAR()/Py_SETREF() on C++ On C++, do not use decltype() in _Py_TYPEOF since it produces invalid code in Py_CLEAR() and Py_SETREF(). Instead, implement Py_CLEAR() and Py_SETREF() using "auto" on C++11 and newer. Add Py_CLEAR() and Py_SETREF() tests on an array. --- Include/cpython/object.h | 10 +++++++++- Include/pyport.h | 12 +++++------- Include/refcount.h | 15 ++++++++++++++- Lib/test/test_cext/extension.c | 11 +++++++++++ Lib/test/test_cppext/extension.cpp | 14 ++++++++++++-- ...2026-09-24-11-47-32.gh-issue-157649.3HSU2h.rst | 4 ++++ 6 files changed, 55 insertions(+), 11 deletions(-) create mode 100644 Misc/NEWS.d/next/C_API/2026-09-24-11-47-32.gh-issue-157649.3HSU2h.rst diff --git a/Include/cpython/object.h b/Include/cpython/object.h index 0ef52d4d2bc7b4..20a23eb0cbbb5c 100644 --- a/Include/cpython/object.h +++ b/Include/cpython/object.h @@ -349,7 +349,15 @@ PyAPI_FUNC(PyObject *) _PyObject_FunctionStr(PyObject *); * 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'. */ -#ifdef _Py_TYPEOF +#if 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) +#elif defined(_Py_TYPEOF) #define Py_SETREF(dst, src) \ do { \ _Py_TYPEOF(&(dst)) _tmp_dst_ptr = &(dst); \ diff --git a/Include/pyport.h b/Include/pyport.h index 744bae6c57e299..a81559fb787c25 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)) # define _Py_TYPEOF(expr) __typeof__(expr) #endif diff --git a/Include/refcount.h b/Include/refcount.h index d96c75421aef33..3c268241253226 100644 --- a/Include/refcount.h +++ b/Include/refcount.h @@ -478,8 +478,21 @@ 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 +#if 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) +#elif defined(_Py_TYPEOF) #define Py_CLEAR(op) \ do { \ _Py_TYPEOF(&(op)) _tmp_op_ptr = &(op); \ diff --git a/Lib/test/test_cext/extension.c b/Lib/test/test_cext/extension.c index 0a26a0d8753711..286875a23f7569 100644 --- a/Lib/test/test_cext/extension.c +++ b/Lib/test/test_cext/extension.c @@ -80,6 +80,7 @@ static int _testcext_exec(PyObject *module) { PyObject *result, *obj; + PyObject *slots[1]; #ifdef __STDC_VERSION__ if (PyModule_AddIntMacro(module, __STDC_VERSION__) < 0) { @@ -105,11 +106,21 @@ _testcext_exec(PyObject *module) Py_CLEAR(obj); assert(obj == 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, NULL); assert(obj == NULL); + + // gh-157649: Test Py_SETREF() on an array + slots[0] = Py_None; + Py_SETREF(slots[0], _Py_NULL); + assert(slots[0] == _Py_NULL); #endif // Test that Py_BEGIN_CRITICAL_SECTION is available diff --git a/Lib/test/test_cppext/extension.cpp b/Lib/test/test_cppext/extension.cpp index 1ff56d0e7fd25a..73fdd43a0436f8 100644 --- a/Lib/test/test_cppext/extension.cpp +++ b/Lib/test/test_cppext/extension.cpp @@ -307,16 +307,26 @@ _testcppext_exec(PyObject *module) assert(Py_MAX(5, 11) == 11); assert(Py_ABS(-5) == 5); - // Test Py_CLEAR(): use typeof()/__typeof__() if available, or memcpy() + // Test Py_CLEAR(): use auto on C++11, or memcpy() PyObject *obj = Py_None; Py_CLEAR(obj); assert(obj == _Py_NULL); + // gh-157649: Test Py_CLEAR() on an array + PyObject *slots[1] = {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() + // Test Py_SETREF(): use auto on C++11, 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); #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 00000000000000..92eafbdbe39641 --- /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. From 46fc0b4740d2e74c864d1bc22ce1410daeb85420 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Mon, 28 Sep 2026 15:22:14 +0200 Subject: [PATCH 2/5] Fix Py_XSETREF() --- Include/cpython/object.h | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Include/cpython/object.h b/Include/cpython/object.h index 0cc705505529e6..cc69efc519bffb 100644 --- a/Include/cpython/object.h +++ b/Include/cpython/object.h @@ -379,7 +379,15 @@ PyAPI_FUNC(PyObject *) _PyObject_FunctionStr(PyObject *); /* Py_XSETREF() is a variant of Py_SETREF() that uses Py_XDECREF() instead of * Py_DECREF(). */ -#ifdef _Py_TYPEOF +#if 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) +#elif defined(_Py_TYPEOF) #define Py_XSETREF(dst, src) \ do { \ _Py_TYPEOF(&(dst)) _tmp_dst_ptr = &(dst); \ From 8c8df170ab8e1e54e6646e9085029134156e89da Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Mon, 28 Sep 2026 15:23:51 +0200 Subject: [PATCH 3/5] Don't use __typeof__() on MSVC for C++ --- Include/pyport.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Include/pyport.h b/Include/pyport.h index a81559fb787c25..9cfdd09689d5c8 100644 --- a/Include/pyport.h +++ b/Include/pyport.h @@ -546,7 +546,7 @@ extern "C" { #if defined (__STDC_VERSION__) && __STDC_VERSION__ >= 202311L # define _Py_TYPEOF(expr) typeof(expr) #elif (defined(__GNUC__) || defined(__clang__) \ - || (defined(_MSC_VER) && _MSC_VER >= 1939)) + || (defined(_MSC_VER) && _MSC_VER >= 1939 && !defined(__cplusplus))) # define _Py_TYPEOF(expr) __typeof__(expr) #endif From 27f1d3e7169197298a835229a503c8735d6d5ef1 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Mon, 28 Sep 2026 15:28:41 +0200 Subject: [PATCH 4/5] Prefer typeof() over C++ auto --- Include/cpython/object.h | 30 +++++++++++++++--------------- Include/refcount.h | 12 ++++++------ 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/Include/cpython/object.h b/Include/cpython/object.h index cc69efc519bffb..7624ed3f8b134d 100644 --- a/Include/cpython/object.h +++ b/Include/cpython/object.h @@ -342,26 +342,26 @@ 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'. */ -#if defined(__cplusplus) && (__cplusplus >= 201103L || _MSVC_LANG >= 201103L) +#ifdef _Py_TYPEOF #define Py_SETREF(dst, src) \ do { \ - auto _tmp_dst_ptr = &(dst); \ - auto _tmp_old_dst = (*_tmp_dst_ptr); \ + _Py_TYPEOF(&(dst)) _tmp_dst_ptr = &(dst); \ + _Py_TYPEOF(dst) _tmp_old_dst = (*_tmp_dst_ptr); \ *_tmp_dst_ptr = (src); \ Py_DECREF(_tmp_old_dst); \ } while (0) -#elif defined(_Py_TYPEOF) +#elif defined(__cplusplus) && (__cplusplus >= 201103L || _MSVC_LANG >= 201103L) #define Py_SETREF(dst, src) \ do { \ - _Py_TYPEOF(&(dst)) _tmp_dst_ptr = &(dst); \ - _Py_TYPEOF(dst) _tmp_old_dst = (*_tmp_dst_ptr); \ + auto _tmp_dst_ptr = &(dst); \ + auto _tmp_old_dst = (*_tmp_dst_ptr); \ *_tmp_dst_ptr = (src); \ Py_DECREF(_tmp_old_dst); \ } while (0) @@ -379,19 +379,19 @@ PyAPI_FUNC(PyObject *) _PyObject_FunctionStr(PyObject *); /* Py_XSETREF() is a variant of Py_SETREF() that uses Py_XDECREF() instead of * Py_DECREF(). */ -#if defined(__cplusplus) && (__cplusplus >= 201103L || _MSVC_LANG >= 201103L) +#if defined(_Py_TYPEOF) #define Py_XSETREF(dst, src) \ do { \ - auto _tmp_dst_ptr = &(dst); \ - auto _tmp_old_dst = (*_tmp_dst_ptr); \ + _Py_TYPEOF(&(dst)) _tmp_dst_ptr = &(dst); \ + _Py_TYPEOF(dst) _tmp_old_dst = (*_tmp_dst_ptr); \ *_tmp_dst_ptr = (src); \ Py_XDECREF(_tmp_old_dst); \ } while (0) -#elif defined(_Py_TYPEOF) +#elif defined(__cplusplus) && (__cplusplus >= 201103L || _MSVC_LANG >= 201103L) #define Py_XSETREF(dst, src) \ do { \ - _Py_TYPEOF(&(dst)) _tmp_dst_ptr = &(dst); \ - _Py_TYPEOF(dst) _tmp_old_dst = (*_tmp_dst_ptr); \ + auto _tmp_dst_ptr = &(dst); \ + auto _tmp_old_dst = (*_tmp_dst_ptr); \ *_tmp_dst_ptr = (src); \ Py_XDECREF(_tmp_old_dst); \ } while (0) diff --git a/Include/refcount.h b/Include/refcount.h index 3c268241253226..39661680e85a18 100644 --- a/Include/refcount.h +++ b/Include/refcount.h @@ -482,21 +482,21 @@ static inline Py_ALWAYS_INLINE void Py_DECREF(PyObject *op) * On C++11 and newer, use "auto". On MSVC, check also _MSVC_LANG since * __cplusplus is 199711L unless the /Zc:__cplusplus flag is used. */ -#if defined(__cplusplus) && (__cplusplus >= 201103L || _MSVC_LANG >= 201103L) +#ifdef _Py_TYPEOF #define Py_CLEAR(op) \ do { \ - auto _tmp_op_ptr = &(op); \ - auto _tmp_old_op = (*_tmp_op_ptr); \ + _Py_TYPEOF(&(op)) _tmp_op_ptr = &(op); \ + _Py_TYPEOF(op) _tmp_old_op = (*_tmp_op_ptr); \ if (_tmp_old_op != _Py_NULL) { \ *_tmp_op_ptr = _Py_NULL; \ Py_DECREF(_tmp_old_op); \ } \ } while (0) -#elif defined(_Py_TYPEOF) +#elif defined(__cplusplus) && (__cplusplus >= 201103L || _MSVC_LANG >= 201103L) #define Py_CLEAR(op) \ do { \ - _Py_TYPEOF(&(op)) _tmp_op_ptr = &(op); \ - _Py_TYPEOF(op) _tmp_old_op = (*_tmp_op_ptr); \ + 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); \ From 9af8d8df165df83c62c87d021d07eca678614587 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Mon, 28 Sep 2026 19:31:16 +0200 Subject: [PATCH 5/5] Restore #ifdef --- Include/cpython/object.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Include/cpython/object.h b/Include/cpython/object.h index 7624ed3f8b134d..5d35f983d2833c 100644 --- a/Include/cpython/object.h +++ b/Include/cpython/object.h @@ -379,7 +379,7 @@ PyAPI_FUNC(PyObject *) _PyObject_FunctionStr(PyObject *); /* Py_XSETREF() is a variant of Py_SETREF() that uses Py_XDECREF() instead of * Py_DECREF(). */ -#if defined(_Py_TYPEOF) +#ifdef _Py_TYPEOF #define Py_XSETREF(dst, src) \ do { \ _Py_TYPEOF(&(dst)) _tmp_dst_ptr = &(dst); \