From 707ce62bd5443dd692bf0c8e12bcf173daadec5f Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Tue, 2 Sep 2008 10:14:30 +0200 Subject: [PATCH] new exception handling semantics - the original exception status will be saved when entering a try: block - sys.exc_info() will be reset after a successful except: block This mimics the behaviour of Py3 and prevents exceptions, tracebacks and frames (i.e. deep function state) from staying alive any longer than necessary to handle an exception. The new semantics imply that a try: block is no longer free, but it is still very cheap. --- Cython/Compiler/Naming.py | 4 ++ Cython/Compiler/Nodes.py | 79 ++++++++++++++++++++++++++--- tests/run/funcexcept.pyx | 39 +++++++++++++++ tests/run/funcexceptchained.pyx | 89 +++++++++++++++++++++++++++++++++ tests/run/funcexceptcypy.pyx | 51 +++++++++++++++++++ tests/run/funcexceptreturn.pyx | 25 +++++++++ 6 files changed, 281 insertions(+), 6 deletions(-) create mode 100644 tests/run/funcexcept.pyx create mode 100644 tests/run/funcexceptchained.pyx create mode 100644 tests/run/funcexceptcypy.pyx create mode 100644 tests/run/funcexceptreturn.pyx diff --git a/Cython/Compiler/Naming.py b/Cython/Compiler/Naming.py index c0d88ff8..47ec6be3 100644 --- a/Cython/Compiler/Naming.py +++ b/Cython/Compiler/Naming.py @@ -95,6 +95,10 @@ exc_lineno_name = pyrex_prefix + "exc_lineno" exc_vars = (exc_type_name, exc_value_name, exc_tb_name) +exc_save_vars = (pyrex_prefix + 'save_exc_type', + pyrex_prefix + 'save_exc_value', + pyrex_prefix + 'save_exc_tb') + api_name = pyrex_prefix + "capi__" h_guard_prefix = "__PYX_HAVE__" diff --git a/Cython/Compiler/Nodes.py b/Cython/Compiler/Nodes.py index 890c658b..f3b5bd98 100644 --- a/Cython/Compiler/Nodes.py +++ b/Cython/Compiler/Nodes.py @@ -3527,6 +3527,7 @@ class TryExceptStatNode(StatNode): if self.else_clause: self.else_clause.analyse_control_flow(env) env.finish_branching(self.end_pos()) + env.use_utility_code(reset_exception_utility_code) def analyse_declarations(self, env): self.body.analyse_declarations(env) @@ -3549,22 +3550,33 @@ class TryExceptStatNode(StatNode): gil_message = "Try-except statement" def generate_execution_code(self, code): + old_return_label = code.return_label old_error_label = code.new_error_label() our_error_label = code.error_label - end_label = code.new_label() + except_end_label = code.new_label('exception_handled') + except_error_label = code.new_label('except_error') + except_return_label = code.new_label('except_return') + try_end_label = code.new_label('try') + + code.putln("{") + code.putln("PyObject %s;" % + ', '.join(['*%s' % var for var in Naming.exc_save_vars])) + code.putln("__Pyx_ExceptionSave(%s);" % + ', '.join(['&%s' % var for var in Naming.exc_save_vars])) code.putln( "/*try:*/ {") self.body.generate_execution_code(code) code.putln( "}") - code.error_label = old_error_label + code.error_label = except_error_label + code.return_label = except_return_label if self.else_clause: code.putln( "/*else:*/ {") self.else_clause.generate_execution_code(code) code.putln( "}") - code.put_goto(end_label) + code.put_goto(try_end_label) code.put_label(our_error_label) code.put_var_xdecrefs_clear(self.cleanup_list) default_clause_seen = 0 @@ -3574,10 +3586,31 @@ class TryExceptStatNode(StatNode): else: if default_clause_seen: error(except_clause.pos, "Default except clause not last") - except_clause.generate_handling_code(code, end_label) + except_clause.generate_handling_code(code, except_end_label) if not default_clause_seen: code.put_goto(code.error_label) - code.put_label(end_label) + + if code.label_used(except_error_label): + code.put_label(except_error_label) + for var in Naming.exc_save_vars: + code.put_xdecref(var, py_object_type) + code.put_goto(old_error_label) + + if code.label_used(except_return_label): + code.put_label(except_return_label) + code.putln("__Pyx_ExceptionReset(%s);" % + ', '.join(Naming.exc_save_vars)) + code.put_goto(old_return_label) + + if code.label_used(except_end_label): + code.put_label(except_end_label) + code.putln("__Pyx_ExceptionReset(%s);" % + ', '.join(Naming.exc_save_vars)) + code.put_label(try_end_label) + code.putln("}") + + code.return_label = old_return_label + code.error_label = old_error_label def annotate(self, code): self.body.annotate(code) @@ -4656,7 +4689,7 @@ static void __Pyx_AddTraceback(const char *funcname) { ); if (!py_code) goto bad; py_frame = PyFrame_New( - PyThreadState_Get(), /*PyThreadState *tstate,*/ + PyThreadState_GET(), /*PyThreadState *tstate,*/ py_code, /*PyCodeObject *code,*/ py_globals, /*PyObject *globals,*/ 0 /*PyObject *locals*/ @@ -4837,3 +4870,37 @@ bad: """] #------------------------------------------------------------------------------------ + +reset_exception_utility_code = [ +""" +void INLINE __Pyx_ExceptionSave(PyObject **type, PyObject **value, PyObject **tb); /*proto*/ +void __Pyx_ExceptionReset(PyObject *type, PyObject *value, PyObject *tb); /*proto*/ +""",""" +void INLINE __Pyx_ExceptionSave(PyObject **type, PyObject **value, PyObject **tb) { + PyThreadState *tstate = PyThreadState_GET(); + *type = tstate->exc_type; + *value = tstate->exc_value; + *tb = tstate->exc_traceback; + if (*type) { + Py_INCREF(*type); + Py_XINCREF(*value); + Py_XINCREF(*tb); + } +} + +void __Pyx_ExceptionReset(PyObject *type, PyObject *value, PyObject *tb) { + PyObject *tmp_type, *tmp_value, *tmp_tb; + PyThreadState *tstate = PyThreadState_GET(); + tmp_type = tstate->exc_type; + tmp_value = tstate->exc_value; + tmp_tb = tstate->exc_traceback; + tstate->exc_type = type; + tstate->exc_value = value; + tstate->exc_traceback = tb; + Py_XDECREF(tmp_type); + Py_XDECREF(tmp_value); + Py_XDECREF(tmp_tb); +} +"""] + +#------------------------------------------------------------------------------------ diff --git a/tests/run/funcexcept.pyx b/tests/run/funcexcept.pyx new file mode 100644 index 00000000..a2460439 --- /dev/null +++ b/tests/run/funcexcept.pyx @@ -0,0 +1,39 @@ +__doc__ = u""" +>>> import sys +>>> if not IS_PY3: sys.exc_clear() + +>>> def test_py(): +... try: +... raise AttributeError +... except AttributeError: +... print(sys.exc_info()[0] == AttributeError or sys.exc_info()[0]) +... print((IS_PY3 and sys.exc_info()[0] is None) or +... (not IS_PY3 and sys.exc_info()[0] == AttributeError) or +... sys.exc_info()[0]) + +>>> print(sys.exc_info()[0]) # 0 +None +>>> test_py() +True +True + +>>> print(sys.exc_info()[0]) # test_py() +None + +>>> test_c() +True +True +>>> print(sys.exc_info()[0]) # test_c() +None +""" + +import sys + +IS_PY3 = sys.version_info[0] >= 3 + +def test_c(): + try: + raise AttributeError + except AttributeError: + print(sys.exc_info()[0] == AttributeError or sys.exc_info()[0]) + print(sys.exc_info()[0] is None or sys.exc_info()[0]) diff --git a/tests/run/funcexceptchained.pyx b/tests/run/funcexceptchained.pyx new file mode 100644 index 00000000..78c02c38 --- /dev/null +++ b/tests/run/funcexceptchained.pyx @@ -0,0 +1,89 @@ +__doc__ = u""" +>>> import sys +>>> if not IS_PY3: sys.exc_clear() + +>>> def test_py(outer_exc): +... try: +... raise AttributeError +... except AttributeError: +... print(sys.exc_info()[0] is AttributeError or sys.exc_info()[0]) +... try: raise KeyError +... except: print(sys.exc_info()[0] is KeyError or sys.exc_info()[0]) +... print((IS_PY3 and sys.exc_info()[0] is AttributeError) or +... (not IS_PY3 and sys.exc_info()[0] is KeyError) or +... sys.exc_info()[0]) +... print((IS_PY3 and sys.exc_info()[0] is outer_exc) or +... (not IS_PY3 and sys.exc_info()[0] is KeyError) or +... sys.exc_info()[0]) + +>>> print(sys.exc_info()[0]) # 0 +None + +>>> test_py(None) +True +True +True +True +>>> print(sys.exc_info()[0]) # test_py() +None + +>>> test_c(None) +True +True +True +True +>>> print(sys.exc_info()[0]) # test_c() +None + +>>> def test_py2(): +... try: +... raise Exception +... except Exception: +... test_py(Exception) +... print(sys.exc_info()[0] is Exception or sys.exc_info()[0]) +... print((IS_PY3 and sys.exc_info()[0] is None) or +... (not IS_PY3 and sys.exc_info()[0] is Exception) or +... sys.exc_info()[0]) + +>>> test_py2() +True +True +True +True +True +True +>>> print(sys.exc_info()[0]) # test_py2() +None + +>>> test_c2() +True +True +True +True +True +True +>>> print(sys.exc_info()[0]) # test_c2() +None +""" + +import sys + +IS_PY3 = sys.version_info[0] >= 3 + +def test_c(outer_exc): + try: + raise AttributeError + except AttributeError: + print(sys.exc_info()[0] is AttributeError or sys.exc_info()[0]) + try: raise KeyError + except: print(sys.exc_info()[0] is KeyError or sys.exc_info()[0]) + print(sys.exc_info()[0] is AttributeError or sys.exc_info()[0]) + print(sys.exc_info()[0] is outer_exc or sys.exc_info()[0]) + +def test_c2(): + try: + raise Exception + except Exception: + test_c(Exception) + print(sys.exc_info()[0] is Exception or sys.exc_info()[0]) + print(sys.exc_info()[0] is None or sys.exc_info()[0]) diff --git a/tests/run/funcexceptcypy.pyx b/tests/run/funcexceptcypy.pyx new file mode 100644 index 00000000..a0d64856 --- /dev/null +++ b/tests/run/funcexceptcypy.pyx @@ -0,0 +1,51 @@ +__doc__ = u""" +>>> import sys +>>> if not IS_PY3: sys.exc_clear() + +>>> def test_py(): +... try: +... raise AttributeError +... except AttributeError: +... test_c(error=AttributeError) +... print(sys.exc_info()[0] is AttributeError or sys.exc_info()[0]) +... print((IS_PY3 and sys.exc_info()[0] is TestException) or +... (not IS_PY3 and sys.exc_info()[0] is AttributeError) or +... sys.exc_info()[0]) + +>>> print(sys.exc_info()[0]) # 0 +None +>>> test_py() +True +True +True +True + +>>> print(sys.exc_info()[0]) # test_py() +None + +>>> test_c(test_py) +True +True +True +True +True +True + +>>> print(sys.exc_info()[0]) # test_c() +None +""" + +import sys +IS_PY3 = sys.version_info[0] >= 3 + +class TestException(Exception): + pass + +def test_c(func=None, error=None): + try: + raise TestException + except TestException: + if func: + func() + print(sys.exc_info()[0] is TestException or sys.exc_info()[0]) + print(sys.exc_info()[0] is error or sys.exc_info()[0]) diff --git a/tests/run/funcexceptreturn.pyx b/tests/run/funcexceptreturn.pyx new file mode 100644 index 00000000..358ee3d7 --- /dev/null +++ b/tests/run/funcexceptreturn.pyx @@ -0,0 +1,25 @@ +__doc__ = u""" +>>> import sys +>>> if not IS_PY3: sys.exc_clear() + +>>> print(sys.exc_info()[0]) # 0 +None +>>> exc = test_c() +>>> type(exc) is TestException +True +>>> print(sys.exc_info()[0]) # test_c() +None +""" + +import sys + +IS_PY3 = sys.version_info[0] >= 3 + +class TestException(Exception): + pass + +def test_c(): + try: + raise TestException + except TestException, e: + return e -- 2.26.2