]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.13] gh-140373: Correctly emit PY_UNWIND event when generator is closed (GH-140767...
authorMikhail Efimov <efimov.mikhail@gmail.com>
Sat, 20 Dec 2025 14:21:11 +0000 (17:21 +0300)
committerGitHub <noreply@github.com>
Sat, 20 Dec 2025 14:21:11 +0000 (16:21 +0200)
Include/internal/pycore_ceval.h
Lib/test/test_cprofile.py
Lib/test/test_monitoring.py
Lib/test/test_sys_setprofile.py
Misc/NEWS.d/next/Core and Builtins/2025-10-29-20-59-10.gh-issue-140373.-uoaPP.rst [new file with mode: 0644]
Objects/genobject.c
Python/ceval.c

index f804823cc530dd9e2cd3565fedc8d9d45fcb33d2..25605533aacf8fe7db82b44da2e2a7ee0d8276a6 100644 (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);
 
 
index b46edf66bf09f8ae4d71a26112cf17cf1d66f3dc..acead07d507e4b8518fcdc19d851ba0b05431b8b 100644 (file)
@@ -136,8 +136,8 @@ class CProfileTest(ProfileTest):
 
         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
index 094d25b88c6e613cd783ffe8b45562bbb362f4d2..64435a382bb5780d2f0c4d2aad38e3aeebf12f66 100644 (file)
@@ -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
index b2e8e8a15b67eae73750b5ea4e4ed4993cbb7707..e7ba97891a3635ab71d0c8602b59d52047e2b4b2 100644 (file)
@@ -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 (file)
index 0000000..c9a9703
--- /dev/null
@@ -0,0 +1,2 @@
+Correctly emit ``PY_UNWIND`` event when generator object is closed. Patch by
+Mikhail Efimov.
index 412d3c1090b5229edfda96328a37265879c6bdf8..b19ff252ddb213e4a2a523188a814b04f9bb85d5 100644 (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;
index 301cc3b2b90358c42f61c35fe1db394eb891bc1d..ca07bfbaaf6a38dd72f40a7c1e87240f9213aacd 100644 (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,