From d448889b94509784c2c8d2219ad4d63790d9d462 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Thu, 24 Sep 2026 11:10:41 +0200 Subject: [PATCH] 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.