From: Miss Islington (bot) <31488909+miss-islington@users.noreply.github.com> Date: Tue, 10 Oct 2023 09:12:52 +0000 (+0200) Subject: [3.11] gh-110378: Close invalid generators in contextmanager and asynccontextmanager... X-Git-Tag: v3.11.7~219 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=2b3a4182798bc95a426e55d1f9f7199e2f2b5662;p=thirdparty%2FPython%2Fcpython.git [3.11] gh-110378: Close invalid generators in contextmanager and asynccontextmanager (GH-110499) (#110589) contextmanager and asynccontextmanager context managers now close an invalid underlying generator object that yields more then one value. (cherry picked from commit 96fed66a65097eac2dc528ce29c9ba676bb07689) Co-authored-by: Serhiy Storchaka Co-authored-by: Ɓukasz Langa --- diff --git a/Lib/contextlib.py b/Lib/contextlib.py index 58e9a498878d..4a338f5c637d 100644 --- a/Lib/contextlib.py +++ b/Lib/contextlib.py @@ -145,7 +145,10 @@ class _GeneratorContextManager( except StopIteration: return False else: - raise RuntimeError("generator didn't stop") + try: + raise RuntimeError("generator didn't stop") + finally: + self.gen.close() else: if value is None: # Need to force instantiation so we can reliably @@ -187,7 +190,10 @@ class _GeneratorContextManager( raise exc.__traceback__ = traceback return False - raise RuntimeError("generator didn't stop after throw()") + try: + raise RuntimeError("generator didn't stop after throw()") + finally: + self.gen.close() class _AsyncGeneratorContextManager( _GeneratorContextManagerBase, @@ -212,7 +218,10 @@ class _AsyncGeneratorContextManager( except StopAsyncIteration: return False else: - raise RuntimeError("generator didn't stop") + try: + raise RuntimeError("generator didn't stop") + finally: + await self.gen.aclose() else: if value is None: # Need to force instantiation so we can reliably @@ -254,7 +263,10 @@ class _AsyncGeneratorContextManager( raise exc.__traceback__ = traceback return False - raise RuntimeError("generator didn't stop after athrow()") + try: + raise RuntimeError("generator didn't stop after athrow()") + finally: + await self.gen.aclose() def contextmanager(func): diff --git a/Lib/test/test_contextlib.py b/Lib/test/test_contextlib.py index ec06785b5667..093f2593a4a4 100644 --- a/Lib/test/test_contextlib.py +++ b/Lib/test/test_contextlib.py @@ -156,9 +156,24 @@ class ContextManagerTestCase(unittest.TestCase): yield ctx = whoo() ctx.__enter__() - self.assertRaises( - RuntimeError, ctx.__exit__, TypeError, TypeError("foo"), None - ) + with self.assertRaises(RuntimeError): + ctx.__exit__(TypeError, TypeError("foo"), None) + if support.check_impl_detail(cpython=True): + # The "gen" attribute is an implementation detail. + self.assertFalse(ctx.gen.gi_suspended) + + def test_contextmanager_trap_second_yield(self): + @contextmanager + def whoo(): + yield + yield + ctx = whoo() + ctx.__enter__() + with self.assertRaises(RuntimeError): + ctx.__exit__(None, None, None) + if support.check_impl_detail(cpython=True): + # The "gen" attribute is an implementation detail. + self.assertFalse(ctx.gen.gi_suspended) def test_contextmanager_except(self): state = [] diff --git a/Misc/NEWS.d/next/Library/2023-10-07-13-50-12.gh-issue-110378.Y4L8fl.rst b/Misc/NEWS.d/next/Library/2023-10-07-13-50-12.gh-issue-110378.Y4L8fl.rst new file mode 100644 index 000000000000..ef5395fc3c64 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-10-07-13-50-12.gh-issue-110378.Y4L8fl.rst @@ -0,0 +1,3 @@ +:func:`~contextlib.contextmanager` and +:func:`~contextlib.asynccontextmanager` context managers now close an invalid +underlying generator object that yields more then one value.