Skip to content
Merged
10 changes: 10 additions & 0 deletions Include/internal/pycore_frame.h
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,16 @@ PyGenObject *_PyFrame_GetGenerator(_PyInterpreterFrame *frame)
return (PyGenObject *)(((char *)frame) - offset_in_gen);
}

/* Determine whether a frame is incomplete.
* A frame is incomplete until the first RESUME instruction
* as it may be part way through creating cell objects or a
* generator or coroutine. */
static inline bool
_PyFrame_IsIncomplete(_PyInterpreterFrame *frame)
Comment thread
markshannon marked this conversation as resolved.
Outdated
{
return frame->prev_instr < _PyCode_CODE(frame->f_code) + frame->f_code->_co_firsttraceable;
Comment thread
markshannon marked this conversation as resolved.
Outdated
}

#ifdef __cplusplus
}
#endif
Expand Down
20 changes: 20 additions & 0 deletions Lib/test/test_generators.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,26 @@ def f():
g.send(0)
self.assertEqual(next(g), 1)

def test_handle_frame_object_in_creation(self):
Comment thread
markshannon marked this conversation as resolved.

def cb(*args):
try:
sys._getframe(1)
except ValueError:
pass

def gen():
yield 1

thresholds = gc.get_threshold()
gc.callbacks.append(cb)
gc.set_threshold(1, 0, 0)

try:
gen()
finally:
gc.set_threshold(*thresholds)
gc.callbacks.pop()

class ExceptionTest(unittest.TestCase):
# Tests for the issue #23353: check that the currently handled exception
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Don't create frame objects for incomplete frames. Prevents the creation of
generators and closures from being observable to Python and C extensions,
restoring the behavior of 3.10 and earlier.
8 changes: 6 additions & 2 deletions Python/frame.c
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,13 @@ take_ownership(PyFrameObject *f, _PyInterpreterFrame *frame)
f->f_frame = frame;
frame->owner = FRAME_OWNED_BY_FRAME_OBJECT;
assert(f->f_back == NULL);
if (frame->previous != NULL) {
_PyInterpreterFrame *prev = frame->previous;
while (prev && _PyFrame_IsIncomplete(prev)) {
prev = prev->previous;
}
if (prev) {
/* Link PyFrameObjects.f_back and remove link through _PyInterpreterFrame.previous */
PyFrameObject *back = _PyFrame_GetFrameObject(frame->previous);
PyFrameObject *back = _PyFrame_GetFrameObject(prev);
if (back == NULL) {
/* Memory error here. */
assert(PyErr_ExceptionMatches(PyExc_MemoryError));
Expand Down
14 changes: 11 additions & 3 deletions Python/sysmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -1776,9 +1776,17 @@ sys__getframe_impl(PyObject *module, int depth)
return NULL;
}

while (depth > 0 && frame != NULL) {
frame = frame->previous;
--depth;
if (frame != NULL) {
while (depth > 0) {
frame = frame->previous;
if (frame == NULL) {
break;
}
if (_PyFrame_IsIncomplete(frame)) {
continue;
}
--depth;
}
}
if (frame == NULL) {
_PyErr_SetString(tstate, PyExc_ValueError,
Expand Down