Skip to content

Add PyWeakref_GetRef() function #61

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jun 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ Python 3.13

See `PyImport_AddModuleRef() documentation <https://docs.python.org/dev/c-api/import.html#c.PyImport_AddModuleRef>`__.

.. c:function:: int PyWeakref_GetRef(PyObject *ref, PyObject **pobj)

See `PyWeakref_GetRef() documentation <https://docs.python.org/dev/c-api/weakref.html#c.PyWeakref_GetRef>`__.

Python 3.12
-----------
Expand Down
1 change: 1 addition & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
@@ -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``.
Expand Down
28 changes: 28 additions & 0 deletions pythoncapi_compat.h
Original file line number Diff line number Diff line change
Expand Up @@ -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*)
Expand All @@ -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
Expand Down
93 changes: 87 additions & 6 deletions tests/test_pythoncapi_compat_cext.c
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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) {
Expand All @@ -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);
Expand Down Expand Up @@ -668,10 +668,90 @@ 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 *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(ref);

// delete the referenced object: clear the weakref
Py_DECREF(obj);
gc_collect();

// test PyWeakref_GetRef(), reference is dead
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());
ref = Py_True;
assert(PyWeakref_GetRef(invalid_weakref, &ref) == -1);
assert(PyErr_ExceptionMatches(PyExc_TypeError));
assert(ref == NULL);
PyErr_Clear();

#ifndef PYPY_VERSION
// test PyWeakref_GetRef(NULL)
ref = Py_True;
assert(PyWeakref_GetRef(NULL, &ref) == -1);
assert(PyErr_ExceptionMatches(PyExc_SystemError));
assert(ref == NULL);
PyErr_Clear();
#endif

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},
#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},
Expand All @@ -682,11 +762,12 @@ 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},
{"test_import", test_import, METH_NOARGS, _Py_NULL},
{"test_weakref", test_weakref, METH_NOARGS, _Py_NULL},
{_Py_NULL, _Py_NULL, 0, _Py_NULL}
};

Expand Down