Skip to content

gh-130931: Add PyFrame_SetBack() function #131252

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

Closed
wants to merge 1 commit into from
Closed
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
7 changes: 7 additions & 0 deletions Doc/c-api/frame.rst
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,13 @@ See also :ref:`Reflection <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
^^^^^^^^^^^^^^^^^^^^

Expand Down
4 changes: 4 additions & 0 deletions Doc/data/refcounts.dat
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down
3 changes: 3 additions & 0 deletions Doc/whatsnew/3.14.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
---------------------
Expand Down
1 change: 1 addition & 0 deletions Include/cpython/pyframe.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
20 changes: 18 additions & 2 deletions Lib/test/test_capi/test_frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down Expand Up @@ -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()
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Add :c:func:`PyFrame_SetBack` function to set a frame next outer frame.
Patch by Victor Stinner.
67 changes: 53 additions & 14 deletions Modules/_testcapi/frame.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -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;
}

Expand All @@ -105,15 +110,47 @@ 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;
}

return PyFrame_GetVarString((PyFrameObject *)frame, name);
}


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},
Expand All @@ -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},
};

Expand Down
10 changes: 10 additions & 0 deletions Objects/frameobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down
Loading