Skip to content

Commit cfa517d

Browse files
authored
GH-96803: Document and test new unstable internal frame API functions (GH-104211)
Weaken contract of PyUnstable_InterpreterFrame_GetCode to return PyObject*.
1 parent 68b5f08 commit cfa517d

File tree

5 files changed

+98
-3
lines changed

5 files changed

+98
-3
lines changed

Doc/c-api/frame.rst

+35
Original file line numberDiff line numberDiff line change
@@ -130,3 +130,38 @@ See also :ref:`Reflection <reflection>`.
130130
.. c:function:: int PyFrame_GetLineNumber(PyFrameObject *frame)
131131
132132
Return the line number that *frame* is currently executing.
133+
134+
135+
136+
Internal Frames
137+
---------------
138+
139+
Unless using :pep:`523`, you will not need this.
140+
141+
.. c:struct:: _PyInterpreterFrame
142+
143+
The interpreter's internal frame representation.
144+
145+
.. versionadded:: 3.11
146+
147+
.. c:function:: PyObject* PyUnstable_InterpreterFrame_GetCode(struct _PyInterpreterFrame *frame);
148+
149+
Return a :term:`strong reference` to the code object for the frame.
150+
151+
.. versionadded:: 3.12
152+
153+
154+
.. c:function:: int PyUnstable_InterpreterFrame_GetLasti(struct _PyInterpreterFrame *frame);
155+
156+
Return the byte offset into the last executed instruction.
157+
158+
.. versionadded:: 3.12
159+
160+
161+
.. c:function:: int PyUnstable_InterpreterFrame_GetLine(struct _PyInterpreterFrame *frame);
162+
163+
Return the currently executing line number, or -1 if there is no line number.
164+
165+
.. versionadded:: 3.12
166+
167+

Include/cpython/frameobject.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ PyAPI_FUNC(void) PyFrame_FastToLocals(PyFrameObject *);
3535

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

4040
/* Returns a byte ofsset into the last executed instruction.
4141
* Does not raise an exception. */

Lib/test/test_capi/test_misc.py

+24
Original file line numberDiff line numberDiff line change
@@ -1744,6 +1744,30 @@ class Subclass(BaseException, self.module.StateAccessType):
17441744
self.assertIs(Subclass().get_defining_module(), self.module)
17451745

17461746

1747+
class TestInternalFrameApi(unittest.TestCase):
1748+
1749+
@staticmethod
1750+
def func():
1751+
return sys._getframe()
1752+
1753+
def test_code(self):
1754+
frame = self.func()
1755+
code = _testinternalcapi.iframe_getcode(frame)
1756+
self.assertIs(code, self.func.__code__)
1757+
1758+
def test_lasti(self):
1759+
frame = self.func()
1760+
lasti = _testinternalcapi.iframe_getlasti(frame)
1761+
self.assertGreater(lasti, 0)
1762+
self.assertLess(lasti, len(self.func.__code__.co_code))
1763+
1764+
def test_line(self):
1765+
frame = self.func()
1766+
line = _testinternalcapi.iframe_getline(frame)
1767+
firstline = self.func.__code__.co_firstlineno
1768+
self.assertEqual(line, firstline + 2)
1769+
1770+
17471771
SUFFICIENT_TO_DEOPT_AND_SPECIALIZE = 100
17481772

17491773
class Test_Pep523API(unittest.TestCase):

Modules/_testinternalcapi.c

+36
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#define PY_SSIZE_T_CLEAN
1313

1414
#include "Python.h"
15+
#include "frameobject.h"
1516
#include "pycore_atomic_funcs.h" // _Py_atomic_int_get()
1617
#include "pycore_bitutils.h" // _Py_bswap32()
1718
#include "pycore_compile.h" // _PyCompile_CodeGen, _PyCompile_OptimizeCfg, _PyCompile_Assemble
@@ -757,6 +758,38 @@ clear_extension(PyObject *self, PyObject *args)
757758
Py_RETURN_NONE;
758759
}
759760

761+
static PyObject *
762+
iframe_getcode(PyObject *self, PyObject *frame)
763+
{
764+
if (!PyFrame_Check(frame)) {
765+
PyErr_SetString(PyExc_TypeError, "argument must be a frame");
766+
return NULL;
767+
}
768+
struct _PyInterpreterFrame *f = ((PyFrameObject *)frame)->f_frame;
769+
return PyUnstable_InterpreterFrame_GetCode(f);
770+
}
771+
772+
static PyObject *
773+
iframe_getline(PyObject *self, PyObject *frame)
774+
{
775+
if (!PyFrame_Check(frame)) {
776+
PyErr_SetString(PyExc_TypeError, "argument must be a frame");
777+
return NULL;
778+
}
779+
struct _PyInterpreterFrame *f = ((PyFrameObject *)frame)->f_frame;
780+
return PyLong_FromLong(PyUnstable_InterpreterFrame_GetLine(f));
781+
}
782+
783+
static PyObject *
784+
iframe_getlasti(PyObject *self, PyObject *frame)
785+
{
786+
if (!PyFrame_Check(frame)) {
787+
PyErr_SetString(PyExc_TypeError, "argument must be a frame");
788+
return NULL;
789+
}
790+
struct _PyInterpreterFrame *f = ((PyFrameObject *)frame)->f_frame;
791+
return PyLong_FromLong(PyUnstable_InterpreterFrame_GetLasti(f));
792+
}
760793

761794
static PyMethodDef module_functions[] = {
762795
{"get_configs", get_configs, METH_NOARGS},
@@ -781,6 +814,9 @@ static PyMethodDef module_functions[] = {
781814
_TESTINTERNALCAPI_ASSEMBLE_CODE_OBJECT_METHODDEF
782815
{"get_interp_settings", get_interp_settings, METH_VARARGS, NULL},
783816
{"clear_extension", clear_extension, METH_VARARGS, NULL},
817+
{"iframe_getcode", iframe_getcode, METH_O, NULL},
818+
{"iframe_getline", iframe_getline, METH_O, NULL},
819+
{"iframe_getlasti", iframe_getlasti, METH_O, NULL},
784820
{NULL, NULL} /* sentinel */
785821
};
786822

Python/frame.c

+2-2
Original file line numberDiff line numberDiff line change
@@ -146,10 +146,10 @@ _PyFrame_ClearExceptCode(_PyInterpreterFrame *frame)
146146

147147
/* Unstable API functions */
148148

149-
PyCodeObject *
149+
PyObject *
150150
PyUnstable_InterpreterFrame_GetCode(struct _PyInterpreterFrame *frame)
151151
{
152-
PyCodeObject *code = frame->f_code;
152+
PyObject *code = (PyObject *)frame->f_code;
153153
Py_INCREF(code);
154154
return code;
155155
}

0 commit comments

Comments
 (0)