From 5b4a4b6f0905c60514528b454af43aeea058b5a2 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Fri, 8 Apr 2022 12:18:57 +0100 Subject: Add new PyFrame_GetLasti C-API function (GH-32413) --- Doc/c-api/frame.rst | 11 +++++++++++ Doc/whatsnew/3.11.rst | 6 +++--- Include/cpython/frameobject.h | 1 + Lib/test/test_capi.py | 1 + .../next/C API/2022-04-08-11-29-36.bpo-40421.H0ORmT.rst | 2 ++ Modules/_testcapimodule.c | 16 ++++++++++++++++ Objects/frameobject.c | 12 +++++++++++- 7 files changed, 45 insertions(+), 4 deletions(-) create mode 100644 Misc/NEWS.d/next/C API/2022-04-08-11-29-36.bpo-40421.H0ORmT.rst diff --git a/Doc/c-api/frame.rst b/Doc/c-api/frame.rst index f6c682c..68e5dc6 100644 --- a/Doc/c-api/frame.rst +++ b/Doc/c-api/frame.rst @@ -78,6 +78,17 @@ See also :ref:`Reflection `. .. versionadded:: 3.11 +.. c:function:: int PyFrame_GetLasti(PyFrameObject *frame) + + Get the *frame*'s ``f_lasti`` attribute (:class:`dict`). + + Returns -1 if ``frame.f_lasti`` is ``None``. + + *frame* must not be ``NULL``. + + .. versionadded:: 3.11 + + .. c:function:: PyObject* PyFrame_GetLocals(PyFrameObject *frame) Get the *frame*'s ``f_locals`` attribute (:class:`dict`). diff --git a/Doc/whatsnew/3.11.rst b/Doc/whatsnew/3.11.rst index bc4a195..2da01d8 100644 --- a/Doc/whatsnew/3.11.rst +++ b/Doc/whatsnew/3.11.rst @@ -1121,7 +1121,7 @@ New Features * Add new functions to get frame object attributes: :c:func:`PyFrame_GetBuiltins`, :c:func:`PyFrame_GetGenerator`, - :c:func:`PyFrame_GetGlobals`. + :c:func:`PyFrame_GetGlobals`, :c:func:`PyFrame_GetLasti`. Porting to Python 3.11 ---------------------- @@ -1246,9 +1246,9 @@ Porting to Python 3.11 * ``f_gen``: use :c:func:`PyFrame_GetGenerator`. * ``f_globals``: use :c:func:`PyFrame_GetGlobals`. * ``f_iblock``: removed. - * ``f_lasti``: use ``PyObject_GetAttrString((PyObject*)frame, "f_lasti")``. + * ``f_lasti``: use :c:func:`PyFrame_GetLasti`. Code using ``f_lasti`` with ``PyCode_Addr2Line()`` should use - :c:func:`PyFrame_GetLineNumber` instead. + :c:func:`PyFrame_GetLineNumber` instead; it may be faster. * ``f_lineno``: use :c:func:`PyFrame_GetLineNumber` * ``f_locals``: use :c:func:`PyFrame_GetLocals`. * ``f_stackdepth``: removed. diff --git a/Include/cpython/frameobject.h b/Include/cpython/frameobject.h index ffeb8bd..01cf6c9 100644 --- a/Include/cpython/frameobject.h +++ b/Include/cpython/frameobject.h @@ -29,3 +29,4 @@ PyAPI_FUNC(PyObject *) PyFrame_GetGlobals(PyFrameObject *frame); PyAPI_FUNC(PyObject *) PyFrame_GetBuiltins(PyFrameObject *frame); PyAPI_FUNC(PyObject *) PyFrame_GetGenerator(PyFrameObject *frame); +PyAPI_FUNC(int) PyFrame_GetLasti(PyFrameObject *frame); diff --git a/Lib/test/test_capi.py b/Lib/test/test_capi.py index 3837f80..40e4774 100644 --- a/Lib/test/test_capi.py +++ b/Lib/test/test_capi.py @@ -1102,6 +1102,7 @@ class Test_FrameAPI(unittest.TestCase): 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)) + self.assertEqual(frame.f_lasti, _testcapi.frame_getlasti(frame)) def test_frame_get_generator(self): gen = self.getgenframe() diff --git a/Misc/NEWS.d/next/C API/2022-04-08-11-29-36.bpo-40421.H0ORmT.rst b/Misc/NEWS.d/next/C API/2022-04-08-11-29-36.bpo-40421.H0ORmT.rst new file mode 100644 index 0000000..2e10c23 --- /dev/null +++ b/Misc/NEWS.d/next/C API/2022-04-08-11-29-36.bpo-40421.H0ORmT.rst @@ -0,0 +1,2 @@ +Add ``PyFrame_GetLasti`` C-API function to access frame object's ``lasti`` +attribute safely from C code. diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index 759656a..13dd294 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -5893,6 +5893,21 @@ frame_getbuiltins(PyObject *self, PyObject *frame) return PyFrame_GetBuiltins((PyFrameObject *)frame); } +static PyObject * +frame_getlasti(PyObject *self, PyObject *frame) +{ + if (!PyFrame_Check(frame)) { + PyErr_SetString(PyExc_TypeError, "argument must be a frame"); + return NULL; + } + int lasti = PyFrame_GetLasti((PyFrameObject *)frame); + if (lasti < 0) { + assert(lasti == -1); + Py_RETURN_NONE; + } + return PyLong_FromLong(lasti); +} + static PyObject *negative_dictoffset(PyObject *, PyObject *); static PyObject *test_buildvalue_issue38913(PyObject *, PyObject *); @@ -6186,6 +6201,7 @@ static PyMethodDef TestMethods[] = { {"frame_getglobals", frame_getglobals, METH_O, NULL}, {"frame_getgenerator", frame_getgenerator, METH_O, NULL}, {"frame_getbuiltins", frame_getbuiltins, METH_O, NULL}, + {"frame_getlasti", frame_getlasti, METH_O, NULL}, {NULL, NULL} /* sentinel */ }; diff --git a/Objects/frameobject.c b/Objects/frameobject.c index 07b6107..5bb8937 100644 --- a/Objects/frameobject.c +++ b/Objects/frameobject.c @@ -880,7 +880,7 @@ _PyFrame_OpAlreadyRan(_PyInterpreterFrame *frame, int opcode, int oparg) // This only works when opcode is a non-quickened form: assert(_PyOpcode_Deopt[opcode] == opcode); int check_oparg = 0; - for (_Py_CODEUNIT *instruction = _PyCode_CODE(frame->f_code); + for (_Py_CODEUNIT *instruction = _PyCode_CODE(frame->f_code); instruction < frame->prev_instr; instruction++) { int check_opcode = _PyOpcode_Deopt[_Py_OPCODE(*instruction)]; @@ -1135,6 +1135,16 @@ PyFrame_GetBuiltins(PyFrameObject *frame) return frame_getbuiltins(frame, NULL); } +int +PyFrame_GetLasti(PyFrameObject *frame) +{ + int lasti = _PyInterpreterFrame_LASTI(frame->f_frame); + if (lasti < 0) { + return -1; + } + return lasti*2; +} + PyObject * PyFrame_GetGenerator(PyFrameObject *frame) { -- cgit v0.12