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]