From fc2de4343a28d6f7928e9c63b3796105262d3f5d Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Tue, 31 Dec 2013 11:02:27 +0100 Subject: [PATCH] back up and restore complete exception state around 'finally' clauses; move current exception into sys.exc_info in Py3 to match Py3 semantics --- Cython/Compiler/Nodes.py | 37 ++++++++++++++++++----- Cython/Utility/Exceptions.c | 8 +++-- tests/run/tryfinally.pyx | 71 ++++++++++++++++++++++++++++++++++++--------- 3 files changed, 92 insertions(+), 24 deletions(-) diff --git a/Cython/Compiler/Nodes.py b/Cython/Compiler/Nodes.py index 174b963..31d91c2 100644 --- a/Cython/Compiler/Nodes.py +++ b/Cython/Compiler/Nodes.py @@ -6464,9 +6464,8 @@ class TryFinallyStatNode(StatNode): exc_lineno_cnames = exc_filename_cname = None exc_vars = tuple([ code.funcstate.allocate_temp(py_object_type, manage_ref=False) - for _ in range(3)]) + for _ in range(6)]) code.put_label(new_error_label) - code.putln("%s = 0; %s = 0; %s = 0;" % exc_vars) self.put_error_catcher( code, temps_to_clean_up, exc_vars, exc_lineno_cnames, exc_filename_cname) finally_old_labels = code.all_new_labels() @@ -6539,14 +6538,25 @@ class TryFinallyStatNode(StatNode): def put_error_catcher(self, code, temps_to_clean_up, exc_vars, exc_lineno_cnames, exc_filename_cname): code.globalstate.use_utility_code(restore_exception_utility_code) + code.globalstate.use_utility_code(get_exception_utility_code) + code.globalstate.use_utility_code(swap_exception_utility_code) + code.putln(' '.join(["%s = 0;"]*len(exc_vars)) % exc_vars) if self.is_try_finally_in_nogil: code.put_ensure_gil(declare_gilstate=False) for temp_name, type in temps_to_clean_up: code.put_xdecref_clear(temp_name, type) - code.putln("__Pyx_ErrFetch(&%s, &%s, &%s);" % exc_vars) + # not using preprocessor here to avoid warnings about + # unused utility functions and/or temps + code.putln("if (PY_MAJOR_VERSION >= 3)" + " __Pyx_ExceptionSwap(&%s, &%s, &%s);" % exc_vars[3:]) + code.putln("if ((PY_MAJOR_VERSION < 3) ||" + # if __Pyx_GetException() fails in Py3, + # store the newly raised exception instead + " unlikely(__Pyx_GetException(&%s, &%s, &%s) < 0)) " + "__Pyx_ErrFetch(&%s, &%s, &%s);" % (exc_vars[:3] * 2)) for var in exc_vars: code.put_xgotref(var) if exc_lineno_cnames: @@ -6560,18 +6570,24 @@ class TryFinallyStatNode(StatNode): def put_error_uncatcher(self, code, exc_vars, exc_lineno_cnames, exc_filename_cname): code.globalstate.use_utility_code(restore_exception_utility_code) + code.globalstate.use_utility_code(reset_exception_utility_code) if self.is_try_finally_in_nogil: code.put_ensure_gil(declare_gilstate=False) - for var in exc_vars: + code.putln("#if PY_MAJOR_VERSION >= 3") + for var in exc_vars[3:]: + code.put_xgiveref(var) + code.putln("__Pyx_ExceptionReset(%s, %s, %s);" % exc_vars[3:]) + code.putln("#endif") + for var in exc_vars[:3]: code.put_xgiveref(var) - code.putln("__Pyx_ErrRestore(%s, %s, %s);" % exc_vars) + code.putln("__Pyx_ErrRestore(%s, %s, %s);" % exc_vars[:3]) if self.is_try_finally_in_nogil: code.put_release_ensured_gil() - code.putln("%s = 0; %s = 0; %s = 0;" % exc_vars) + code.putln(' '.join(["%s = 0;"]*len(exc_vars)) % exc_vars) if exc_lineno_cnames: code.putln("%s = %s; %s = %s; %s = %s;" % ( Naming.lineno_cname, exc_lineno_cnames[0], @@ -6579,12 +6595,19 @@ class TryFinallyStatNode(StatNode): Naming.filename_cname, exc_filename_cname)) def put_error_cleaner(self, code, exc_vars): + code.globalstate.use_utility_code(reset_exception_utility_code) if self.is_try_finally_in_nogil: code.put_ensure_gil(declare_gilstate=False) - for var in exc_vars: + code.putln("#if PY_MAJOR_VERSION >= 3") + for var in exc_vars[3:]: + code.put_xgiveref(var) + code.putln("__Pyx_ExceptionReset(%s, %s, %s);" % exc_vars[3:]) + code.putln("#endif") + for var in exc_vars[:3]: code.put_xdecref_clear(var, py_object_type) if self.is_try_finally_in_nogil: code.put_release_ensured_gil() + code.putln(' '.join(["%s = 0;"]*3) % exc_vars[3:]) def annotate(self, code): self.body.annotate(code) diff --git a/Cython/Utility/Exceptions.c b/Cython/Utility/Exceptions.c index bdc5341..9e31a15 100644 --- a/Cython/Utility/Exceptions.c +++ b/Cython/Utility/Exceptions.c @@ -272,12 +272,14 @@ static int __Pyx_GetException(PyObject **type, PyObject **value, PyObject **tb) #endif goto bad; #if PY_MAJOR_VERSION >= 3 - if (unlikely(PyException_SetTraceback(local_value, local_tb) < 0)) - goto bad; + if (local_tb) { + if (unlikely(PyException_SetTraceback(local_value, local_tb) < 0)) + goto bad; + } #endif + Py_XINCREF(local_tb); Py_INCREF(local_type); Py_INCREF(local_value); - Py_INCREF(local_tb); *type = local_type; *value = local_value; *tb = local_tb; diff --git a/tests/run/tryfinally.pyx b/tests/run/tryfinally.pyx index 878fbbd..a0e3fdb 100644 --- a/tests/run/tryfinally.pyx +++ b/tests/run/tryfinally.pyx @@ -42,11 +42,35 @@ def finally_pass(): pass -cdef void swallow(): +def except_finally_reraise(): + """ + >>> def py_check(): + ... try: raise ValueError + ... except ValueError: + ... for i in range(2): + ... try: raise TypeError + ... finally: + ... break + ... assert sys.exc_info()[0] == ValueError, str(sys.exc_info()) + ... raise + ... + >>> py_check() + Traceback (most recent call last): + ValueError + >>> except_finally_reraise() + Traceback (most recent call last): + ValueError + """ try: - raise TypeError() - finally: - return + raise ValueError + except ValueError: + for i in range(2): + try: + raise TypeError + finally: + break + assert sys.exc_info()[0] == ValueError, str(sys.exc_info()) + raise def finally_exception_check_return(): @@ -70,23 +94,33 @@ def finally_exception_check_return(): raise ValueError() finally: if IS_PY3: - pass # FIXME: - #assert sys.exc_info()[0] == ValueError, str(sys.exc_info()) + assert sys.exc_info()[0] == ValueError, str(sys.exc_info()) else: assert sys.exc_info() == (None, None, None), str(sys.exc_info()) return 1 +cdef void swallow(): + try: + raise TypeError() + except: + return + + def finally_exception_check_swallow(): """ >>> if not IS_PY3: ... sys.exc_clear() >>> def swallow(): ... try: raise TypeError() - ... finally: return + ... except: return >>> def py_check(): ... try: raise ValueError() ... finally: + ... if IS_PY3: + ... assert sys.exc_info()[0] == ValueError, str(sys.exc_info()) + ... else: + ... assert sys.exc_info() == (None, None, None), str(sys.exc_info()) ... swallow() ... if IS_PY3: ... assert sys.exc_info()[0] == ValueError, str(sys.exc_info()) @@ -104,10 +138,13 @@ def finally_exception_check_swallow(): try: raise ValueError() finally: + if IS_PY3: + assert sys.exc_info()[0] == ValueError, str(sys.exc_info()) + else: + assert sys.exc_info() == (None, None, None), str(sys.exc_info()) swallow() if IS_PY3: - pass # FIXME: - #assert sys.exc_info()[0] == ValueError, str(sys.exc_info()) + assert sys.exc_info()[0] == ValueError, str(sys.exc_info()) else: assert sys.exc_info() == (None, None, None), str(sys.exc_info()) @@ -139,8 +176,7 @@ def finally_exception_break_check(): raise ValueError() finally: if IS_PY3: - pass # FIXME: - #assert sys.exc_info()[0] == ValueError, str(sys.exc_info()) + assert sys.exc_info()[0] == ValueError, str(sys.exc_info()) else: assert sys.exc_info() == (None, None, None), str(sys.exc_info()) break @@ -154,12 +190,16 @@ def finally_exception_break_check_with_swallowed_raise(): ... sys.exc_clear() >>> def swallow(): ... try: raise TypeError() - ... finally: return + ... except: return >>> def py_check(): ... i = None ... for i in range(2): ... try: raise ValueError() ... finally: + ... if IS_PY3: + ... assert sys.exc_info()[0] == ValueError, str(sys.exc_info()) + ... else: + ... assert sys.exc_info() == (None, None, None), str(sys.exc_info()) ... swallow() ... if IS_PY3: ... assert sys.exc_info()[0] == ValueError, str(sys.exc_info()) @@ -178,10 +218,13 @@ def finally_exception_break_check_with_swallowed_raise(): try: raise ValueError() finally: + if IS_PY3: + assert sys.exc_info()[0] == ValueError, str(sys.exc_info()) + else: + assert sys.exc_info() == (None, None, None), str(sys.exc_info()) swallow() if IS_PY3: - pass # FIXME: - #assert sys.exc_info()[0] == ValueError, str(sys.exc_info()) + assert sys.exc_info()[0] == ValueError, str(sys.exc_info()) else: assert sys.exc_info() == (None, None, None), str(sys.exc_info()) break -- 2.7.4