Skip to content
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

GH-96803: Document and test new unstable internal frame API functions #104211

Merged
merged 9 commits into from
May 18, 2023
35 changes: 35 additions & 0 deletions Doc/c-api/frame.rst
Original file line number Diff line number Diff line change
Expand Up @@ -130,3 +130,38 @@ See also :ref:`Reflection <reflection>`.
.. c:function:: int PyFrame_GetLineNumber(PyFrameObject *frame)

Return the line number that *frame* is currently executing.



Internal Frames
---------------

Unless using :pep:`523`, you will not need this.

.. c:struct:: _PyInterpreterFrame

The interpreter's internal frame representation.

markshannon marked this conversation as resolved.
Show resolved Hide resolved
.. versionadded:: 3.11

.. c:function:: PyObject* PyUnstable_InterpreterFrame_GetCode(struct _PyInterpreterFrame *frame);

Return a :term:`strong reference` to the code object for the frame.

.. versionadded:: 3.12


.. c:function:: int PyUnstable_InterpreterFrame_GetLasti(struct _PyInterpreterFrame *frame);

Return the byte offset into the last executed instruction.

.. versionadded:: 3.12


.. c:function:: int PyUnstable_InterpreterFrame_GetLine(struct _PyInterpreterFrame *frame);

Return the currently executing line number, or -1 if there is no line number.

.. versionadded:: 3.12


2 changes: 1 addition & 1 deletion Include/cpython/frameobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ PyAPI_FUNC(void) PyFrame_FastToLocals(PyFrameObject *);

/* Returns the code object of the frame (strong reference).
* Does not raise an exception. */
PyAPI_FUNC(PyCodeObject *) PyUnstable_InterpreterFrame_GetCode(struct _PyInterpreterFrame *frame);
PyAPI_FUNC(PyObject *) PyUnstable_InterpreterFrame_GetCode(struct _PyInterpreterFrame *frame);

/* Returns a byte ofsset into the last executed instruction.
* Does not raise an exception. */
Expand Down
24 changes: 24 additions & 0 deletions Lib/test/test_capi/test_misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -1744,6 +1744,30 @@ class Subclass(BaseException, self.module.StateAccessType):
self.assertIs(Subclass().get_defining_module(), self.module)


class TestInternalFrameApi(unittest.TestCase):

@staticmethod
def func():
return sys._getframe()

def test_code(self):
frame = self.func()
code = _testinternalcapi.iframe_getcode(frame)
self.assertIs(code, self.func.__code__)

def test_lasti(self):
frame = self.func()
lasti = _testinternalcapi.iframe_getlasti(frame)
self.assertGreater(lasti, 0)
self.assertLess(lasti, len(self.func.__code__.co_code))

def test_line(self):
frame = self.func()
line = _testinternalcapi.iframe_getline(frame)
firstline = self.func.__code__.co_firstlineno
self.assertEqual(line, firstline + 2)


SUFFICIENT_TO_DEOPT_AND_SPECIALIZE = 100

class Test_Pep523API(unittest.TestCase):
Expand Down
36 changes: 36 additions & 0 deletions Modules/_testinternalcapi.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#define PY_SSIZE_T_CLEAN

#include "Python.h"
#include "frameobject.h"
#include "pycore_atomic_funcs.h" // _Py_atomic_int_get()
#include "pycore_bitutils.h" // _Py_bswap32()
#include "pycore_compile.h" // _PyCompile_CodeGen, _PyCompile_OptimizeCfg, _PyCompile_Assemble
Expand Down Expand Up @@ -755,6 +756,38 @@ clear_extension(PyObject *self, PyObject *args)
Py_RETURN_NONE;
}

static PyObject *
iframe_getcode(PyObject *self, PyObject *frame)
{
if (!PyFrame_Check(frame)) {
PyErr_SetString(PyExc_TypeError, "argument must be a frame");
return NULL;
}
struct _PyInterpreterFrame *f = ((PyFrameObject *)frame)->f_frame;
return PyUnstable_InterpreterFrame_GetCode(f);
}

static PyObject *
iframe_getline(PyObject *self, PyObject *frame)
{
if (!PyFrame_Check(frame)) {
PyErr_SetString(PyExc_TypeError, "argument must be a frame");
return NULL;
}
struct _PyInterpreterFrame *f = ((PyFrameObject *)frame)->f_frame;
return PyLong_FromLong(PyUnstable_InterpreterFrame_GetLine(f));
}

static PyObject *
iframe_getlasti(PyObject *self, PyObject *frame)
{
if (!PyFrame_Check(frame)) {
PyErr_SetString(PyExc_TypeError, "argument must be a frame");
return NULL;
}
struct _PyInterpreterFrame *f = ((PyFrameObject *)frame)->f_frame;
return PyLong_FromLong(PyUnstable_InterpreterFrame_GetLasti(f));
}

static PyMethodDef module_functions[] = {
{"get_configs", get_configs, METH_NOARGS},
Expand All @@ -779,6 +812,9 @@ static PyMethodDef module_functions[] = {
_TESTINTERNALCAPI_ASSEMBLE_CODE_OBJECT_METHODDEF
{"get_interp_settings", get_interp_settings, METH_VARARGS, NULL},
{"clear_extension", clear_extension, METH_VARARGS, NULL},
{"iframe_getcode", iframe_getcode, METH_O, NULL},
{"iframe_getline", iframe_getline, METH_O, NULL},
{"iframe_getlasti", iframe_getlasti, METH_O, NULL},
{NULL, NULL} /* sentinel */
};

Expand Down
4 changes: 2 additions & 2 deletions Python/frame.c
Original file line number Diff line number Diff line change
Expand Up @@ -146,10 +146,10 @@ _PyFrame_ClearExceptCode(_PyInterpreterFrame *frame)

/* Unstable API functions */

PyCodeObject *
PyObject *
PyUnstable_InterpreterFrame_GetCode(struct _PyInterpreterFrame *frame)
{
PyCodeObject *code = frame->f_code;
PyObject *code = (PyObject *)frame->f_code;
Py_INCREF(code);
return code;
}
Expand Down