new exception handling semantics
authorStefan Behnel <scoder@users.berlios.de>
Tue, 2 Sep 2008 08:14:30 +0000 (10:14 +0200)
committerStefan Behnel <scoder@users.berlios.de>
Tue, 2 Sep 2008 08:14:30 +0000 (10:14 +0200)
- 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
Cython/Compiler/Nodes.py
tests/run/funcexcept.pyx [new file with mode: 0644]
tests/run/funcexceptchained.pyx [new file with mode: 0644]
tests/run/funcexceptcypy.pyx [new file with mode: 0644]
tests/run/funcexceptreturn.pyx [new file with mode: 0644]

index c0d88ff8e1db6e30a8099d4d325768183e650c76..47ec6be33fe2d75969f25e0a3a4eb9dac47e6a7d 100644 (file)
@@ -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__"
index 890c658b32a15cf65e43a94f5dfa875ce1f6c2a9..f3b5bd98968b3d439ac0739224b129cddee661cd 100644 (file)
@@ -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 (file)
index 0000000..a246043
--- /dev/null
@@ -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 (file)
index 0000000..78c02c3
--- /dev/null
@@ -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 (file)
index 0000000..a0d6485
--- /dev/null
@@ -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 (file)
index 0000000..358ee3d
--- /dev/null
@@ -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