From 062591a317acaa1c5b6c5be64afd7ed4c633b962 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Fri, 14 Mar 2025 16:55:54 +0100 Subject: [PATCH] gh-130931: Add PyFrame_SetBack() function Add tests on PyFrame_GetBack() and PyFrame_SetBack(). --- Doc/c-api/frame.rst | 7 ++ Doc/data/refcounts.dat | 4 ++ Doc/whatsnew/3.14.rst | 3 + Include/cpython/pyframe.h | 1 + Lib/test/test_capi/test_frame.py | 20 +++++- ...-03-14-17-04-58.gh-issue-130931.VlgWga.rst | 2 + Modules/_testcapi/frame.c | 67 +++++++++++++++---- Objects/frameobject.c | 10 +++ 8 files changed, 98 insertions(+), 16 deletions(-) create mode 100644 Misc/NEWS.d/next/C_API/2025-03-14-17-04-58.gh-issue-130931.VlgWga.rst diff --git a/Doc/c-api/frame.rst b/Doc/c-api/frame.rst index 1a52e146a69751..9101a26e49ef85 100644 --- a/Doc/c-api/frame.rst +++ b/Doc/c-api/frame.rst @@ -140,6 +140,13 @@ See also :ref:`Reflection `. Return the line number that *frame* is currently executing. +.. c:function:: void PyFrame_SetBack(PyFrameObject *frame, PyFrameObject *back) + + Set the *frame* next outer frame to a :term:`strong reference` to *back*. + + .. versionadded:: next + + Frame Locals Proxies ^^^^^^^^^^^^^^^^^^^^ diff --git a/Doc/data/refcounts.dat b/Doc/data/refcounts.dat index 14629fbff0fb78..a3759a8756332b 100644 --- a/Doc/data/refcounts.dat +++ b/Doc/data/refcounts.dat @@ -948,6 +948,10 @@ PyFrame_GetVarString:PyObject*::+1: PyFrame_GetVarString:PyFrameObject*:frame:0: PyFrame_GetVarString:const char*:name:: +PyFrame_SetBack:void::: +PyFrame_SetBack:PyFrameObject*:frame:0: +PyFrame_SetBack:PyFrameObject*:back:+1: + PyFrozenSet_Check:int::: PyFrozenSet_Check:PyObject*:p:0: diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index 65ab57eb821c6c..1b1d5d0a5065f5 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -1590,6 +1590,9 @@ New features take a C integer and produce a Python :class:`bool` object. (Contributed by Pablo Galindo in :issue:`45325`.) +* Add :c:func:`PyFrame_SetBack` function to set a frame next outer frame. + (Contributed by Victor Stinner in :gh:`130931`.) + Limited C API changes --------------------- diff --git a/Include/cpython/pyframe.h b/Include/cpython/pyframe.h index 51529763923ec3..0500c49c5d91cb 100644 --- a/Include/cpython/pyframe.h +++ b/Include/cpython/pyframe.h @@ -9,6 +9,7 @@ PyAPI_DATA(PyTypeObject) PyFrameLocalsProxy_Type; #define PyFrameLocalsProxy_Check(op) Py_IS_TYPE((op), &PyFrameLocalsProxy_Type) PyAPI_FUNC(PyFrameObject *) PyFrame_GetBack(PyFrameObject *frame); +PyAPI_FUNC(void) PyFrame_SetBack(PyFrameObject *frame, PyFrameObject *back); PyAPI_FUNC(PyObject *) PyFrame_GetLocals(PyFrameObject *frame); PyAPI_FUNC(PyObject *) PyFrame_GetGlobals(PyFrameObject *frame); diff --git a/Lib/test/test_capi/test_frame.py b/Lib/test/test_capi/test_frame.py index 23cb8e3dada9d4..55c98b2fcbdcb6 100644 --- a/Lib/test/test_capi/test_frame.py +++ b/Lib/test/test_capi/test_frame.py @@ -7,11 +7,11 @@ class FrameTest(unittest.TestCase): - def getframe(self): + def create_frame(self): return sys._getframe() def test_frame_getters(self): - frame = self.getframe() + frame = self.create_frame() self.assertEqual(frame.f_locals, _testcapi.frame_getlocals(frame)) self.assertIs(frame.f_globals, _testcapi.frame_getglobals(frame)) self.assertIs(frame.f_builtins, _testcapi.frame_getbuiltins(frame)) @@ -51,6 +51,22 @@ def dummy(): # The following line should not cause a segmentation fault. self.assertIsNone(frame.f_back) + def test_back(self): + # Test PyFrame_GetBack() and PyFrame_SetBack() + frame_getback = _testcapi.frame_getback + frame_setback = _testcapi.frame_setback + + frame = self.create_frame() + current_frame = sys._getframe() + self.assertIs(frame_getback(frame), current_frame) + + frame2 = self.create_frame() + try: + frame_setback(frame, frame2) + self.assertIs(frame_getback(frame), frame2) + finally: + frame_setback(frame, current_frame) + if __name__ == "__main__": unittest.main() diff --git a/Misc/NEWS.d/next/C_API/2025-03-14-17-04-58.gh-issue-130931.VlgWga.rst b/Misc/NEWS.d/next/C_API/2025-03-14-17-04-58.gh-issue-130931.VlgWga.rst new file mode 100644 index 00000000000000..0a3dc6a0ff8829 --- /dev/null +++ b/Misc/NEWS.d/next/C_API/2025-03-14-17-04-58.gh-issue-130931.VlgWga.rst @@ -0,0 +1,2 @@ +Add :c:func:`PyFrame_SetBack` function to set a frame next outer frame. +Patch by Victor Stinner. diff --git a/Modules/_testcapi/frame.c b/Modules/_testcapi/frame.c index 5748dca948ea94..ab188715ec5ada 100644 --- a/Modules/_testcapi/frame.c +++ b/Modules/_testcapi/frame.c @@ -4,11 +4,21 @@ #include "frameobject.h" // PyFrame_New() +static int +check_frame(PyObject *op) +{ + if (!PyFrame_Check(op)) { + PyErr_SetString(PyExc_TypeError, "argument must be a frame"); + return -1; + } + return 0; +} + + static PyObject * frame_getlocals(PyObject *self, PyObject *frame) { - if (!PyFrame_Check(frame)) { - PyErr_SetString(PyExc_TypeError, "argument must be a frame"); + if (check_frame(frame) < 0) { return NULL; } return PyFrame_GetLocals((PyFrameObject *)frame); @@ -18,8 +28,7 @@ frame_getlocals(PyObject *self, PyObject *frame) static PyObject * frame_getglobals(PyObject *self, PyObject *frame) { - if (!PyFrame_Check(frame)) { - PyErr_SetString(PyExc_TypeError, "argument must be a frame"); + if (check_frame(frame) < 0) { return NULL; } return PyFrame_GetGlobals((PyFrameObject *)frame); @@ -29,8 +38,7 @@ frame_getglobals(PyObject *self, PyObject *frame) static PyObject * frame_getgenerator(PyObject *self, PyObject *frame) { - if (!PyFrame_Check(frame)) { - PyErr_SetString(PyExc_TypeError, "argument must be a frame"); + if (check_frame(frame) < 0) { return NULL; } return PyFrame_GetGenerator((PyFrameObject *)frame); @@ -40,8 +48,7 @@ frame_getgenerator(PyObject *self, PyObject *frame) static PyObject * frame_getbuiltins(PyObject *self, PyObject *frame) { - if (!PyFrame_Check(frame)) { - PyErr_SetString(PyExc_TypeError, "argument must be a frame"); + if (check_frame(frame) < 0) { return NULL; } return PyFrame_GetBuiltins((PyFrameObject *)frame); @@ -51,8 +58,7 @@ frame_getbuiltins(PyObject *self, PyObject *frame) static PyObject * frame_getlasti(PyObject *self, PyObject *frame) { - if (!PyFrame_Check(frame)) { - PyErr_SetString(PyExc_TypeError, "argument must be a frame"); + if (check_frame(frame) < 0) { return NULL; } int lasti = PyFrame_GetLasti((PyFrameObject *)frame); @@ -88,8 +94,7 @@ frame_getvar(PyObject *self, PyObject *args) if (!PyArg_ParseTuple(args, "OO", &frame, &name)) { return NULL; } - if (!PyFrame_Check(frame)) { - PyErr_SetString(PyExc_TypeError, "argument must be a frame"); + if (check_frame(frame) < 0) { return NULL; } @@ -105,8 +110,7 @@ frame_getvarstring(PyObject *self, PyObject *args) if (!PyArg_ParseTuple(args, "Oy", &frame, &name)) { return NULL; } - if (!PyFrame_Check(frame)) { - PyErr_SetString(PyExc_TypeError, "argument must be a frame"); + if (check_frame(frame) < 0) { return NULL; } @@ -114,6 +118,39 @@ frame_getvarstring(PyObject *self, PyObject *args) } +static PyObject * +frame_getback(PyObject *self, PyObject *frame) +{ + if (check_frame(frame) < 0) { + return NULL; + } + PyFrameObject *back = PyFrame_GetBack((PyFrameObject *)frame); + if (back == NULL) { + Py_RETURN_NONE; + } + return (PyObject*)back; +} + + +static PyObject * +frame_setback(PyObject *self, PyObject *args) +{ + PyObject *frame, *back; + if (!PyArg_ParseTuple(args, "OO", &frame, &back)) { + return NULL; + } + if (check_frame(frame) < 0) { + return NULL; + } + if (check_frame(back) < 0) { + return NULL; + } + + PyFrame_SetBack((PyFrameObject *)frame, (PyFrameObject *)back); + Py_RETURN_NONE; +} + + static PyMethodDef test_methods[] = { {"frame_getlocals", frame_getlocals, METH_O, NULL}, {"frame_getglobals", frame_getglobals, METH_O, NULL}, @@ -123,6 +160,8 @@ static PyMethodDef test_methods[] = { {"frame_new", frame_new, METH_VARARGS, NULL}, {"frame_getvar", frame_getvar, METH_VARARGS, NULL}, {"frame_getvarstring", frame_getvarstring, METH_VARARGS, NULL}, + {"frame_getback", frame_getback, METH_O, NULL}, + {"frame_setback", frame_setback, METH_VARARGS, NULL}, {NULL}, }; diff --git a/Objects/frameobject.c b/Objects/frameobject.c index 001b58dc052416..0cb9499585d543 100644 --- a/Objects/frameobject.c +++ b/Objects/frameobject.c @@ -2250,6 +2250,16 @@ PyFrame_GetBack(PyFrameObject *frame) return (PyFrameObject*)Py_XNewRef(back); } + +void +PyFrame_SetBack(PyFrameObject *frame, PyFrameObject *back) +{ + assert(frame != NULL); + assert(!_PyFrame_IsIncomplete(frame->f_frame)); + Py_XSETREF(frame->f_back, (PyFrameObject*)Py_XNewRef(back)); +} + + PyObject* PyFrame_GetLocals(PyFrameObject *frame) {