[Python-checkins] bpo-29590: fix stack trace for gen.throw() with yield from (#19896)
Chris Jerdonek
webhook-mailer at python.org
Thu Jul 9 09:27:32 EDT 2020
https://github.com/python/cpython/commit/8b33961e4bc4020d8b2d5b949ad9d5c669300e89
commit: 8b33961e4bc4020d8b2d5b949ad9d5c669300e89
branch: master
author: Chris Jerdonek <chris.jerdonek at gmail.com>
committer: GitHub <noreply at github.com>
date: 2020-07-09T14:27:23+01:00
summary:
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.
files:
A Misc/NEWS.d/next/Core and Builtins/2020-05-03-22-26-00.bpo-29590.aRz3l7.rst
M Lib/test/test_generators.py
M Objects/genobject.c
diff --git a/Lib/test/test_generators.py b/Lib/test/test_generators.py
index bf482213c178a..3bf152280868e 100644
--- a/Lib/test/test_generators.py
+++ b/Lib/test/test_generators.py
@@ -415,6 +415,55 @@ def g():
gen.throw(ValueError)
+class GeneratorStackTraceTest(unittest.TestCase):
+
+ def check_stack_names(self, frame, expected):
+ names = []
+ while frame:
+ name = frame.f_code.co_name
+ # Stop checking frames when we get to our test helper.
+ if name.startswith('check_') or name.startswith('call_'):
+ break
+
+ names.append(name)
+ frame = frame.f_back
+
+ self.assertEqual(names, expected)
+
+ def check_yield_from_example(self, call_method):
+ def f():
+ self.check_stack_names(sys._getframe(), ['f', 'g'])
+ try:
+ yield
+ except Exception:
+ pass
+ self.check_stack_names(sys._getframe(), ['f', 'g'])
+
+ def g():
+ self.check_stack_names(sys._getframe(), ['g'])
+ yield from f()
+ self.check_stack_names(sys._getframe(), ['g'])
+
+ gen = g()
+ gen.send(None)
+ try:
+ call_method(gen)
+ except StopIteration:
+ pass
+
+ def test_send_with_yield_from(self):
+ def call_send(gen):
+ gen.send(None)
+
+ self.check_yield_from_example(call_send)
+
+ def test_throw_with_yield_from(self):
+ def call_throw(gen):
+ gen.throw(RuntimeError)
+
+ self.check_yield_from_example(call_throw)
+
+
class YieldFromTests(unittest.TestCase):
def test_generator_gi_yieldfrom(self):
def a():
diff --git a/Misc/NEWS.d/next/Core and Builtins/2020-05-03-22-26-00.bpo-29590.aRz3l7.rst b/Misc/NEWS.d/next/Core and Builtins/2020-05-03-22-26-00.bpo-29590.aRz3l7.rst
new file mode 100644
index 0000000000000..2570c4f2c7c0f
--- /dev/null
+++ b/Misc/NEWS.d/next/Core and Builtins/2020-05-03-22-26-00.bpo-29590.aRz3l7.rst
@@ -0,0 +1,2 @@
+Make the stack trace correct after calling :meth:`generator.throw`
+on a generator that has yielded from a ``yield from``.
diff --git a/Objects/genobject.c b/Objects/genobject.c
index 6a68c9484a6ae..a379fa6088e16 100644
--- a/Objects/genobject.c
+++ b/Objects/genobject.c
@@ -415,11 +415,21 @@ _gen_throw(PyGenObject *gen, int close_on_genexit,
}
if (PyGen_CheckExact(yf) || PyCoro_CheckExact(yf)) {
/* `yf` is a generator or a coroutine. */
+ PyThreadState *tstate = _PyThreadState_GET();
+ PyFrameObject *f = tstate->frame;
+
gen->gi_running = 1;
+ /* Since we are fast-tracking things by skipping the eval loop,
+ we need to update the current frame so the stack trace
+ will be reported correctly to the user. */
+ /* XXX We should probably be updating the current frame
+ somewhere in ceval.c. */
+ tstate->frame = gen->gi_frame;
/* Close the generator that we are currently iterating with
'yield from' or awaiting on with 'await'. */
ret = _gen_throw((PyGenObject *)yf, close_on_genexit,
typ, val, tb);
+ tstate->frame = f;
gen->gi_running = 0;
} else {
/* `yf` is an iterator or a coroutine-like object. */
More information about the Python-checkins
mailing list