From 13405ecffda6c66d6a5cf2a22dff892c108c69db Mon Sep 17 00:00:00 2001 From: Irit Katriel <1055913+iritkatriel@users.noreply.github.com> Date: Tue, 7 Nov 2023 08:49:30 +0000 Subject: [PATCH] gh-79932: raise exception if frame.clear() is called on a suspended frame (#111792) --- Doc/reference/datamodel.rst | 7 ++++++- Doc/whatsnew/3.13.rst | 4 ++++ Lib/test/test_frame.py | 15 +++++++++------ .../2023-11-06-16-44-09.gh-issue-79932.2qv7uD.rst | 1 + Objects/frameobject.c | 7 +++++++ 5 files changed, 27 insertions(+), 7 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2023-11-06-16-44-09.gh-issue-79932.2qv7uD.rst diff --git a/Doc/reference/datamodel.rst b/Doc/reference/datamodel.rst index 9e9fe831f4a647..f7d3d2d0bbec23 100644 --- a/Doc/reference/datamodel.rst +++ b/Doc/reference/datamodel.rst @@ -1214,10 +1214,15 @@ Frame objects support one method: objects (for example when catching an exception and storing its traceback for later use). - :exc:`RuntimeError` is raised if the frame is currently executing. + :exc:`RuntimeError` is raised if the frame is currently executing + or suspended. .. versionadded:: 3.4 + .. versionchanged:: 3.13 + Attempting to clear a suspended frame raises :exc:`RuntimeError` + (as has always been the case for executing frames). + .. _traceback-objects: diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index e12c2a1b0454fd..ef83f662788fe4 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -397,6 +397,10 @@ Deprecated and methods that consider plural forms even if the translation was not found. (Contributed by Serhiy Storchaka in :gh:`88434`.) +* Calling :meth:`frame.clear` on a suspended frame raises :exc:`RuntimeError` + (as has always been the case for an executing frame). + (Contributed by Irit Katriel in :gh:`79932`.) + Pending Removal in Python 3.14 ------------------------------ diff --git a/Lib/test/test_frame.py b/Lib/test/test_frame.py index 9491c7facdf077..7f17666a8d9697 100644 --- a/Lib/test/test_frame.py +++ b/Lib/test/test_frame.py @@ -80,9 +80,11 @@ def g(): gen = g() next(gen) self.assertFalse(endly) - # Clearing the frame closes the generator - gen.gi_frame.clear() - self.assertTrue(endly) + + # Cannot clear a suspended frame + with self.assertRaisesRegex(RuntimeError, r'suspended frame'): + gen.gi_frame.clear() + self.assertFalse(endly) def test_clear_executing(self): # Attempting to clear an executing frame is forbidden. @@ -114,9 +116,10 @@ def g(): gen = g() f = next(gen) self.assertFalse(endly) - # Clearing the frame closes the generator - f.clear() - self.assertTrue(endly) + # Cannot clear a suspended frame + with self.assertRaisesRegex(RuntimeError, 'suspended frame'): + f.clear() + self.assertFalse(endly) def test_lineno_with_tracing(self): def record_line(): diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-11-06-16-44-09.gh-issue-79932.2qv7uD.rst b/Misc/NEWS.d/next/Core and Builtins/2023-11-06-16-44-09.gh-issue-79932.2qv7uD.rst new file mode 100644 index 00000000000000..543dbe4413027a --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2023-11-06-16-44-09.gh-issue-79932.2qv7uD.rst @@ -0,0 +1 @@ +Raise exception if :meth:`frame.clear` is called on a suspended frame. diff --git a/Objects/frameobject.c b/Objects/frameobject.c index 3a10f622ccc6c7..be330a775872c2 100644 --- a/Objects/frameobject.c +++ b/Objects/frameobject.c @@ -937,6 +937,9 @@ frame_clear(PyFrameObject *f, PyObject *Py_UNUSED(ignored)) if (gen->gi_frame_state == FRAME_EXECUTING) { goto running; } + if (FRAME_STATE_SUSPENDED(gen->gi_frame_state)) { + goto suspended; + } _PyGen_Finalize((PyObject *)gen); } else if (f->f_frame->owner == FRAME_OWNED_BY_THREAD) { @@ -951,6 +954,10 @@ frame_clear(PyFrameObject *f, PyObject *Py_UNUSED(ignored)) PyErr_SetString(PyExc_RuntimeError, "cannot clear an executing frame"); return NULL; +suspended: + PyErr_SetString(PyExc_RuntimeError, + "cannot clear a suspended frame"); + return NULL; } PyDoc_STRVAR(clear__doc__,