From: Mikhail Efimov Date: Sat, 20 Dec 2025 14:21:11 +0000 (+0300) Subject: [3.13] gh-140373: Correctly emit PY_UNWIND event when generator is closed (GH-140767... X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=4504ff89d5cfaa9f5a57956d20030184ca959361;p=thirdparty%2FPython%2Fcpython.git [3.13] gh-140373: Correctly emit PY_UNWIND event when generator is closed (GH-140767) (#140821) --- diff --git a/Include/internal/pycore_ceval.h b/Include/internal/pycore_ceval.h index f804823cc530..25605533aacf 100644 --- a/Include/internal/pycore_ceval.h +++ b/Include/internal/pycore_ceval.h @@ -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); diff --git a/Lib/test/test_cprofile.py b/Lib/test/test_cprofile.py index b46edf66bf09..acead07d507e 100644 --- a/Lib/test/test_cprofile.py +++ b/Lib/test/test_cprofile.py @@ -136,8 +136,8 @@ class CProfileTest(ProfileTest): for func, (cc, nc, _, _, _) in pr.stats.items(): if func[2] == "": - self.assertEqual(cc, 1) - self.assertEqual(nc, 1) + self.assertEqual(cc, 2) + self.assertEqual(nc, 2) def test_bad_descriptor(self): # gh-132250 diff --git a/Lib/test/test_monitoring.py b/Lib/test/test_monitoring.py index 094d25b88c6e..64435a382bb5 100644 --- a/Lib/test/test_monitoring.py +++ b/Lib/test/test_monitoring.py @@ -1017,6 +1017,25 @@ class ExceptionMonitoringTest(CheckEvents): 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 diff --git a/Lib/test/test_sys_setprofile.py b/Lib/test/test_sys_setprofile.py index b2e8e8a15b67..e7ba97891a36 100644 --- a/Lib/test/test_sys_setprofile.py +++ b/Lib/test/test_sys_setprofile.py @@ -272,6 +272,8 @@ class ProfileHookTestCase(TestCaseBase): 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) diff --git a/Misc/NEWS.d/next/Core and Builtins/2025-10-29-20-59-10.gh-issue-140373.-uoaPP.rst b/Misc/NEWS.d/next/Core and Builtins/2025-10-29-20-59-10.gh-issue-140373.-uoaPP.rst new file mode 100644 index 000000000000..c9a97037920f --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2025-10-29-20-59-10.gh-issue-140373.-uoaPP.rst @@ -0,0 +1,2 @@ +Correctly emit ``PY_UNWIND`` event when generator object is closed. Patch by +Mikhail Efimov. diff --git a/Objects/genobject.c b/Objects/genobject.c index 412d3c1090b5..b19ff252ddb2 100644 --- a/Objects/genobject.c +++ b/Objects/genobject.c @@ -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; diff --git a/Python/ceval.c b/Python/ceval.c index 301cc3b2b903..ca07bfbaaf6a 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -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,