https://github.com/python/cpython/commit/e65cb4c6f01a687f451ad9db1600525e1c5832c4
commit: e65cb4c6f01a687f451ad9db1600525e1c5832c4
branch: main
author: Tian Gao <[email protected]>
committer: gaogaotiantian <[email protected]>
date: 2024-07-16T12:17:47-07:00
summary:

gh-118934: Make PyEval_GetLocals return borrowed reference (#119769)

Co-authored-by: Alyssa Coghlan <[email protected]>

files:
A Misc/NEWS.d/next/Core and 
Builtins/2024-05-30-04-11-36.gh-issue-118934.fbDqve.rst
M Include/internal/pycore_frame.h
M Lib/test/test_sys.py
M Objects/frameobject.c
M Python/ceval.c

diff --git a/Include/internal/pycore_frame.h b/Include/internal/pycore_frame.h
index 506c20ca1950bd..d5115adf32ec1f 100644
--- a/Include/internal/pycore_frame.h
+++ b/Include/internal/pycore_frame.h
@@ -27,6 +27,10 @@ struct _frame {
     char f_trace_lines;         /* Emit per-line trace events? */
     char f_trace_opcodes;       /* Emit per-opcode trace events? */
     PyObject *f_extra_locals;   /* Dict for locals set by users using 
f_locals, could be NULL */
+    /* This is purely for backwards compatibility for PyEval_GetLocals.
+       PyEval_GetLocals requires a borrowed reference so the actual reference
+       is stored here */
+    PyObject *f_locals_cache;
     /* The frame data, if this frame object owns the frame */
     PyObject *_f_frame_data[1];
 };
diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py
index 69dccf9a9322f6..81789b20433be9 100644
--- a/Lib/test/test_sys.py
+++ b/Lib/test/test_sys.py
@@ -1603,7 +1603,7 @@ class C(object): pass
         def func():
             return sys._getframe()
         x = func()
-        check(x, size('3Pi2cP7P2ic??2P'))
+        check(x, size('3Pi2c2P7P2ic??2P'))
         # function
         def func(): pass
         check(func, size('16Pi'))
diff --git a/Misc/NEWS.d/next/Core and 
Builtins/2024-05-30-04-11-36.gh-issue-118934.fbDqve.rst b/Misc/NEWS.d/next/Core 
and Builtins/2024-05-30-04-11-36.gh-issue-118934.fbDqve.rst
new file mode 100644
index 00000000000000..3087034fe458b8
--- /dev/null
+++ b/Misc/NEWS.d/next/Core and 
Builtins/2024-05-30-04-11-36.gh-issue-118934.fbDqve.rst 
@@ -0,0 +1 @@
+Make ``PyEval_GetLocals`` return borrowed reference
diff --git a/Objects/frameobject.c b/Objects/frameobject.c
index 3dc9ff058a5c9e..2ef3beaf83a48f 100644
--- a/Objects/frameobject.c
+++ b/Objects/frameobject.c
@@ -1627,6 +1627,7 @@ frame_dealloc(PyFrameObject *f)
     Py_CLEAR(f->f_back);
     Py_CLEAR(f->f_trace);
     Py_CLEAR(f->f_extra_locals);
+    Py_CLEAR(f->f_locals_cache);
     PyObject_GC_Del(f);
     Py_XDECREF(co);
     Py_TRASHCAN_END;
@@ -1638,6 +1639,7 @@ frame_traverse(PyFrameObject *f, visitproc visit, void 
*arg)
     Py_VISIT(f->f_back);
     Py_VISIT(f->f_trace);
     Py_VISIT(f->f_extra_locals);
+    Py_VISIT(f->f_locals_cache);
     if (f->f_frame->owner != FRAME_OWNED_BY_FRAME_OBJECT) {
         return 0;
     }
@@ -1650,6 +1652,7 @@ frame_tp_clear(PyFrameObject *f)
 {
     Py_CLEAR(f->f_trace);
     Py_CLEAR(f->f_extra_locals);
+    Py_CLEAR(f->f_locals_cache);
 
     /* locals and stack */
     _PyStackRef *locals = _PyFrame_GetLocalsArray(f->f_frame);
@@ -1787,6 +1790,7 @@ _PyFrame_New_NoTrack(PyCodeObject *code)
     f->f_trace_opcodes = 0;
     f->f_lineno = 0;
     f->f_extra_locals = NULL;
+    f->f_locals_cache = NULL;
     return f;
 }
 
diff --git a/Python/ceval.c b/Python/ceval.c
index d8bc830f8e80c1..026e018676caad 100644
--- a/Python/ceval.c
+++ b/Python/ceval.c
@@ -2525,6 +2525,7 @@ _PyEval_GetBuiltinId(_Py_Identifier *name)
 PyObject *
 PyEval_GetLocals(void)
 {
+    // We need to return a borrowed reference here, so some tricks are needed
     PyThreadState *tstate = _PyThreadState_GET();
      _PyInterpreterFrame *current_frame = _PyThreadState_GetFrame(tstate);
     if (current_frame == NULL) {
@@ -2532,7 +2533,37 @@ PyEval_GetLocals(void)
         return NULL;
     }
 
-    PyObject *locals = _PyEval_GetFrameLocals();
+    // Be aware that this returns a new reference
+    PyObject *locals = _PyFrame_GetLocals(current_frame);
+
+    if (locals == NULL) {
+        return NULL;
+    }
+
+    if (PyFrameLocalsProxy_Check(locals)) {
+        PyFrameObject *f = _PyFrame_GetFrameObject(current_frame);
+        PyObject *ret = f->f_locals_cache;
+        if (ret == NULL) {
+            PyObject *ret = PyDict_New();
+            if (ret == NULL) {
+                Py_DECREF(locals);
+                return NULL;
+            }
+            f->f_locals_cache = ret;
+        }
+        if (PyDict_Update(ret, locals) < 0) {
+            // At this point, if the cache dict is broken, it will stay 
broken, as
+            // trying to clean it up or replace it will just cause other 
problems
+            ret = NULL;
+        }
+        Py_DECREF(locals);
+        return ret;
+    }
+
+    assert(PyMapping_Check(locals));
+    assert(Py_REFCNT(locals) > 1);
+    Py_DECREF(locals);
+
     return locals;
 }
 

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: [email protected]

Reply via email to