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

Co-authored-by: Alyssa Coghlan <ncoghlan@gmail.com>
This commit is contained in:
Tian Gao 2024-07-16 12:17:47 -07:00 committed by GitHub
parent 162b41f577
commit e65cb4c6f0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 42 additions and 2 deletions

View File

@ -27,6 +27,10 @@ struct _frame {
char f_trace_lines; /* Emit per-line trace events? */ char f_trace_lines; /* Emit per-line trace events? */
char f_trace_opcodes; /* Emit per-opcode 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 */ 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 */ /* The frame data, if this frame object owns the frame */
PyObject *_f_frame_data[1]; PyObject *_f_frame_data[1];
}; };

View File

@ -1603,7 +1603,7 @@ class SizeofTest(unittest.TestCase):
def func(): def func():
return sys._getframe() return sys._getframe()
x = func() x = func()
check(x, size('3Pi2cP7P2ic??2P')) check(x, size('3Pi2c2P7P2ic??2P'))
# function # function
def func(): pass def func(): pass
check(func, size('16Pi')) check(func, size('16Pi'))

View File

@ -0,0 +1 @@
Make ``PyEval_GetLocals`` return borrowed reference

View File

@ -1627,6 +1627,7 @@ frame_dealloc(PyFrameObject *f)
Py_CLEAR(f->f_back); Py_CLEAR(f->f_back);
Py_CLEAR(f->f_trace); Py_CLEAR(f->f_trace);
Py_CLEAR(f->f_extra_locals); Py_CLEAR(f->f_extra_locals);
Py_CLEAR(f->f_locals_cache);
PyObject_GC_Del(f); PyObject_GC_Del(f);
Py_XDECREF(co); Py_XDECREF(co);
Py_TRASHCAN_END; Py_TRASHCAN_END;
@ -1638,6 +1639,7 @@ frame_traverse(PyFrameObject *f, visitproc visit, void *arg)
Py_VISIT(f->f_back); Py_VISIT(f->f_back);
Py_VISIT(f->f_trace); Py_VISIT(f->f_trace);
Py_VISIT(f->f_extra_locals); Py_VISIT(f->f_extra_locals);
Py_VISIT(f->f_locals_cache);
if (f->f_frame->owner != FRAME_OWNED_BY_FRAME_OBJECT) { if (f->f_frame->owner != FRAME_OWNED_BY_FRAME_OBJECT) {
return 0; return 0;
} }
@ -1650,6 +1652,7 @@ frame_tp_clear(PyFrameObject *f)
{ {
Py_CLEAR(f->f_trace); Py_CLEAR(f->f_trace);
Py_CLEAR(f->f_extra_locals); Py_CLEAR(f->f_extra_locals);
Py_CLEAR(f->f_locals_cache);
/* locals and stack */ /* locals and stack */
_PyStackRef *locals = _PyFrame_GetLocalsArray(f->f_frame); _PyStackRef *locals = _PyFrame_GetLocalsArray(f->f_frame);
@ -1787,6 +1790,7 @@ _PyFrame_New_NoTrack(PyCodeObject *code)
f->f_trace_opcodes = 0; f->f_trace_opcodes = 0;
f->f_lineno = 0; f->f_lineno = 0;
f->f_extra_locals = NULL; f->f_extra_locals = NULL;
f->f_locals_cache = NULL;
return f; return f;
} }

View File

@ -2525,6 +2525,7 @@ _PyEval_GetBuiltinId(_Py_Identifier *name)
PyObject * PyObject *
PyEval_GetLocals(void) PyEval_GetLocals(void)
{ {
// We need to return a borrowed reference here, so some tricks are needed
PyThreadState *tstate = _PyThreadState_GET(); PyThreadState *tstate = _PyThreadState_GET();
_PyInterpreterFrame *current_frame = _PyThreadState_GetFrame(tstate); _PyInterpreterFrame *current_frame = _PyThreadState_GetFrame(tstate);
if (current_frame == NULL) { if (current_frame == NULL) {
@ -2532,7 +2533,37 @@ PyEval_GetLocals(void)
return NULL; 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; return locals;
} }