From dbf7057ff58bdc45352b6d635d111f6b963c860b Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Tue, 20 Jun 2023 23:02:33 +0200 Subject: [PATCH 1/4] Add PyWeakref_GetRef() function --- docs/api.rst | 3 ++ docs/changelog.rst | 1 + pythoncapi_compat.h | 28 +++++++++++ tests/test_pythoncapi_compat_cext.c | 73 +++++++++++++++++++++++++++++ 4 files changed, 105 insertions(+) diff --git a/docs/api.rst b/docs/api.rst index 3aa9b46..fef2985 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -31,6 +31,9 @@ Python 3.13 See `PyImport_AddModuleRef() documentation `__. +.. c:function:: int PyWeakref_GetRef(PyObject *ref, PyObject **pobj) + + See `PyWeakref_GetRef() documentation `__. Python 3.12 ----------- diff --git a/docs/changelog.rst b/docs/changelog.rst index 012d211..e95be94 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,7 @@ Changelog ========= +* 2023-06-21: Add ``PyWeakref_GetRef()`` function. * 2023-06-20: Add ``PyImport_AddModuleRef()`` function. * 2022-11-15: Add experimental operations to the ``upgrade_pythoncapi.py`` script: ``Py_NewRef``, ``Py_CLEAR`` and ``Py_SETREF``. diff --git a/pythoncapi_compat.h b/pythoncapi_compat.h index 1930709..ba6d04e 100644 --- a/pythoncapi_compat.h +++ b/pythoncapi_compat.h @@ -570,6 +570,7 @@ PyCode_GetCellvars(PyCodeObject *code) # endif #endif + // gh-105922 added PyImport_AddModuleRef() to Python 3.13.0a1 #if PY_VERSION_HEX < 0x030D00A0 PYCAPI_COMPAT_STATIC_INLINE(PyObject*) @@ -580,6 +581,33 @@ PyImport_AddModuleRef(const char *name) #endif +// gh-105927 added PyWeakref_GetRef() to Python 3.13.0a1 +#if PY_VERSION_HEX < 0x030D0000 +PYCAPI_COMPAT_STATIC_INLINE(int) +PyWeakref_GetRef(PyObject *ref, PyObject **pobj) +{ + PyObject *obj; + if (ref != NULL && !PyWeakref_Check(ref)) { + *pobj = NULL; + PyErr_SetString(PyExc_TypeError, "expected a weakref"); + return -1; + } + obj = PyWeakref_GetObject(ref); + if (obj == NULL) { + // SystemError if ref is NULL + *pobj = NULL; + return -1; + } + if (obj == Py_None) { + *pobj = NULL; + return 0; + } + *pobj = Py_NewRef(obj); + return 0; +} +#endif + + #ifdef __cplusplus } #endif diff --git a/tests/test_pythoncapi_compat_cext.c b/tests/test_pythoncapi_compat_cext.c index ed00cbe..abbc8b6 100644 --- a/tests/test_pythoncapi_compat_cext.c +++ b/tests/test_pythoncapi_compat_cext.c @@ -668,6 +668,78 @@ test_import(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) } +static void +gc_collect(void) +{ +#if defined(PYPY_VERSION) + PyObject *mod = PyImport_ImportModule("gc"); + assert(mod != NULL); + + PyObject *res = PyObject_CallMethod(mod, "collect", NULL); + Py_DECREF(mod); + assert(res != NULL); + Py_DECREF(res); +#else + PyGC_Collect(); +#endif +} + + +static PyObject * +test_weakref(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) +{ + // Create a new heap type, create an instance of this type, and delete the + // type. This object supports weak references. + PyObject *new_type = PyObject_CallFunction((PyObject*)&PyType_Type, + "s(){}", "TypeName"); + if (new_type == NULL) { + return NULL; + } + PyObject *obj = PyObject_CallNoArgs(new_type); + Py_DECREF(new_type); + if (obj == NULL) { + return NULL; + } + Py_ssize_t refcnt = Py_REFCNT(obj); + + // create a weak reference + PyObject *weakref = PyWeakref_NewRef(obj, NULL); + if (weakref == NULL) { + return NULL; + } + + // test PyWeakref_GetRef(), reference is alive + PyObject *ref1; + assert(PyWeakref_GetRef(weakref, &ref1) == 0); + assert(ref1 == obj); + assert(Py_REFCNT(obj) == (refcnt + 1)); + Py_DECREF(ref1); + + // delete the referenced object + assert(Py_REFCNT(obj) == 1); + Py_DECREF(obj); + gc_collect(); + + // test PyWeakref_GetRef(), reference is dead + PyObject *ref2 = Py_True; // marker to check that value was set + assert(PyWeakref_GetRef(weakref, &ref2) == 0); + assert(ref2 == NULL); + + // test PyWeakref_GetRef(), invalid type + PyObject *invalid_weakref = Py_None; + assert(!PyErr_Occurred()); + PyObject *ref3 = Py_True; // marker to check that value was set + assert(PyWeakref_GetRef(invalid_weakref, &ref3) == -1); + assert(PyErr_ExceptionMatches(PyExc_TypeError)); + assert(ref3 == NULL); + PyErr_Clear(); + + Py_DECREF(weakref); + + Py_RETURN_NONE; +} + + static struct PyMethodDef methods[] = { {"test_object", test_object, METH_NOARGS, _Py_NULL}, {"test_py_is", test_py_is, METH_NOARGS, _Py_NULL}, @@ -687,6 +759,7 @@ static struct PyMethodDef methods[] = { #endif {"test_api_casts", test_api_casts, METH_NOARGS, _Py_NULL}, {"test_import", test_import, METH_NOARGS, _Py_NULL}, + {"test_weakref", test_weakref, METH_NOARGS, _Py_NULL}, {_Py_NULL, _Py_NULL, 0, _Py_NULL} }; From b38be20f81fdd366643b2af42b253fa833192e34 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 21 Jun 2023 13:56:20 +0200 Subject: [PATCH 2/4] Fix PyPy --- tests/test_pythoncapi_compat_cext.c | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_pythoncapi_compat_cext.c b/tests/test_pythoncapi_compat_cext.c index abbc8b6..9abdc16 100644 --- a/tests/test_pythoncapi_compat_cext.c +++ b/tests/test_pythoncapi_compat_cext.c @@ -716,7 +716,6 @@ test_weakref(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) Py_DECREF(ref1); // delete the referenced object - assert(Py_REFCNT(obj) == 1); Py_DECREF(obj); gc_collect(); From 8c07a624dfcbc76829bf84cafbf3e9ab4ec91992 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 21 Jun 2023 15:42:03 +0200 Subject: [PATCH 3/4] Test NULL --- tests/test_pythoncapi_compat_cext.c | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/tests/test_pythoncapi_compat_cext.c b/tests/test_pythoncapi_compat_cext.c index 9abdc16..6aa823b 100644 --- a/tests/test_pythoncapi_compat_cext.c +++ b/tests/test_pythoncapi_compat_cext.c @@ -33,7 +33,7 @@ #define MODULE_NAME_STR STR(MODULE_NAME) // Ignore reference count checks on PyPy -#if !defined(PYPY_VERSION) +#ifndef PYPY_VERSION # define CHECK_REFCNT #endif @@ -148,7 +148,7 @@ test_py_is(PyObject *Py_UNUSED(module), PyObject* Py_UNUSED(ignored)) } -#if !defined(PYPY_VERSION) +#ifndef PYPY_VERSION static void test_frame_getvar(PyFrameObject *frame) { @@ -279,7 +279,7 @@ test_thread_state(PyObject *Py_UNUSED(module), PyObject* Py_UNUSED(ignored)) PyInterpreterState *interp = PyThreadState_GetInterpreter(tstate); assert(interp != _Py_NULL); -#if !defined(PYPY_VERSION) +#ifndef PYPY_VERSION // test PyThreadState_GetFrame() PyFrameObject *frame = PyThreadState_GetFrame(tstate); if (frame != _Py_NULL) { @@ -293,7 +293,7 @@ test_thread_state(PyObject *Py_UNUSED(module), PyObject* Py_UNUSED(ignored)) assert(id > 0); #endif -#if !defined(PYPY_VERSION) +#ifndef PYPY_VERSION // PyThreadState_EnterTracing(), PyThreadState_LeaveTracing() PyThreadState_EnterTracing(tstate); PyThreadState_LeaveTracing(tstate); @@ -733,6 +733,15 @@ test_weakref(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) assert(ref3 == NULL); PyErr_Clear(); +#ifndef PYPY_VERSION + // test PyWeakref_GetRef(NULL) + PyObject *ref4 = Py_True; // marker to check that value was set + assert(PyWeakref_GetRef(NULL, &ref4) == -1); + assert(PyErr_ExceptionMatches(PyExc_SystemError)); + assert(ref4 == NULL); + PyErr_Clear(); +#endif + Py_DECREF(weakref); Py_RETURN_NONE; @@ -742,7 +751,7 @@ test_weakref(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) static struct PyMethodDef methods[] = { {"test_object", test_object, METH_NOARGS, _Py_NULL}, {"test_py_is", test_py_is, METH_NOARGS, _Py_NULL}, -#if !defined(PYPY_VERSION) +#ifndef PYPY_VERSION {"test_frame", test_frame, METH_NOARGS, _Py_NULL}, #endif {"test_thread_state", test_thread_state, METH_NOARGS, _Py_NULL}, @@ -753,7 +762,7 @@ static struct PyMethodDef methods[] = { #if (PY_VERSION_HEX <= 0x030B00A1 || 0x030B00A7 <= PY_VERSION_HEX) && !defined(PYPY_VERSION) {"test_float_pack", test_float_pack, METH_NOARGS, _Py_NULL}, #endif -#if !defined(PYPY_VERSION) +#ifndef PYPY_VERSION {"test_code", test_code, METH_NOARGS, _Py_NULL}, #endif {"test_api_casts", test_api_casts, METH_NOARGS, _Py_NULL}, From 8194ab6b41b4fff9cc03440d0669d433ca8246ea Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Wed, 21 Jun 2023 16:02:12 +0200 Subject: [PATCH 4/4] Cleanup tests --- tests/test_pythoncapi_compat_cext.c | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/tests/test_pythoncapi_compat_cext.c b/tests/test_pythoncapi_compat_cext.c index 6aa823b..bd80641 100644 --- a/tests/test_pythoncapi_compat_cext.c +++ b/tests/test_pythoncapi_compat_cext.c @@ -709,36 +709,36 @@ test_weakref(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args)) } // test PyWeakref_GetRef(), reference is alive - PyObject *ref1; - assert(PyWeakref_GetRef(weakref, &ref1) == 0); - assert(ref1 == obj); + PyObject *ref = Py_True; // marker to check that value was set + assert(PyWeakref_GetRef(weakref, &ref) == 0); + assert(ref == obj); assert(Py_REFCNT(obj) == (refcnt + 1)); - Py_DECREF(ref1); + Py_DECREF(ref); - // delete the referenced object + // delete the referenced object: clear the weakref Py_DECREF(obj); gc_collect(); // test PyWeakref_GetRef(), reference is dead - PyObject *ref2 = Py_True; // marker to check that value was set - assert(PyWeakref_GetRef(weakref, &ref2) == 0); - assert(ref2 == NULL); + ref = Py_True; + assert(PyWeakref_GetRef(weakref, &ref) == 0); + assert(ref == NULL); // test PyWeakref_GetRef(), invalid type PyObject *invalid_weakref = Py_None; assert(!PyErr_Occurred()); - PyObject *ref3 = Py_True; // marker to check that value was set - assert(PyWeakref_GetRef(invalid_weakref, &ref3) == -1); + ref = Py_True; + assert(PyWeakref_GetRef(invalid_weakref, &ref) == -1); assert(PyErr_ExceptionMatches(PyExc_TypeError)); - assert(ref3 == NULL); + assert(ref == NULL); PyErr_Clear(); #ifndef PYPY_VERSION // test PyWeakref_GetRef(NULL) - PyObject *ref4 = Py_True; // marker to check that value was set - assert(PyWeakref_GetRef(NULL, &ref4) == -1); + ref = Py_True; + assert(PyWeakref_GetRef(NULL, &ref) == -1); assert(PyErr_ExceptionMatches(PyExc_SystemError)); - assert(ref4 == NULL); + assert(ref == NULL); PyErr_Clear(); #endif