8000 bpo-29590: fix stack trace for gen.throw() with yield from (#19896) · python/cpython@8b33961 · GitHub
[go: up one dir, main page]

Skip to content

Commit 8b33961

Browse files
authored
bpo-29590: fix stack trace for gen.throw() with yield from (#19896)
* Add failing test. * bpo-29590: fix stack trace for gen.throw() with yield from (GH-NNNN) When gen.throw() is called on a generator after a "yield from", the intermediate stack trace entries are lost. This commit fixes that.
1 parent 96a6a6d commit 8b33961

File tree

3 files changed

+61
-0
lines changed

3 files changed

+61
-0
lines changed

Lib/test/test_generators.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,55 @@ def g():
415415
gen.throw(ValueError)
416416

417417

418+
class GeneratorStackTraceTest(unittest.TestCase):
419+
420+
def check_stack_names(self, frame, expected):
421+
names = []
422+
while frame:
423+
name = frame.f_code.co_name
424+
# Stop checking frames when we get to our test helper.
425+
if name.startswith('check_') or name.startswith('call_'):
426+
break
427+
428+
names.append(name)
429+
frame = frame.f_back
430+
431+
self.assertEqual(names, expected)
432+
433+
def check_yield_from_example(self, call_method):
434+
def f():
435+
self.check_stack_names(sys._getframe(), ['f', 'g'])
436+
try:
437+
yield
438+
except Exception:
439+
pass
440+
self.check_stack_names(sys._getframe(), ['f', 'g'])
441+
442+
def g():
443+
self.check_stack_names(sys._getframe(), ['g'])
444+
yield from f()
445+
self.check_stack_names(sys._getframe(), ['g'])
446+
447+
gen = g()
448+
gen.send(None)
449+
try:
450+
call_method(gen)
451+
except StopIteration:
452+
pass
453+
454+
def test_send_with_yield_from(self):
455+
def call_send(gen):
456+
gen.send(None)
457+
458+
self.check_yield_from_example(call_send)
459+
460+
def test_throw_with_yield_from(self):
461+
def call_throw(gen):
462+
gen.throw(RuntimeError)
463+
464+
self.check_yield_from_example(call_throw)
465+
466+
418467
class YieldFromTests(unittest.TestCase):
419468
def test_generator_gi_yieldfrom(self):
420469
def a():
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Make the stack trace correct after calling :meth:`generator.throw`
2+
on a generator that has yielded from a ``yield from``.

Objects/genobject.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -415,11 +415,21 @@ _gen_throw(PyGenObject *gen, int close_on_genexit,
415415
}
416416
if (PyGen_CheckExact(yf) || PyCoro_CheckExact(yf)) {
417417
/* `yf` is a generator or a coroutine. */
418+
PyThreadState *tstate = _PyThreadState_GET();
419+
PyFrameObject *f = tstate->frame;
420+
418421
gen->gi_running = 1;
422+
/* Since we are fast-tracking things by skipping the eval loop,
423+
we need to update the current frame so the stack trace
424+
will be reported correctly to the user. */
425+
/* XXX We should probably be updating the current frame
426+
somewhere in ceval.c. */
427+
tstate->frame = gen->gi_frame;
419428
/* Close the generator that we are currently iterating with
420429
'yield from' or awaiting on with 'await'. */
421430
ret = _gen_throw((PyGenObject *)yf, close_on_genexit,
422431
typ, val, tb);
432+
tstate->frame = f;
423433
gen->gi_running = 0;
424434
} else {
425435
/* `yf` is an iterator or a coroutine-like object. */

0 commit comments

Comments
 (0)
0