diff --git a/Include/object.h b/Include/object.h index 3774f126730005..a296c316a6251f 100644 --- a/Include/object.h +++ b/Include/object.h @@ -612,7 +612,13 @@ static 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(). + * + * If _Py_TYPEOF() is not available, the limited C API implementation uses a + * function call to hide implementation details. The function uses memcpy() of + * which is not included by . */ +PyAPI_FUNC(void) _Py_Clear(PyObject **pobj); + #ifdef _Py_TYPEOF #define Py_CLEAR(op) \ do { \ @@ -623,6 +629,8 @@ static inline void Py_DECREF(PyObject *op) Py_DECREF(_tmp_old_op); \ } \ } while (0) +#elif defined(Py_LIMITED_API) +#define Py_CLEAR(op) _Py_Clear(_Py_CAST(PyObject**, &(op))) #else #define Py_CLEAR(op) \ do { \ diff --git a/Lib/test/test_stable_abi_ctypes.py b/Lib/test/test_stable_abi_ctypes.py index 67c653428a6dee..67e3afdeb45e58 100644 --- a/Lib/test/test_stable_abi_ctypes.py +++ b/Lib/test/test_stable_abi_ctypes.py @@ -868,6 +868,7 @@ def test_windows_feature_macros(self): "_PyWeakref_RefType", "_Py_BuildValue_SizeT", "_Py_CheckRecursiveCall", + "_Py_Clear", "_Py_Dealloc", "_Py_DecRef", "_Py_EllipsisObject", diff --git a/Misc/stable_abi.toml b/Misc/stable_abi.toml index c716f403d638ac..dcf2a8e452c296 100644 --- a/Misc/stable_abi.toml +++ b/Misc/stable_abi.toml @@ -2386,3 +2386,7 @@ added = '3.12' # Before 3.12, available in "structmember.h" w/o Py_ prefix [const.Py_AUDIT_READ] added = '3.12' # Before 3.12, available in "structmember.h" + +[function._Py_Clear] + added = '3.12' + abi_only = true diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index 83eef73a875d9d..f29e68a6ae8520 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -2593,27 +2593,53 @@ test_set_type_size(PyObject *self, PyObject *Py_UNUSED(ignored)) static PyObject* test_py_clear(PyObject *self, PyObject *Py_UNUSED(ignored)) { + PyObject *list = PyList_New(0); + if (list == NULL) { + return NULL; + } + assert(Py_REFCNT(list) == 1); + // simple case with a variable - PyObject *obj = PyList_New(0); + PyObject *obj = Py_NewRef(list); if (obj == NULL) { - return NULL; + goto error; } + assert(Py_REFCNT(list) == 2); Py_CLEAR(obj); + assert(Py_REFCNT(list) == 1); assert(obj == NULL); + // test _Py_Clear() used by the limited C API + PyObject *obj2 = Py_NewRef(list); + if (obj2 == NULL) { + goto error; + } + assert(Py_REFCNT(list) == 2); + _Py_Clear(&obj2); + assert(Py_REFCNT(list) == 1); + assert(obj2 == NULL); + // gh-98724: complex case, Py_CLEAR() argument has a side effect PyObject* array[1]; - array[0] = PyList_New(0); + array[0] = Py_NewRef(list); if (array[0] == NULL) { - return NULL; + goto error; } PyObject **p = array; + assert(Py_REFCNT(list) == 2); Py_CLEAR(*p++); + assert(Py_REFCNT(list) == 1); assert(array[0] == NULL); assert(p == array + 1); + assert(Py_REFCNT(list) == 1); + Py_DECREF(list); Py_RETURN_NONE; + +error: + Py_DECREF(list); + return NULL; } diff --git a/Objects/object.c b/Objects/object.c index 687bd36d2b4af1..43cb6b6393181c 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -2446,6 +2446,23 @@ int Py_IsFalse(PyObject *x) return Py_Is(x, Py_False); } +void _Py_Clear(PyObject **pobj) +{ + PyObject *old_obj = *pobj; + if (old_obj == NULL) { + return; + } + + // gh-99701: In the limited C API, the Py_CLEAR(obj) macro has a type + // punning issue, it casts '&obj' to PyObject** to call _Py_Clear(). Use + // memcpy() instead of a simple assignment to cause type erasure and so + // prevent the compiler to reuse an old cached 'obj' value after + // Py_CLEAR(). + PyObject *null_ptr = NULL; + memcpy(pobj, &null_ptr, sizeof(PyObject*)); + Py_DECREF(old_obj); +} + #ifdef __cplusplus } #endif diff --git a/PC/python3dll.c b/PC/python3dll.c index 931f316bb99843..eba1589a14e3d6 100755 --- a/PC/python3dll.c +++ b/PC/python3dll.c @@ -16,6 +16,7 @@ EXPORT_FUNC(_Py_BuildValue_SizeT) EXPORT_FUNC(_Py_CheckRecursiveCall) +EXPORT_FUNC(_Py_Clear) EXPORT_FUNC(_Py_Dealloc) EXPORT_FUNC(_Py_DecRef) EXPORT_FUNC(_Py_IncRef)