Skip to content

Commit e8bb053

Browse files
authored
gh-126091: Always link generator frames when propagating a thrown-in exception through a yield-from chain (#126092)
Always link generator frames when propagating a thrown-in exception through a yield-from chain.
1 parent 3fafc1b commit e8bb053

File tree

3 files changed

+35
-7
lines changed

3 files changed

+35
-7
lines changed

Lib/test/test_generators.py

+21-1
Original file line numberDiff line numberDiff line change
@@ -758,7 +758,8 @@ def check_stack_names(self, frame, expected):
758758
while frame:
759759
name = frame.f_code.co_name
760760
# Stop checking frames when we get to our test helper.
761-
if name.startswith('check_') or name.startswith('call_'):
761+
if (name.startswith('check_') or name.startswith('call_')
762+
or name.startswith('test')):
762763
break
763764

764765
names.append(name)
@@ -799,6 +800,25 @@ def call_throw(gen):
799800

800801
self.check_yield_from_example(call_throw)
801802

803+
def test_throw_with_yield_from_custom_generator(self):
804+
805+
class CustomGen:
806+
def __init__(self, test):
807+
self.test = test
808+
def throw(self, *args):
809+
self.test.check_stack_names(sys._getframe(), ['throw', 'g'])
810+
def __iter__(self):
811+
return self
812+
def __next__(self):
813+
return 42
814+
815+
def g(target):
816+
yield from target
817+
818+
gen = g(CustomGen(self))
819+
gen.send(None)
820+
gen.throw(RuntimeError)
821+
802822

803823
class YieldFromTests(unittest.TestCase):
804824
def test_generator_gi_yieldfrom(self):
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Ensure stack traces are complete when throwing into a generator chain that
2+
ends in a custom generator.

Objects/genobject.c

+12-6
Original file line numberDiff line numberDiff line change
@@ -471,14 +471,14 @@ _gen_throw(PyGenObject *gen, int close_on_genexit,
471471
return gen_send_ex(gen, Py_None, 1, 0);
472472
goto throw_here;
473473
}
474+
PyThreadState *tstate = _PyThreadState_GET();
475+
assert(tstate != NULL);
474476
if (PyGen_CheckExact(yf) || PyCoro_CheckExact(yf)) {
475477
/* `yf` is a generator or a coroutine. */
476-
PyThreadState *tstate = _PyThreadState_GET();
477-
/* Since we are fast-tracking things by skipping the eval loop,
478-
we need to update the current frame so the stack trace
479-
will be reported correctly to the user. */
480-
/* XXX We should probably be updating the current frame
481-
somewhere in ceval.c. */
478+
479+
/* Link frame into the stack to enable complete backtraces. */
480+
/* XXX We should probably be updating the current frame somewhere in
481+
ceval.c. */
482482
_PyInterpreterFrame *prev = tstate->current_frame;
483483
frame->previous = prev;
484484
tstate->current_frame = frame;
@@ -502,10 +502,16 @@ _gen_throw(PyGenObject *gen, int close_on_genexit,
502502
Py_DECREF(yf);
503503
goto throw_here;
504504
}
505+
506+
_PyInterpreterFrame *prev = tstate->current_frame;
507+
frame->previous = prev;
508+
tstate->current_frame = frame;
505509
PyFrameState state = gen->gi_frame_state;
506510
gen->gi_frame_state = FRAME_EXECUTING;
507511
ret = PyObject_CallFunctionObjArgs(meth, typ, val, tb, NULL);
508512
gen->gi_frame_state = state;
513+
tstate->current_frame = prev;
514+
frame->previous = NULL;
509515
Py_DECREF(meth);
510516
}
511517
Py_DECREF(yf);

0 commit comments

Comments
 (0)