Skip to content

Commit

Permalink
Always link generator frames when propagating a thrown-in exception t…
Browse files Browse the repository at this point in the history
…hrough a yield-from chain

Summary: "Back port" of python/cpython#126092

Reviewed By: alexmalyshev

Differential Revision: D66332155

fbshipit-source-id: 391f332af6dd58a5cd2deac30b8d87f3a762c399
  • Loading branch information
jbower-fb authored and facebook-github-bot committed Nov 23, 2024
1 parent 2ee2da1 commit 55b50e1
Show file tree
Hide file tree
Showing 2 changed files with 33 additions and 8 deletions.
22 changes: 21 additions & 1 deletion Lib/test/test_generators.py
Original file line number Diff line number Diff line change
Expand Up @@ -577,7 +577,8 @@ def check_stack_names(self, frame, expected):
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_'):
if (name.startswith('check_') or name.startswith('call_')
or name.startswith('test')):
break

names.append(name)
Expand Down Expand Up @@ -618,6 +619,25 @@ def call_throw(gen):

self.check_yield_from_example(call_throw)

def test_throw_with_yield_from_custom_generator(self):

class CustomGen:
def __init__(self, test):
self.test = test
def throw(self, *args):
self.test.check_stack_names(sys._getframe(), ['throw', 'g'])
def __iter__(self):
return self
def __next__(self):
return 42

def g(target):
yield from target

gen = g(CustomGen(self))
gen.send(None)
gen.throw(RuntimeError)


class YieldFromTests(unittest.TestCase):
def test_generator_gi_yieldfrom(self):
Expand Down
19 changes: 12 additions & 7 deletions Objects/genobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -482,14 +482,13 @@ _gen_throw(PyGenObject *gen, int close_on_genexit,
return gen_send_ex(gen, Py_None, 1, 0);
goto throw_here;
}
/* `yf` is a generator or a coroutine. */
PyThreadState *tstate = _PyThreadState_GET();
assert(tstate != NULL);
if (PyGen_CheckExact(yf) || PyCoro_CheckExact(yf)) {
/* `yf` is a generator or a coroutine. */
PyThreadState *tstate = _PyThreadState_GET();
/* 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. */
/* Link frame into the stack to enable complete backtraces. */
/* XXX We should probably be updating the current frame somewhere in
ceval.c. */
_PyInterpreterFrame *prev = tstate->cframe->current_frame;
frame->previous = prev;
tstate->cframe->current_frame = frame;
Expand All @@ -513,10 +512,16 @@ _gen_throw(PyGenObject *gen, int close_on_genexit,
Py_DECREF(yf);
goto throw_here;
}

_PyInterpreterFrame *prev = tstate->cframe->current_frame;
frame->previous = prev;
tstate->cframe->current_frame = frame;
PyFrameState state = gen->gi_frame_state;
gen->gi_frame_state = FRAME_EXECUTING;
ret = PyObject_CallFunctionObjArgs(meth, typ, val, tb, NULL);
gen->gi_frame_state = state;
tstate->cframe->current_frame = prev;
frame->previous = NULL;
Py_DECREF(meth);
}
Py_DECREF(yf);
Expand Down

0 comments on commit 55b50e1

Please sign in to comment.