From f3515cae3a900d929d4dc9b771622b3e63e43080 Mon Sep 17 00:00:00 2001 From: Irit Katriel Date: Tue, 30 Apr 2024 23:06:46 +0100 Subject: [PATCH 1/2] gh-118272: Clear generator frame's locals when the generator is closed --- Include/internal/pycore_frame.h | 3 +++ Lib/test/test_generators.py | 20 +++++++++++++++++++ ...-04-30-23-06-10.gh-issue-118272.5ptjk_.rst | 2 ++ Objects/genobject.c | 1 + Python/frame.c | 17 +++++++++++----- 5 files changed, 38 insertions(+), 5 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2024-04-30-23-06-10.gh-issue-118272.5ptjk_.rst diff --git a/Include/internal/pycore_frame.h b/Include/internal/pycore_frame.h index bfe4a759bac078..ad7d74c5dd2b9f 100644 --- a/Include/internal/pycore_frame.h +++ b/Include/internal/pycore_frame.h @@ -213,6 +213,9 @@ _PyFrame_GetFrameObject(_PyInterpreterFrame *frame) return _PyFrame_MakeAndSetFrameObject(frame); } +void +_PyFrame_ClearLocals(_PyInterpreterFrame *frame); + /* Clears all references in the frame. * If take is non-zero, then the _PyInterpreterFrame frame * may be transferred to the frame object it references diff --git a/Lib/test/test_generators.py b/Lib/test/test_generators.py index 1ee9958445bf18..e0da9152c33954 100644 --- a/Lib/test/test_generators.py +++ b/Lib/test/test_generators.py @@ -450,6 +450,26 @@ def g(): self.assertIsInstance(cm.exception.value, StopIteration) self.assertEqual(cm.exception.value.value, 2) + def test_close_releases_frame_locals(self): + # See gh-118272 + + class Foo: + pass + + f = Foo() + f_wr = weakref.ref(f) + + def genfn(): + a = f + yield + + g = genfn() + next(g) + del f + g.close() + support.gc_collect() + self.assertIsNone(f_wr()) + class GeneratorThrowTest(unittest.TestCase): diff --git a/Misc/NEWS.d/next/Core and Builtins/2024-04-30-23-06-10.gh-issue-118272.5ptjk_.rst b/Misc/NEWS.d/next/Core and Builtins/2024-04-30-23-06-10.gh-issue-118272.5ptjk_.rst new file mode 100644 index 00000000000000..32043440fd0365 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2024-04-30-23-06-10.gh-issue-118272.5ptjk_.rst @@ -0,0 +1,2 @@ +Fix bug where ``generator.close`` does not free the generator frame's +locals. diff --git a/Objects/genobject.c b/Objects/genobject.c index 3fc2ac083d1c15..a69f1f3f5e54a2 100644 --- a/Objects/genobject.c +++ b/Objects/genobject.c @@ -403,6 +403,7 @@ gen_close(PyGenObject *gen, PyObject *args) * StopIteration. */ if (exception_handler_depth == 1) { gen->gi_frame_state = FRAME_COMPLETED; + _PyFrame_ClearLocals((_PyInterpreterFrame *)gen->gi_iframe); Py_RETURN_NONE; } } diff --git a/Python/frame.c b/Python/frame.c index b84fd9b6a9380a..87ff7db52d8ddb 100644 --- a/Python/frame.c +++ b/Python/frame.c @@ -115,6 +115,17 @@ take_ownership(PyFrameObject *f, _PyInterpreterFrame *frame) } } +void +_PyFrame_ClearLocals(_PyInterpreterFrame *frame) +{ + assert(frame->stacktop >= 0); + for (int i = 0; i < frame->stacktop; i++) { + Py_XDECREF(frame->localsplus[i]); + } + frame->stacktop = 0; + Py_CLEAR(frame->f_locals); +} + void _PyFrame_ClearExceptCode(_PyInterpreterFrame *frame) { @@ -135,12 +146,8 @@ _PyFrame_ClearExceptCode(_PyInterpreterFrame *frame) } Py_DECREF(f); } - assert(frame->stacktop >= 0); - for (int i = 0; i < frame->stacktop; i++) { - Py_XDECREF(frame->localsplus[i]); - } + _PyFrame_ClearLocals(frame); Py_XDECREF(frame->frame_obj); - Py_XDECREF(frame->f_locals); Py_DECREF(frame->f_funcobj); } From 3dc25ae8c0e8b699843065690996106a28be267e Mon Sep 17 00:00:00 2001 From: Irit Katriel Date: Wed, 1 May 2024 15:49:15 +0100 Subject: [PATCH 2/2] set stacktop to 0 before decref --- Python/frame.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Python/frame.c b/Python/frame.c index 87ff7db52d8ddb..a49215fa44a796 100644 --- a/Python/frame.c +++ b/Python/frame.c @@ -119,10 +119,11 @@ void _PyFrame_ClearLocals(_PyInterpreterFrame *frame) { assert(frame->stacktop >= 0); - for (int i = 0; i < frame->stacktop; i++) { + int stacktop = frame->stacktop; + frame->stacktop = 0; + for (int i = 0; i < stacktop; i++) { Py_XDECREF(frame->localsplus[i]); } - frame->stacktop = 0; Py_CLEAR(frame->f_locals); }