https://github.com/python/cpython/commit/ec14f81e74272424778002d5d26002338796e09e
commit: ec14f81e74272424778002d5d26002338796e09e
branch: main
author: Victor Stinner <[email protected]>
committer: vstinner <[email protected]>
date: 2026-10-06T21:13:49+02:00
summary:

gh-158842: Add Py_SETREF() and Py_XSETREF() to the limited C API (#158843)

Test these macros with the limited C API in _testlimitedcapi and
test_cext.

Move also other tests (refcount, Py_CLEAR, etc.) to _testlimitedcapi.

files:
A Misc/NEWS.d/next/C_API/2026-10-05-15-24-22.gh-issue-158842.g7eLtE.rst
M Doc/data/stable_abi.dat
M Doc/whatsnew/3.16.rst
M Include/cpython/object.h
M Include/refcount.h
M Lib/test/test_cext/extension.c
M Misc/stable_abi.toml
M Modules/_testcapi/object.c
M Modules/_testlimitedcapi/object.c

diff --git a/Doc/data/stable_abi.dat b/Doc/data/stable_abi.dat
index 82447cc98cd8d03..622a079ee6b4f22 100644
--- a/Doc/data/stable_abi.dat
+++ b/Doc/data/stable_abi.dat
@@ -986,6 +986,7 @@ func,Py_REFCNT,3.14,,
 macro,Py_RELATIVE_OFFSET,3.12,,
 func,Py_ReprEnter,3.2,,
 func,Py_ReprLeave,3.2,,
+macro,Py_SETREF,3.16,,
 func,Py_SET_SIZE,3.15,,
 func,Py_SIZE,3.15,,
 func,Py_SetRecursionLimit,3.2,,
@@ -1020,6 +1021,7 @@ macro,Py_UNBLOCK_THREADS,3.2,,
 func,Py_VaBuildValue,3.2,,
 data,Py_Version,3.11,,
 func,Py_XNewRef,3.10,,
+macro,Py_XSETREF,3.16,,
 macro,Py_am_aiter,3.5,,
 macro,Py_am_anext,3.5,,
 macro,Py_am_await,3.5,,
diff --git a/Doc/whatsnew/3.16.rst b/Doc/whatsnew/3.16.rst
index 524252cab87936a..759651d61359812 100644
--- a/Doc/whatsnew/3.16.rst
+++ b/Doc/whatsnew/3.16.rst
@@ -1186,6 +1186,10 @@ New features
 * Add :c:func:`Py_HashBuffer` to the limited C API.
   (Contributed by Victor Stinner in :gh:`148233`.)
 
+* Add :c:macro:`Py_SETREF` and :c:macro:`Py_XSETREF` macros to the limited C
+  API.
+  (Contributed by Nathan Goldbaum and Victor Stinner in :gh:`158842`.)
+
 Porting to Python 3.16
 ----------------------
 
diff --git a/Include/cpython/object.h b/Include/cpython/object.h
index 5d35f983d2833c3..12d2e614623e30b 100644
--- a/Include/cpython/object.h
+++ b/Include/cpython/object.h
@@ -326,87 +326,6 @@ _PyObject_GenericSetAttrWithDict(PyObject *, PyObject *,
 
 PyAPI_FUNC(PyObject *) _PyObject_FunctionStr(PyObject *);
 
-/* Safely decref `dst` and set `dst` to `src`.
- *
- * As in case of Py_CLEAR "the obvious" code can be deadly:
- *
- *     Py_DECREF(dst);
- *     dst = src;
- *
- * The safe way is:
- *
- *      Py_SETREF(dst, src);
- *
- * That arranges to set `dst` to `src` _before_ decref'ing, so that any code
- * triggered as a side-effect of `dst` getting torn down no longer believes
- * `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(), 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'.
- */
-#ifdef _Py_TYPEOF
-#define Py_SETREF(dst, src) \
-    do { \
-        _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(__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 { \
-        PyObject **_tmp_dst_ptr = _Py_CAST(PyObject**, &(dst)); \
-        PyObject *_tmp_old_dst = (*_tmp_dst_ptr); \
-        PyObject *_tmp_src = _PyObject_CAST(src); \
-        memcpy(_tmp_dst_ptr, &_tmp_src, sizeof(PyObject*)); \
-        Py_DECREF(_tmp_old_dst); \
-    } while (0)
-#endif
-
-/* Py_XSETREF() is a variant of Py_SETREF() that uses Py_XDECREF() instead of
- * Py_DECREF().
- */
-#ifdef _Py_TYPEOF
-#define Py_XSETREF(dst, src) \
-    do { \
-        _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(__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 { \
-        PyObject **_tmp_dst_ptr = _Py_CAST(PyObject**, &(dst)); \
-        PyObject *_tmp_old_dst = (*_tmp_dst_ptr); \
-        PyObject *_tmp_src = _PyObject_CAST(src); \
-        memcpy(_tmp_dst_ptr, &_tmp_src, sizeof(PyObject*)); \
-        Py_XDECREF(_tmp_old_dst); \
-    } while (0)
-#endif
-
-
 /* Define a pair of assertion macros:
    _PyObject_ASSERT_FROM(), _PyObject_ASSERT_WITH_MSG() and _PyObject_ASSERT().
 
diff --git a/Include/refcount.h b/Include/refcount.h
index 39661680e85a183..afbecd48ab31a1b 100644
--- a/Include/refcount.h
+++ b/Include/refcount.h
@@ -516,6 +516,87 @@ static inline Py_ALWAYS_INLINE void Py_DECREF(PyObject *op)
 #endif
 
 
+/* Safely decref `dst` and set `dst` to `src`.
+ *
+ * As in case of Py_CLEAR "the obvious" code can be deadly:
+ *
+ *     Py_DECREF(dst);
+ *     dst = src;
+ *
+ * The safe way is:
+ *
+ *      Py_SETREF(dst, src);
+ *
+ * That arranges to set `dst` to `src` _before_ decref'ing, so that any code
+ * triggered as a side-effect of `dst` getting torn down no longer believes
+ * `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(), 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'.
+ */
+#ifdef _Py_TYPEOF
+#define Py_SETREF(dst, src) \
+    do { \
+        _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(__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 { \
+        PyObject **_tmp_dst_ptr = _Py_CAST(PyObject**, &(dst)); \
+        PyObject *_tmp_old_dst = (*_tmp_dst_ptr); \
+        PyObject *_tmp_src = _PyObject_CAST(src); \
+        memcpy(_tmp_dst_ptr, &_tmp_src, sizeof(PyObject*)); \
+        Py_DECREF(_tmp_old_dst); \
+    } while (0)
+#endif
+
+/* Py_XSETREF() is a variant of Py_SETREF() that uses Py_XDECREF() instead of
+ * Py_DECREF().
+ */
+#ifdef _Py_TYPEOF
+#define Py_XSETREF(dst, src) \
+    do { \
+        _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(__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 { \
+        PyObject **_tmp_dst_ptr = _Py_CAST(PyObject**, &(dst)); \
+        PyObject *_tmp_old_dst = (*_tmp_dst_ptr); \
+        PyObject *_tmp_src = _PyObject_CAST(src); \
+        memcpy(_tmp_dst_ptr, &_tmp_src, sizeof(PyObject*)); \
+        Py_XDECREF(_tmp_old_dst); \
+    } while (0)
+#endif
+
+
 /* Function to use in case the object pointer can be NULL: */
 static inline void Py_XINCREF(PyObject *op)
 {
diff --git a/Lib/test/test_cext/extension.c b/Lib/test/test_cext/extension.c
index f1528b4df7776fe..779ea9facca8c78 100644
--- a/Lib/test/test_cext/extension.c
+++ b/Lib/test/test_cext/extension.c
@@ -104,7 +104,6 @@ test_macros(PyObject *Py_UNUSED(module), PyObject 
*Py_UNUSED(args))
     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);
@@ -124,7 +123,6 @@ test_macros(PyObject *Py_UNUSED(module), PyObject 
*Py_UNUSED(args))
     slots[0] = Py_None;
     Py_XSETREF(slots[0], _Py_NULL);
     assert(slots[0] == _Py_NULL);
-#endif
 
     // Test that Py_BEGIN_CRITICAL_SECTION is available
     dict = PyDict_New();
diff --git 
a/Misc/NEWS.d/next/C_API/2026-10-05-15-24-22.gh-issue-158842.g7eLtE.rst 
b/Misc/NEWS.d/next/C_API/2026-10-05-15-24-22.gh-issue-158842.g7eLtE.rst
new file mode 100644
index 000000000000000..222d5279a3fa3ae
--- /dev/null
+++ b/Misc/NEWS.d/next/C_API/2026-10-05-15-24-22.gh-issue-158842.g7eLtE.rst
@@ -0,0 +1,2 @@
+Add :c:macro:`Py_SETREF` and :c:macro:`Py_XSETREF` macros to the limited C
+API. Patch by Nathan Goldbaum and Victor Stinner.
diff --git a/Misc/stable_abi.toml b/Misc/stable_abi.toml
index 0425fba42cbe2c7..751c186674c7c91 100644
--- a/Misc/stable_abi.toml
+++ b/Misc/stable_abi.toml
@@ -2884,3 +2884,7 @@
 
 [function.Py_HashBuffer]
     added = '3.16'
+[macro.Py_SETREF]
+    added = '3.16'
+[macro.Py_XSETREF]
+    added = '3.16'
diff --git a/Modules/_testcapi/object.c b/Modules/_testcapi/object.c
index 425ec540b62dcc0..8fcb86e33e55a83 100644
--- a/Modules/_testcapi/object.c
+++ b/Modules/_testcapi/object.c
@@ -246,63 +246,6 @@ test_py_set_immortal(PyObject *self, PyObject *unused)
     Py_RETURN_NONE;
 }
 
-static PyObject *
-_test_incref(PyObject *ob)
-{
-    return Py_NewRef(ob);
-}
-
-static PyObject *
-test_xincref_doesnt_leak(PyObject *ob, PyObject *Py_UNUSED(ignored))
-{
-    PyObject *obj = PyLong_FromLong(0);
-    Py_XINCREF(_test_incref(obj));
-    Py_DECREF(obj);
-    Py_DECREF(obj);
-    Py_DECREF(obj);
-    Py_RETURN_NONE;
-}
-
-
-static PyObject *
-test_incref_doesnt_leak(PyObject *ob, PyObject *Py_UNUSED(ignored))
-{
-    PyObject *obj = PyLong_FromLong(0);
-    Py_INCREF(_test_incref(obj));
-    Py_DECREF(obj);
-    Py_DECREF(obj);
-    Py_DECREF(obj);
-    Py_RETURN_NONE;
-}
-
-
-static PyObject *
-test_xdecref_doesnt_leak(PyObject *ob, PyObject *Py_UNUSED(ignored))
-{
-    Py_XDECREF(PyLong_FromLong(0));
-    Py_RETURN_NONE;
-}
-
-
-static PyObject *
-test_decref_doesnt_leak(PyObject *ob, PyObject *Py_UNUSED(ignored))
-{
-    Py_DECREF(PyLong_FromLong(0));
-    Py_RETURN_NONE;
-}
-
-
-static PyObject *
-test_incref_decref_API(PyObject *ob, PyObject *Py_UNUSED(ignored))
-{
-    PyObject *obj = PyLong_FromLong(0);
-    Py_IncRef(obj);
-    Py_DecRef(obj);
-    Py_DecRef(obj);
-    Py_RETURN_NONE;
-}
-
-
 #ifdef Py_REF_DEBUG
 static PyObject *
 negative_refcount(PyObject *self, PyObject *Py_UNUSED(args))
@@ -343,186 +286,6 @@ decref_freed_object(PyObject *self, PyObject 
*Py_UNUSED(args))
 #endif
 
 
-// Test Py_CLEAR() macro
-static PyObject*
-test_py_clear(PyObject *self, PyObject *Py_UNUSED(ignored))
-{
-    // simple case with a variable
-    PyObject *obj = PyList_New(0);
-    if (obj == NULL) {
-        return NULL;
-    }
-    Py_CLEAR(obj);
-    assert(obj == NULL);
-
-    // gh-98724: complex case, Py_CLEAR() argument has a side effect
-    PyObject* array[1];
-    array[0] = PyList_New(0);
-    if (array[0] == NULL) {
-        return NULL;
-    }
-
-    PyObject **p = array;
-    Py_CLEAR(*p++);
-    assert(array[0] == NULL);
-    assert(p == array + 1);
-
-    Py_RETURN_NONE;
-}
-
-
-// Test Py_SETREF() and Py_XSETREF() macros, similar to test_py_clear()
-static PyObject*
-test_py_setref(PyObject *self, PyObject *Py_UNUSED(ignored))
-{
-    // Py_SETREF() simple case with a variable
-    PyObject *obj = PyList_New(0);
-    if (obj == NULL) {
-        return NULL;
-    }
-    Py_SETREF(obj, NULL);
-    assert(obj == NULL);
-
-    // Py_XSETREF() simple case with a variable
-    PyObject *obj2 = PyList_New(0);
-    if (obj2 == NULL) {
-        return NULL;
-    }
-    Py_XSETREF(obj2, NULL);
-    assert(obj2 == NULL);
-    // test Py_XSETREF() when the argument is NULL
-    Py_XSETREF(obj2, NULL);
-    assert(obj2 == NULL);
-
-    // gh-98724: complex case, Py_SETREF() argument has a side effect
-    PyObject* array[1];
-    array[0] = PyList_New(0);
-    if (array[0] == NULL) {
-        return NULL;
-    }
-
-    PyObject **p = array;
-    Py_SETREF(*p++, NULL);
-    assert(array[0] == NULL);
-    assert(p == array + 1);
-
-    // gh-98724: complex case, Py_XSETREF() argument has a side effect
-    PyObject* array2[1];
-    array2[0] = PyList_New(0);
-    if (array2[0] == NULL) {
-        return NULL;
-    }
-
-    PyObject **p2 = array2;
-    Py_XSETREF(*p2++, NULL);
-    assert(array2[0] == NULL);
-    assert(p2 == array2 + 1);
-
-    // test Py_XSETREF() when the argument is NULL
-    p2 = array2;
-    Py_XSETREF(*p2++, NULL);
-    assert(array2[0] == NULL);
-    assert(p2 == array2 + 1);
-
-    Py_RETURN_NONE;
-}
-
-
-#define TEST_REFCOUNT() \
-    do { \
-        PyObject *obj = PyList_New(0); \
-        if (obj == NULL) { \
-            return NULL; \
-        } \
-        assert(Py_REFCNT(obj) == 1); \
-        \
-        /* test Py_NewRef() */ \
-        PyObject *ref = Py_NewRef(obj); \
-        assert(ref == obj); \
-        assert(Py_REFCNT(obj) == 2); \
-        Py_DECREF(ref); \
-        \
-        /* test Py_XNewRef() */ \
-        PyObject *xref = Py_XNewRef(obj); \
-        assert(xref == obj); \
-        assert(Py_REFCNT(obj) == 2); \
-        Py_DECREF(xref); \
-        \
-        assert(Py_XNewRef(NULL) == NULL); \
-        \
-        Py_DECREF(obj); \
-        Py_RETURN_NONE; \
-    } while (0)
-
-
-// Test Py_NewRef() and Py_XNewRef() macros
-static PyObject*
-test_refcount_macros(PyObject *self, PyObject *Py_UNUSED(ignored))
-{
-    TEST_REFCOUNT();
-}
-
-#undef Py_NewRef
-#undef Py_XNewRef
-
-// Test Py_NewRef() and Py_XNewRef() functions, after undefining macros.
-static PyObject*
-test_refcount_funcs(PyObject *self, PyObject *Py_UNUSED(ignored))
-{
-    TEST_REFCOUNT();
-}
-
-
-// Test Py_Is() function
-#define TEST_PY_IS() \
-    do { \
-        PyObject *o_none = Py_None; \
-        PyObject *o_true = Py_True; \
-        PyObject *o_false = Py_False; \
-        PyObject *obj = PyList_New(0); \
-        if (obj == NULL) { \
-            return NULL; \
-        } \
-        \
-        /* test Py_Is() */ \
-        assert(Py_Is(obj, obj)); \
-        assert(!Py_Is(obj, o_none)); \
-        \
-        /* test Py_None */ \
-        assert(Py_Is(o_none, o_none)); \
-        assert(!Py_Is(obj, o_none)); \
-        \
-        /* test Py_True */ \
-        assert(Py_Is(o_true, o_true)); \
-        assert(!Py_Is(o_false, o_true)); \
-        assert(!Py_Is(obj, o_true)); \
-        \
-        /* test Py_False */ \
-        assert(Py_Is(o_false, o_false)); \
-        assert(!Py_Is(o_true, o_false)); \
-        assert(!Py_Is(obj, o_false)); \
-        \
-        Py_DECREF(obj); \
-        Py_RETURN_NONE; \
-    } while (0)
-
-// Test Py_Is() macro
-static PyObject*
-test_py_is_macros(PyObject *self, PyObject *Py_UNUSED(ignored))
-{
-    TEST_PY_IS();
-}
-
-#undef Py_Is
-
-// Test Py_Is() function, after undefining its macro.
-static PyObject*
-test_py_is_funcs(PyObject *self, PyObject *Py_UNUSED(ignored))
-{
-    TEST_PY_IS();
-}
-
-
 static PyObject *
 clear_managed_dict(PyObject *self, PyObject *obj)
 {
@@ -597,21 +360,10 @@ static PyMethodDef test_methods[] = {
     {"pyobject_is_unique_temporary_new_object", 
pyobject_is_unique_temporary_new_object, METH_NOARGS},
     {"test_py_try_inc_ref", test_py_try_inc_ref, METH_NOARGS},
     {"test_py_set_immortal", test_py_set_immortal, METH_NOARGS},
-    {"test_xincref_doesnt_leak",test_xincref_doesnt_leak,        METH_NOARGS},
-    {"test_incref_doesnt_leak", test_incref_doesnt_leak,         METH_NOARGS},
-    {"test_xdecref_doesnt_leak",test_xdecref_doesnt_leak,        METH_NOARGS},
-    {"test_decref_doesnt_leak", test_decref_doesnt_leak,         METH_NOARGS},
-    {"test_incref_decref_API",  test_incref_decref_API,          METH_NOARGS},
 #ifdef Py_REF_DEBUG
     {"negative_refcount", negative_refcount, METH_NOARGS},
     {"decref_freed_object", decref_freed_object, METH_NOARGS},
 #endif
-    {"test_py_clear", test_py_clear, METH_NOARGS},
-    {"test_py_setref", test_py_setref, METH_NOARGS},
-    {"test_refcount_macros", test_refcount_macros, METH_NOARGS},
-    {"test_refcount_funcs", test_refcount_funcs, METH_NOARGS},
-    {"test_py_is_macros", test_py_is_macros, METH_NOARGS},
-    {"test_py_is_funcs", test_py_is_funcs, METH_NOARGS},
     {"clear_managed_dict", clear_managed_dict, METH_O, NULL},
     {"is_uniquely_referenced", is_uniquely_referenced, METH_O},
     {"pyobject_dump", pyobject_dump, METH_VARARGS},
diff --git a/Modules/_testlimitedcapi/object.c 
b/Modules/_testlimitedcapi/object.c
index 594ba60f9abf204..03debb56b33c4b9 100644
--- a/Modules/_testlimitedcapi/object.c
+++ b/Modules/_testlimitedcapi/object.c
@@ -64,10 +64,259 @@ test_constants(PyObject *Py_UNUSED(module), PyObject 
*Py_UNUSED(args))
     Py_RETURN_NONE;
 }
 
+
+static PyObject *
+_test_incref(PyObject *ob)
+{
+    return Py_NewRef(ob);
+}
+
+static PyObject *
+test_xincref_doesnt_leak(PyObject *ob, PyObject *Py_UNUSED(ignored))
+{
+    PyObject *obj = PyLong_FromLong(0);
+    Py_XINCREF(_test_incref(obj));
+    Py_DECREF(obj);
+    Py_DECREF(obj);
+    Py_DECREF(obj);
+    Py_RETURN_NONE;
+}
+
+
+static PyObject *
+test_incref_doesnt_leak(PyObject *ob, PyObject *Py_UNUSED(ignored))
+{
+    PyObject *obj = PyLong_FromLong(0);
+    Py_INCREF(_test_incref(obj));
+    Py_DECREF(obj);
+    Py_DECREF(obj);
+    Py_DECREF(obj);
+    Py_RETURN_NONE;
+}
+
+
+static PyObject *
+test_xdecref_doesnt_leak(PyObject *ob, PyObject *Py_UNUSED(ignored))
+{
+    Py_XDECREF(PyLong_FromLong(0));
+    Py_RETURN_NONE;
+}
+
+
+static PyObject *
+test_decref_doesnt_leak(PyObject *ob, PyObject *Py_UNUSED(ignored))
+{
+    Py_DECREF(PyLong_FromLong(0));
+    Py_RETURN_NONE;
+}
+
+
+static PyObject *
+test_incref_decref_API(PyObject *ob, PyObject *Py_UNUSED(ignored))
+{
+    PyObject *obj = PyLong_FromLong(0);
+    Py_IncRef(obj);
+    Py_DecRef(obj);
+    Py_DecRef(obj);
+    Py_RETURN_NONE;
+}
+
+
+// Test Py_CLEAR() macro
+static PyObject*
+test_py_clear(PyObject *self, PyObject *Py_UNUSED(ignored))
+{
+    // simple case with a variable
+    PyObject *obj = PyList_New(0);
+    if (obj == NULL) {
+        return NULL;
+    }
+    Py_CLEAR(obj);
+    assert(obj == NULL);
+
+    // gh-98724: complex case, Py_CLEAR() argument has a side effect
+    PyObject* array[1];
+    array[0] = PyList_New(0);
+    if (array[0] == NULL) {
+        return NULL;
+    }
+
+    PyObject **p = array;
+    Py_CLEAR(*p++);
+    assert(array[0] == NULL);
+    assert(p == array + 1);
+
+    Py_RETURN_NONE;
+}
+
+
+// Test Py_SETREF() and Py_XSETREF() macros, similar to test_py_clear()
+static PyObject*
+test_py_setref(PyObject *self, PyObject *Py_UNUSED(ignored))
+{
+    // Py_SETREF() simple case with a variable
+    PyObject *obj = PyList_New(0);
+    if (obj == NULL) {
+        return NULL;
+    }
+    Py_SETREF(obj, NULL);
+    assert(obj == NULL);
+
+    // Py_XSETREF() simple case with a variable
+    PyObject *obj2 = PyList_New(0);
+    if (obj2 == NULL) {
+        return NULL;
+    }
+    Py_XSETREF(obj2, NULL);
+    assert(obj2 == NULL);
+    // test Py_XSETREF() when the argument is NULL
+    Py_XSETREF(obj2, NULL);
+    assert(obj2 == NULL);
+
+    // gh-98724: complex case, Py_SETREF() argument has a side effect
+    PyObject* array[1];
+    array[0] = PyList_New(0);
+    if (array[0] == NULL) {
+        return NULL;
+    }
+
+    PyObject **p = array;
+    Py_SETREF(*p++, NULL);
+    assert(array[0] == NULL);
+    assert(p == array + 1);
+
+    // gh-98724: complex case, Py_XSETREF() argument has a side effect
+    PyObject* array2[1];
+    array2[0] = PyList_New(0);
+    if (array2[0] == NULL) {
+        return NULL;
+    }
+
+    PyObject **p2 = array2;
+    Py_XSETREF(*p2++, NULL);
+    assert(array2[0] == NULL);
+    assert(p2 == array2 + 1);
+
+    // test Py_XSETREF() when the argument is NULL
+    p2 = array2;
+    Py_XSETREF(*p2++, NULL);
+    assert(array2[0] == NULL);
+    assert(p2 == array2 + 1);
+
+    Py_RETURN_NONE;
+}
+
+
+#define TEST_REFCOUNT() \
+    do { \
+        PyObject *obj = PyList_New(0); \
+        if (obj == NULL) { \
+            return NULL; \
+        } \
+        assert(Py_REFCNT(obj) == 1); \
+        \
+        /* test Py_NewRef() */ \
+        PyObject *ref = Py_NewRef(obj); \
+        assert(ref == obj); \
+        assert(Py_REFCNT(obj) == 2); \
+        Py_DECREF(ref); \
+        \
+        /* test Py_XNewRef() */ \
+        PyObject *xref = Py_XNewRef(obj); \
+        assert(xref == obj); \
+        assert(Py_REFCNT(obj) == 2); \
+        Py_DECREF(xref); \
+        \
+        assert(Py_XNewRef(NULL) == NULL); \
+        \
+        Py_DECREF(obj); \
+        Py_RETURN_NONE; \
+    } while (0)
+
+
+// Test Py_NewRef() and Py_XNewRef() macros
+static PyObject*
+test_refcount_macros(PyObject *self, PyObject *Py_UNUSED(ignored))
+{
+    TEST_REFCOUNT();
+}
+
+#undef Py_NewRef
+#undef Py_XNewRef
+
+// Test Py_NewRef() and Py_XNewRef() functions, after undefining macros.
+static PyObject*
+test_refcount_funcs(PyObject *self, PyObject *Py_UNUSED(ignored))
+{
+    TEST_REFCOUNT();
+}
+
+
+// Test Py_Is() function
+#define TEST_PY_IS() \
+    do { \
+        PyObject *o_none = Py_None; \
+        PyObject *o_true = Py_True; \
+        PyObject *o_false = Py_False; \
+        PyObject *obj = PyList_New(0); \
+        if (obj == NULL) { \
+            return NULL; \
+        } \
+        \
+        /* test Py_Is() */ \
+        assert(Py_Is(obj, obj)); \
+        assert(!Py_Is(obj, o_none)); \
+        \
+        /* test Py_None */ \
+        assert(Py_Is(o_none, o_none)); \
+        assert(!Py_Is(obj, o_none)); \
+        \
+        /* test Py_True */ \
+        assert(Py_Is(o_true, o_true)); \
+        assert(!Py_Is(o_false, o_true)); \
+        assert(!Py_Is(obj, o_true)); \
+        \
+        /* test Py_False */ \
+        assert(Py_Is(o_false, o_false)); \
+        assert(!Py_Is(o_true, o_false)); \
+        assert(!Py_Is(obj, o_false)); \
+        \
+        Py_DECREF(obj); \
+        Py_RETURN_NONE; \
+    } while (0)
+
+// Test Py_Is() macro
+static PyObject*
+test_py_is_macros(PyObject *self, PyObject *Py_UNUSED(ignored))
+{
+    TEST_PY_IS();
+}
+
+#undef Py_Is
+
+// Test Py_Is() function, after undefining its macro.
+static PyObject*
+test_py_is_funcs(PyObject *self, PyObject *Py_UNUSED(ignored))
+{
+    TEST_PY_IS();
+}
+
+
 static PyMethodDef test_methods[] = {
     {"get_constant", get_constant, METH_VARARGS},
     {"get_constant_borrowed", get_constant_borrowed, METH_VARARGS},
     {"test_constants", test_constants, METH_NOARGS},
+    {"test_xincref_doesnt_leak",test_xincref_doesnt_leak,        METH_NOARGS},
+    {"test_incref_doesnt_leak", test_incref_doesnt_leak,         METH_NOARGS},
+    {"test_xdecref_doesnt_leak",test_xdecref_doesnt_leak,        METH_NOARGS},
+    {"test_decref_doesnt_leak", test_decref_doesnt_leak,         METH_NOARGS},
+    {"test_incref_decref_API",  test_incref_decref_API,          METH_NOARGS},
+    {"test_py_clear", test_py_clear, METH_NOARGS},
+    {"test_py_setref", test_py_setref, METH_NOARGS},
+    {"test_refcount_macros", test_refcount_macros, METH_NOARGS},
+    {"test_refcount_funcs", test_refcount_funcs, METH_NOARGS},
+    {"test_py_is_macros", test_py_is_macros, METH_NOARGS},
+    {"test_py_is_funcs", test_py_is_funcs, METH_NOARGS},
     {NULL},
 };
 

_______________________________________________
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