Skip to content

Commit d7163bb

Browse files
authored
bpo-42197: Don't create f_locals dictionary unless we actually need it. (GH-32055)
* `PyFrame_FastToLocalsWithError` and `PyFrame_LocalsToFast` are no longer called during profile and tracing. (Contributed by Fabio Zadrozny) * Make accesses to a frame's `f_locals` safe from C code, not relying on calls to `PyFrame_FastToLocals` or `PyFrame_LocalsToFast`. * Document new `PyFrame_GetLocals` C-API function.
1 parent b68431f commit d7163bb

File tree

7 files changed

+46
-10
lines changed

7 files changed

+46
-10
lines changed

Doc/c-api/frame.rst

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,17 @@ See also :ref:`Reflection <reflection>`.
4141
.. versionadded:: 3.9
4242
4343
44+
.. c:function:: PyObject* PyFrame_GetLocals(PyFrameObject *frame)
45+
46+
Get the *frame*'s ``f_locals`` attribute (:class:`dict`).
47+
48+
Return a :term:`strong reference`.
49+
50+
*frame* must not be ``NULL``.
51+
52+
.. versionadded:: 3.11
53+
54+
4455
.. c:function:: int PyFrame_GetLineNumber(PyFrameObject *frame)
4556
4657
Return the line number that *frame* is currently executing.

Doc/whatsnew/3.11.rst

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -969,7 +969,7 @@ Porting to Python 3.11
969969
Code using ``f_lasti`` with ``PyCode_Addr2Line()`` must use
970970
:c:func:`PyFrame_GetLineNumber` instead.
971971
* ``f_lineno``: use :c:func:`PyFrame_GetLineNumber`
972-
* ``f_locals``: use ``PyObject_GetAttrString((PyObject*)frame, "f_locals")``.
972+
* ``f_locals``: use :c:func:`PyFrame_GetLocals`.
973973
* ``f_stackdepth``: removed.
974974
* ``f_state``: no public API (renamed to ``f_frame.f_state``).
975975
* ``f_trace``: no public API.
@@ -983,6 +983,12 @@ Porting to Python 3.11
983983
computed lazily. The :c:func:`PyFrame_GetBack` function must be called
984984
instead.
985985

986+
Debuggers that accessed the ``f_locals`` directly *must* call
987+
`:c:func:`PyFrame_GetLocals` instead. They no longer need to call
988+
`:c:func:`PyFrame_FastToLocalsWithError` or :c:func:`PyFrame_LocalsToFast`,
989+
in fact they should not call those functions. The necessary updating of the
990+
frame is now managed by the virtual machine.
991+
986992
Code defining ``PyFrame_GetCode()`` on Python 3.8 and older::
987993

988994
#if PY_VERSION_HEX < 0x030900B1

Include/cpython/frameobject.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,4 @@ PyAPI_FUNC(int) PyFrame_FastToLocalsWithError(PyFrameObject *f);
2323
PyAPI_FUNC(void) PyFrame_FastToLocals(PyFrameObject *);
2424

2525
PyAPI_FUNC(PyFrameObject *) PyFrame_GetBack(PyFrameObject *frame);
26+
PyAPI_FUNC(PyObject *) PyFrame_GetLocals(PyFrameObject *frame);

Include/internal/pycore_frame.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ struct _frame {
1515
int f_lineno; /* Current line number. Only valid if non-zero */
1616
char f_trace_lines; /* Emit per-line trace events? */
1717
char f_trace_opcodes; /* Emit per-opcode trace events? */
18+
char f_fast_as_locals; /* Have the fast locals of this frame been converted to a dict? */
1819
/* The frame data, if this frame object owns the frame */
1920
PyObject *_f_frame_data[1];
2021
};
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
:c:func:`PyFrame_FastToLocalsWithError` and :c:func:`PyFrame_LocalsToFast` are no longer
2+
called during profiling nor tracing. C code can access the ``f_locals`` attribute of :c:type:`PyFrameObject` by calling :c:func:`PyFrame_GetLocals`.

Objects/frameobject.c

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -840,6 +840,7 @@ _PyFrame_New_NoTrack(PyCodeObject *code)
840840
f->f_trace = NULL;
841841
f->f_trace_lines = 1;
842842
f->f_trace_opcodes = 0;
843+
f->f_fast_as_locals = 0;
843844
f->f_lineno = 0;
844845
return f;
845846
}
@@ -1004,7 +1005,11 @@ PyFrame_FastToLocalsWithError(PyFrameObject *f)
10041005
PyErr_BadInternalCall();
10051006
return -1;
10061007
}
1007-
return _PyFrame_FastToLocalsWithError(f->f_frame);
1008+
int err = _PyFrame_FastToLocalsWithError(f->f_frame);
1009+
if (err == 0) {
1010+
f->f_fast_as_locals = 1;
1011+
}
1012+
return err;
10081013
}
10091014

10101015
void
@@ -1028,8 +1033,9 @@ _PyFrame_LocalsToFast(_PyInterpreterFrame *frame, int clear)
10281033
PyObject *error_type, *error_value, *error_traceback;
10291034
PyCodeObject *co;
10301035
locals = frame->f_locals;
1031-
if (locals == NULL)
1036+
if (locals == NULL) {
10321037
return;
1038+
}
10331039
fast = _PyFrame_GetLocalsArray(frame);
10341040
co = frame->f_code;
10351041

@@ -1088,13 +1094,12 @@ _PyFrame_LocalsToFast(_PyInterpreterFrame *frame, int clear)
10881094
void
10891095
PyFrame_LocalsToFast(PyFrameObject *f, int clear)
10901096
{
1091-
if (f == NULL || _PyFrame_GetState(f) == FRAME_CLEARED) {
1092-
return;
1097+
if (f && f->f_fast_as_locals && _PyFrame_GetState(f) != FRAME_CLEARED) {
1098+
_PyFrame_LocalsToFast(f->f_frame, clear);
1099+
f->f_fast_as_locals = 0;
10931100
}
1094-
_PyFrame_LocalsToFast(f->f_frame, clear);
10951101
}
10961102

1097-
10981103
PyCodeObject *
10991104
PyFrame_GetCode(PyFrameObject *frame)
11001105
{
@@ -1118,6 +1123,12 @@ PyFrame_GetBack(PyFrameObject *frame)
11181123
return back;
11191124
}
11201125

1126+
PyObject*
1127+
PyFrame_GetLocals(PyFrameObject *frame)
1128+
{
1129+
return frame_getlocals(frame, NULL);
1130+
}
1131+
11211132
PyObject*
11221133
_PyEval_BuiltinsFromGlobals(PyThreadState *tstate, PyObject *globals)
11231134
{

Python/sysmodule.c

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -924,15 +924,19 @@ static PyObject *
924924
call_trampoline(PyThreadState *tstate, PyObject* callback,
925925
PyFrameObject *frame, int what, PyObject *arg)
926926
{
927-
if (PyFrame_FastToLocalsWithError(frame) < 0) {
928-
return NULL;
929-
}
930927

931928
PyObject *stack[3];
932929
stack[0] = (PyObject *)frame;
933930
stack[1] = whatstrings[what];
934931
stack[2] = (arg != NULL) ? arg : Py_None;
935932

933+
/* Discard any previous modifications the frame's fast locals */
934+
if (frame->f_fast_as_locals) {
935+
if (PyFrame_FastToLocalsWithError(frame) < 0) {
936+
return NULL;
937+
}
938+
}
939+
936940
/* call the Python-level function */
937941
PyObject *result = _PyObject_FastCallTstate(tstate, callback, stack, 3);
938942

0 commit comments

Comments
 (0)