]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
bpo-42197: Don't create `f_locals` dictionary unless we actually need it. (GH-32055)
authorMark Shannon <mark@hotpy.org>
Fri, 25 Mar 2022 12:57:50 +0000 (12:57 +0000)
committerGitHub <noreply@github.com>
Fri, 25 Mar 2022 12:57:50 +0000 (12:57 +0000)
* `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.

Doc/c-api/frame.rst
Doc/whatsnew/3.11.rst
Include/cpython/frameobject.h
Include/internal/pycore_frame.h
Misc/NEWS.d/next/Core and Builtins/2022-03-22-15-12-28.bpo-42197.SwrrFO.rst [new file with mode: 0644]
Objects/frameobject.c
Python/sysmodule.c

index 0e36e6e1fd706fc4fb7c0664fa3ca0fb4214d4fa..0c11bc163b417fee8bfcde2d041187871fc8d953 100644 (file)
@@ -41,6 +41,17 @@ See also :ref:`Reflection <reflection>`.
    .. versionadded:: 3.9
 
 
+.. c:function:: PyObject* PyFrame_GetLocals(PyFrameObject *frame)
+
+   Get the *frame*'s ``f_locals`` attribute (:class:`dict`).
+
+   Return a :term:`strong reference`.
+
+   *frame* must not be ``NULL``.
+
+   .. versionadded:: 3.11
+
+
 .. c:function:: int PyFrame_GetLineNumber(PyFrameObject *frame)
 
    Return the line number that *frame* is currently executing.
index b2fdb4852360d50d0a7cbf3cf723ed0eff6126b0..8c120ec45fe3fcb99ed440d5985329d34746af9e 100644 (file)
@@ -969,7 +969,7 @@ Porting to Python 3.11
     Code using ``f_lasti`` with ``PyCode_Addr2Line()`` must use
     :c:func:`PyFrame_GetLineNumber` instead.
   * ``f_lineno``: use :c:func:`PyFrame_GetLineNumber`
-  * ``f_locals``: use ``PyObject_GetAttrString((PyObject*)frame, "f_locals")``.
+  * ``f_locals``: use :c:func:`PyFrame_GetLocals`.
   * ``f_stackdepth``: removed.
   * ``f_state``: no public API (renamed to ``f_frame.f_state``).
   * ``f_trace``: no public API.
@@ -983,6 +983,12 @@ Porting to Python 3.11
   computed lazily. The :c:func:`PyFrame_GetBack` function must be called
   instead.
 
+  Debuggers that accessed the ``f_locals`` directly *must* call
+  `:c:func:`PyFrame_GetLocals` instead. They no longer need to call
+  `:c:func:`PyFrame_FastToLocalsWithError` or :c:func:`PyFrame_LocalsToFast`,
+  in fact they should not call those functions. The necessary updating of the
+  frame is now managed by the virtual machine.
+
   Code defining ``PyFrame_GetCode()`` on Python 3.8 and older::
 
       #if PY_VERSION_HEX < 0x030900B1
index 9b697fb3cbaf50369ba2ccf0fae8569c58fcb9ce..d54d3652a0dbc85c4a195fad7bba47ac5f687e47 100644 (file)
@@ -23,3 +23,4 @@ PyAPI_FUNC(int) PyFrame_FastToLocalsWithError(PyFrameObject *f);
 PyAPI_FUNC(void) PyFrame_FastToLocals(PyFrameObject *);
 
 PyAPI_FUNC(PyFrameObject *) PyFrame_GetBack(PyFrameObject *frame);
+PyAPI_FUNC(PyObject *) PyFrame_GetLocals(PyFrameObject *frame);
index 14fba8cd1f941c8d92683fd801d2704bd8210cbd..211831a6e497f52051d89260b1bd9229c48d9e69 100644 (file)
@@ -15,6 +15,7 @@ struct _frame {
     int f_lineno;               /* Current line number. Only valid if non-zero */
     char f_trace_lines;         /* Emit per-line trace events? */
     char f_trace_opcodes;       /* Emit per-opcode trace events? */
+    char f_fast_as_locals;      /* Have the fast locals of this frame been converted to a dict? */
     /* The frame data, if this frame object owns the frame */
     PyObject *_f_frame_data[1];
 };
diff --git a/Misc/NEWS.d/next/Core and Builtins/2022-03-22-15-12-28.bpo-42197.SwrrFO.rst b/Misc/NEWS.d/next/Core and Builtins/2022-03-22-15-12-28.bpo-42197.SwrrFO.rst
new file mode 100644 (file)
index 0000000..d54002a
--- /dev/null
@@ -0,0 +1,2 @@
+:c:func:`PyFrame_FastToLocalsWithError` and :c:func:`PyFrame_LocalsToFast` are no longer
+called during profiling nor tracing. C code can access the ``f_locals`` attribute of :c:type:`PyFrameObject` by calling :c:func:`PyFrame_GetLocals`.
index 5c6a8bcb9008da2b9f1a82f9e296c0c98715b44a..13dfbf6b9db41352ee7d81a6627a475d8453990f 100644 (file)
@@ -840,6 +840,7 @@ _PyFrame_New_NoTrack(PyCodeObject *code)
     f->f_trace = NULL;
     f->f_trace_lines = 1;
     f->f_trace_opcodes = 0;
+    f->f_fast_as_locals = 0;
     f->f_lineno = 0;
     return f;
 }
@@ -1004,7 +1005,11 @@ PyFrame_FastToLocalsWithError(PyFrameObject *f)
         PyErr_BadInternalCall();
         return -1;
     }
-    return _PyFrame_FastToLocalsWithError(f->f_frame);
+    int err = _PyFrame_FastToLocalsWithError(f->f_frame);
+    if (err == 0) {
+        f->f_fast_as_locals = 1;
+    }
+    return err;
 }
 
 void
@@ -1028,8 +1033,9 @@ _PyFrame_LocalsToFast(_PyInterpreterFrame *frame, int clear)
     PyObject *error_type, *error_value, *error_traceback;
     PyCodeObject *co;
     locals = frame->f_locals;
-    if (locals == NULL)
+    if (locals == NULL) {
         return;
+    }
     fast = _PyFrame_GetLocalsArray(frame);
     co = frame->f_code;
 
@@ -1088,13 +1094,12 @@ _PyFrame_LocalsToFast(_PyInterpreterFrame *frame, int clear)
 void
 PyFrame_LocalsToFast(PyFrameObject *f, int clear)
 {
-    if (f == NULL || _PyFrame_GetState(f) == FRAME_CLEARED) {
-        return;
+    if (f && f->f_fast_as_locals && _PyFrame_GetState(f) != FRAME_CLEARED) {
+        _PyFrame_LocalsToFast(f->f_frame, clear);
+        f->f_fast_as_locals = 0;
     }
-    _PyFrame_LocalsToFast(f->f_frame, clear);
 }
 
-
 PyCodeObject *
 PyFrame_GetCode(PyFrameObject *frame)
 {
@@ -1118,6 +1123,12 @@ PyFrame_GetBack(PyFrameObject *frame)
     return back;
 }
 
+PyObject*
+PyFrame_GetLocals(PyFrameObject *frame)
+{
+    return frame_getlocals(frame, NULL);
+}
+
 PyObject*
 _PyEval_BuiltinsFromGlobals(PyThreadState *tstate, PyObject *globals)
 {
index c89f81f689f7e3887cbbfdfe9a0845a222a6e121..6322af5f5ca8128bc38b9ac2d5a228dba027ccfa 100644 (file)
@@ -924,15 +924,19 @@ static PyObject *
 call_trampoline(PyThreadState *tstate, PyObject* callback,
                 PyFrameObject *frame, int what, PyObject *arg)
 {
-    if (PyFrame_FastToLocalsWithError(frame) < 0) {
-        return NULL;
-    }
 
     PyObject *stack[3];
     stack[0] = (PyObject *)frame;
     stack[1] = whatstrings[what];
     stack[2] = (arg != NULL) ? arg : Py_None;
 
+    /* Discard any previous modifications the frame's fast locals */
+    if (frame->f_fast_as_locals) {
+        if (PyFrame_FastToLocalsWithError(frame) < 0) {
+            return NULL;
+        }
+    }
+
     /* call the Python-level function */
     PyObject *result = _PyObject_FastCallTstate(tstate, callback, stack, 3);