gh-140373: Correctly emit PY_UNWIND event when generator is closed (GH-140767)

This commit is contained in:
Mikhail Efimov
2025-10-31 13:09:22 +03:00
committed by GitHub
parent ac1ffd7785
commit d17f28fed5
6 changed files with 30 additions and 1 deletions

View File

@@ -301,6 +301,7 @@ PyAPI_FUNC(PyObject *) _PyEval_ImportName(PyThreadState *, _PyInterpreterFrame *
PyAPI_FUNC(PyObject *)_PyEval_MatchClass(PyThreadState *tstate, PyObject *subject, PyObject *type, Py_ssize_t nargs, PyObject *kwargs); PyAPI_FUNC(PyObject *)_PyEval_MatchClass(PyThreadState *tstate, PyObject *subject, PyObject *type, Py_ssize_t nargs, PyObject *kwargs);
PyAPI_FUNC(PyObject *)_PyEval_MatchKeys(PyThreadState *tstate, PyObject *map, PyObject *keys); PyAPI_FUNC(PyObject *)_PyEval_MatchKeys(PyThreadState *tstate, PyObject *map, PyObject *keys);
PyAPI_FUNC(void) _PyEval_MonitorRaise(PyThreadState *tstate, _PyInterpreterFrame *frame, _Py_CODEUNIT *instr); PyAPI_FUNC(void) _PyEval_MonitorRaise(PyThreadState *tstate, _PyInterpreterFrame *frame, _Py_CODEUNIT *instr);
PyAPI_FUNC(bool) _PyEval_NoToolsForUnwind(PyThreadState *tstate);
PyAPI_FUNC(int) _PyEval_UnpackIterableStackRef(PyThreadState *tstate, PyObject *v, int argcnt, int argcntafter, _PyStackRef *sp); PyAPI_FUNC(int) _PyEval_UnpackIterableStackRef(PyThreadState *tstate, PyObject *v, int argcnt, int argcntafter, _PyStackRef *sp);
PyAPI_FUNC(void) _PyEval_FrameClearAndPop(PyThreadState *tstate, _PyInterpreterFrame *frame); PyAPI_FUNC(void) _PyEval_FrameClearAndPop(PyThreadState *tstate, _PyInterpreterFrame *frame);
PyAPI_FUNC(PyObject **) _PyObjectArray_FromStackRefArray(_PyStackRef *input, Py_ssize_t nargs, PyObject **scratch); PyAPI_FUNC(PyObject **) _PyObjectArray_FromStackRefArray(_PyStackRef *input, Py_ssize_t nargs, PyObject **scratch);

View File

@@ -1079,6 +1079,25 @@ class ExceptionMonitoringTest(CheckEvents):
self.assertEqual(events, expected) 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: class LineRecorder:
event_type = E.LINE event_type = E.LINE

View File

@@ -272,6 +272,8 @@ class ProfileHookTestCase(TestCaseBase):
self.check_events(g, [(1, 'call', g_ident, None), self.check_events(g, [(1, 'call', g_ident, None),
(2, 'call', f_ident, None), (2, 'call', f_ident, None),
(2, 'return', f_ident, 0), (2, 'return', f_ident, 0),
(2, 'call', f_ident, None),
(2, 'return', f_ident, None),
(1, 'return', g_ident, None), (1, 'return', g_ident, None),
], check_args=True) ], 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

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

View File

@@ -2466,6 +2466,10 @@ monitor_unwind(PyThreadState *tstate,
do_monitor_exc(tstate, frame, instr, PY_MONITORING_EVENT_PY_UNWIND); 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 static int
monitor_handled(PyThreadState *tstate, monitor_handled(PyThreadState *tstate,