From: Benjamin Peterson Date: Fri, 5 Feb 2010 02:12:14 +0000 (+0000) Subject: normalize exceptions passed to the __exit__ method #7853 X-Git-Tag: v2.7a3~16 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=565d78586babda2b62cbe4f89c2dd3cace79c0fa;p=thirdparty%2FPython%2Fcpython.git normalize exceptions passed to the __exit__ method #7853 In Python 2.x, exceptions in finally blocks are not normalized. Since with statements are implemented using finally blocks, ceval.c had to be tweaked to distinguish between with finally blocks and normal ones. A test for the finalization of generators containing with statements was also added. --- diff --git a/Lib/test/test_generators.py b/Lib/test/test_generators.py index ad7e17cef50e..19bfe074c4ec 100644 --- a/Lib/test/test_generators.py +++ b/Lib/test/test_generators.py @@ -1700,6 +1700,17 @@ And finalization: >>> del g exiting +>>> class context(object): +... def __enter__(self): pass +... def __exit__(self, *args): print 'exiting' +>>> def f(): +... with context(): +... yield +>>> g = f() +>>> g.next() +>>> del g +exiting + GeneratorExit is not caught by except Exception: diff --git a/Lib/test/test_with.py b/Lib/test/test_with.py index 4b947d82e475..a1ec80b3d4db 100644 --- a/Lib/test/test_with.py +++ b/Lib/test/test_with.py @@ -361,7 +361,6 @@ class ExceptionalTestCase(unittest.TestCase, ContextmanagerAssertionMixin): self.assertAfterWithManagerInvariantsWithError(cm) self.assertAfterWithGeneratorInvariantsWithError(self.resource) - @unittest.expectedFailure def testExceptionNormalized(self): cm = mock_contextmanager_generator() def shouldThrow(): diff --git a/Misc/NEWS b/Misc/NEWS index 6bb355bdcbfc..9225c6fc4cee 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -12,6 +12,9 @@ What's New in Python 2.7 alpha 3? Core and Builtins ----------------- +- Issue #7853: Normalize exceptions before they are passed to a context managers + __exit__ method. + - Issue #7385: Fix a crash in `MemoryView_FromObject` when `PyObject_GetBuffer` fails. Patch by Florent Xicluna. diff --git a/Python/ceval.c b/Python/ceval.c index d501a4e10a2b..0b8a377ee443 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -2555,9 +2555,11 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag) Py_DECREF(u); if (!x) break; - /* Setup the finally block before pushing the result - of __enter__ on the stack. */ - PyFrame_BlockSetup(f, SETUP_FINALLY, INSTR_OFFSET() + oparg, + /* Setup a finally block (SETUP_WITH as a block is + equivalent to SETUP_FINALLY except it normalizes + the exception) before pushing the result of + __enter__ on the stack. */ + PyFrame_BlockSetup(f, SETUP_WITH, INSTR_OFFSET() + oparg, STACK_LEVEL()); PUSH(x); @@ -2898,7 +2900,8 @@ fast_block_end: } if (b->b_type == SETUP_FINALLY || (b->b_type == SETUP_EXCEPT && - why == WHY_EXCEPTION)) { + why == WHY_EXCEPTION) || + b->b_type == SETUP_WITH) { if (why == WHY_EXCEPTION) { PyObject *exc, *val, *tb; PyErr_Fetch(&exc, &val, &tb); @@ -2911,7 +2914,8 @@ fast_block_end: so a program can emulate the Python main loop. Don't do this for 'finally'. */ - if (b->b_type == SETUP_EXCEPT) { + if (b->b_type == SETUP_EXCEPT || + b->b_type == SETUP_WITH) { PyErr_NormalizeException( &exc, &val, &tb); set_exc_info(tstate, diff --git a/Python/compile.c b/Python/compile.c index 1e275390c988..4ea2ee9afe34 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -2927,6 +2927,9 @@ compiler_with(struct compiler *c, stmt_ty s) /* SETUP_WITH pushes a finally block. */ compiler_use_next_block(c, block); + /* Note that the block is actually called SETUP_WITH in ceval.c, but + functions the same as SETUP_FINALLY except that exceptions are + normalized. */ if (!compiler_push_fblock(c, FINALLY_TRY, block)) { return 0; }