Skip to content

Commit

Permalink
GH-96803: Document and test new unstable internal frame API functions (
Browse files Browse the repository at this point in the history
…GH-104211)

Weaken contract of PyUnstable_InterpreterFrame_GetCode to return PyObject*.
  • Loading branch information
markshannon authored May 18, 2023
1 parent 68b5f08 commit cfa517d
Show file tree
Hide file tree
Showing 5 changed files with 98 additions and 3 deletions.
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.
.. 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 @@ -757,6 +758,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 @@ -781,6 +814,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

0 comments on commit cfa517d

Please sign in to comment.