Skip to content

Commit 1048576

Browse files
gaogaotiantianncoghlan
authored andcommitted
gh-118934: Make PyEval_GetLocals return borrowed reference (#119769)
Co-authored-by: Alyssa Coghlan <[email protected]>
1 parent 547ab3f commit 1048576

File tree

5 files changed

+42
-2
lines changed

5 files changed

+42
-2
lines changed

Include/internal/pycore_frame.h

+4
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ struct _frame {
2727
char f_trace_lines; /* Emit per-line trace events? */
2828
char f_trace_opcodes; /* Emit per-opcode trace events? */
2929
PyObject *f_extra_locals; /* Dict for locals set by users using f_locals, could be NULL */
30+
/* This is purely for backwards compatibility for PyEval_GetLocals.
31+
PyEval_GetLocals requires a borrowed reference so the actual reference
32+
is stored here */
33+
PyObject *f_locals_cache;
3034
/* The frame data, if this frame object owns the frame */
3135
PyObject *_f_frame_data[1];
3236
};

Lib/test/test_sys.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1603,7 +1603,7 @@ class C(object): pass
16031603
def func():
16041604
return sys._getframe()
16051605
x = func()
1606-
check(x, size('3Pi2cP7P2ic??2P'))
1606+
check(x, size('3Pi2c2P7P2ic??2P'))
16071607
# function
16081608
def func(): pass
16091609
check(func, size('16Pi'))
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Make ``PyEval_GetLocals`` return borrowed reference

Objects/frameobject.c

+4
Original file line numberDiff line numberDiff line change
@@ -1627,6 +1627,7 @@ frame_dealloc(PyFrameObject *f)
16271627
Py_CLEAR(f->f_back);
16281628
Py_CLEAR(f->f_trace);
16291629
Py_CLEAR(f->f_extra_locals);
1630+
Py_CLEAR(f->f_locals_cache);
16301631
PyObject_GC_Del(f);
16311632
Py_XDECREF(co);
16321633
Py_TRASHCAN_END;
@@ -1638,6 +1639,7 @@ frame_traverse(PyFrameObject *f, visitproc visit, void *arg)
16381639
Py_VISIT(f->f_back);
16391640
Py_VISIT(f->f_trace);
16401641
Py_VISIT(f->f_extra_locals);
1642+
Py_VISIT(f->f_locals_cache);
16411643
if (f->f_frame->owner != FRAME_OWNED_BY_FRAME_OBJECT) {
16421644
return 0;
16431645
}
@@ -1650,6 +1652,7 @@ frame_tp_clear(PyFrameObject *f)
16501652
{
16511653
Py_CLEAR(f->f_trace);
16521654
Py_CLEAR(f->f_extra_locals);
1655+
Py_CLEAR(f->f_locals_cache);
16531656

16541657
/* locals and stack */
16551658
_PyStackRef *locals = _PyFrame_GetLocalsArray(f->f_frame);
@@ -1787,6 +1790,7 @@ _PyFrame_New_NoTrack(PyCodeObject *code)
17871790
f->f_trace_opcodes = 0;
17881791
f->f_lineno = 0;
17891792
f->f_extra_locals = NULL;
1793+
f->f_locals_cache = NULL;
17901794
return f;
17911795
}
17921796

Python/ceval.c

+32-1
Original file line numberDiff line numberDiff line change
@@ -2525,14 +2525,45 @@ _PyEval_GetBuiltinId(_Py_Identifier *name)
25252525
PyObject *
25262526
PyEval_GetLocals(void)
25272527
{
2528+
// We need to return a borrowed reference here, so some tricks are needed
25282529
PyThreadState *tstate = _PyThreadState_GET();
25292530
_PyInterpreterFrame *current_frame = _PyThreadState_GetFrame(tstate);
25302531
if (current_frame == NULL) {
25312532
_PyErr_SetString(tstate, PyExc_SystemError, "frame does not exist");
25322533
return NULL;
25332534
}
25342535

2535-
PyObject *locals = _PyEval_GetFrameLocals();
2536+
// Be aware that this returns a new reference
2537+
PyObject *locals = _PyFrame_GetLocals(current_frame);
2538+
2539+
if (locals == NULL) {
2540+
return NULL;
2541+
}
2542+
2543+
if (PyFrameLocalsProxy_Check(locals)) {
2544+
PyFrameObject *f = _PyFrame_GetFrameObject(current_frame);
2545+
PyObject *ret = f->f_locals_cache;
2546+
if (ret == NULL) {
2547+
PyObject *ret = PyDict_New();
2548+
if (ret == NULL) {
2549+
Py_DECREF(locals);
2550+
return NULL;
2551+
}
2552+
f->f_locals_cache = ret;
2553+
}
2554+
if (PyDict_Update(ret, locals) < 0) {
2555+
// At this point, if the cache dict is broken, it will stay broken, as
2556+
// trying to clean it up or replace it will just cause other problems
2557+
ret = NULL;
2558+
}
2559+
Py_DECREF(locals);
2560+
return ret;
2561+
}
2562+
2563+
assert(PyMapping_Check(locals));
2564+
assert(Py_REFCNT(locals) > 1);
2565+
Py_DECREF(locals);
2566+
25362567
return locals;
25372568
}
25382569

0 commit comments

Comments
 (0)