[3.13] gh-140373: Correctly emit PY_UNWIND event when generator is closed (GH-140767) (#140821)

This commit is contained in:
Mikhail Efimov 2025-12-20 17:21:11 +03:00 committed by GitHub
parent 5d82520e38
commit 4504ff89d5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 32 additions and 3 deletions

View file

@ -258,6 +258,7 @@ PyAPI_FUNC(PyObject *)_PyEval_MatchClass(PyThreadState *tstate, PyObject *subjec
PyAPI_FUNC(PyObject *)_PyEval_MatchKeys(PyThreadState *tstate, PyObject *map, PyObject *keys);
PyAPI_FUNC(int) _PyEval_UnpackIterable(PyThreadState *tstate, PyObject *v, int argcnt, int argcntafter, PyObject **sp);
PyAPI_FUNC(void) _PyEval_MonitorRaise(PyThreadState *tstate, _PyInterpreterFrame *frame, _Py_CODEUNIT *instr);
PyAPI_FUNC(bool) _PyEval_NoToolsForUnwind(PyThreadState *tstate);
PyAPI_FUNC(void) _PyEval_FrameClearAndPop(PyThreadState *tstate, _PyInterpreterFrame *frame);

View file

@ -136,8 +136,8 @@ def test_throw(self):
for func, (cc, nc, _, _, _) in pr.stats.items():
if func[2] == "<genexpr>":
self.assertEqual(cc, 1)
self.assertEqual(nc, 1)
self.assertEqual(cc, 2)
self.assertEqual(nc, 2)
def test_bad_descriptor(self):
# gh-132250

View file

@ -1017,6 +1017,25 @@ def f():
self.assertEqual(events, expected)
# gh-140373
def test_gen_unwind(self):
def gen():
yield 1
def f():
g = gen()
next(g)
g.close()
recorders = (
UnwindRecorder,
)
events = self.get_events(f, TEST_TOOL, recorders)
expected = [
("unwind", GeneratorExit, "gen"),
]
self.assertEqual(events, expected)
class LineRecorder:
event_type = E.LINE

View file

@ -272,6 +272,8 @@ def g(p):
self.check_events(g, [(1, 'call', g_ident, None),
(2, 'call', f_ident, None),
(2, 'return', f_ident, 0),
(2, 'call', f_ident, None),
(2, 'return', f_ident, None),
(1, 'return', g_ident, None),
], check_args=True)

View file

@ -0,0 +1,2 @@
Correctly emit ``PY_UNWIND`` event when generator object is closed. Patch by
Mikhail Efimov.

View file

@ -382,11 +382,12 @@ gen_close(PyGenObject *gen, PyObject *args)
}
_PyInterpreterFrame *frame = (_PyInterpreterFrame *)gen->gi_iframe;
if (is_resume(frame->instr_ptr)) {
bool no_unwind_tools = _PyEval_NoToolsForUnwind(_PyThreadState_GET());
/* We can safely ignore the outermost try block
* as it is automatically generated to handle
* StopIteration. */
int oparg = frame->instr_ptr->op.arg;
if (oparg & RESUME_OPARG_DEPTH1_MASK) {
if (oparg & RESUME_OPARG_DEPTH1_MASK && no_unwind_tools) {
// RESUME after YIELD_VALUE and exception depth is 1
assert((oparg & RESUME_OPARG_LOCATION_MASK) != RESUME_AT_FUNC_START);
gen->gi_frame_state = FRAME_COMPLETED;

View file

@ -2272,6 +2272,10 @@ monitor_unwind(PyThreadState *tstate,
do_monitor_exc(tstate, frame, instr, PY_MONITORING_EVENT_PY_UNWIND);
}
bool
_PyEval_NoToolsForUnwind(PyThreadState *tstate) {
return no_tools_for_global_event(tstate, PY_MONITORING_EVENT_PY_UNWIND);
}
static int
monitor_handled(PyThreadState *tstate,