Re: [Cython] [Pyrex] Pyrex exception reraise behavior
Stefan Behnel <[email protected]> Sat, 16 Aug 2008 21:54:27 +0200
| Newsgroups | gmane.comp.python.cython.devel,gmane.comp.python.pyrex |
|---|---|
| Message-ID | <[email protected]> |
Hi, Greg Ewing wrote: > There are some differences, yes. Python makes the > sys.exc_info() vars dynamically scoped by saving and > restoring them in the Python stack frame every time > a call occurs. > > That would be too expensive for Pyrex, so it uses > a different approach -- it stores information about > the caught exception in the C stack frame at the > point where it's caught (i.e. in the except clause). > The raise statement is required to be lexically > enclosed in the except clause so that it can access > this information. > > There may be some other differences as well. What > you appear to be seeing is a leaking of the > sys.exc_info() outside the scope where it would > normally be available in Python. Especially in Py3, where sys.exc_info() is reset when leaving the except block that caught the exception. I have a patch ready that fixes this with relatively little performance impact, although it requires remembering the currently raised exception whenever we enter a try statement (that's just three pointer copy operations). I think the OP's re-raising problem fits into this, too, although it would require some more thoughts to fix. There are some more issues with exception handling in Py3. Due to Python keeps exceptions in frames, I currently get crashes with Cython's exception handling (AFAICT unchanged from Pyrex) in Py3.0 beta2 (beta 1 works fine). I've been trying to dig into this problem, but it's pretty far from obvious to me how this can be fixed, or even what exactly goes wrong here. I attached the (somewhat hackish) patch I'm currently working with. It also contains a couple of test cases that show the behaviour I'm targeting, which is superficially compatible with Python 3. One of them crashes reliably for me. Stefan
exception-handling.patch
(text/x-patch, 8.8 KB)
diff -r 7edb772f74b9 Cython/Compiler/Nodes.py
--- a/Cython/Compiler/Nodes.py Sat Aug 09 13:26:46 2008 +0200
+++ b/Cython/Compiler/Nodes.py Sat Aug 09 13:29:32 2008 +0200
@@ -3371,6 +3371,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)
@@ -3395,20 +3396,28 @@ class TryExceptStatNode(StatNode):
def generate_execution_code(self, code):
old_error_label = code.new_error_label()
our_error_label = code.error_label
- end_label = code.new_label()
+ our_return_label = code.return_label
+ except_end_label = code.new_label('except_end')
+ except_return_label = code.new_label('except_return')
+ try_end_label = code.new_label('try')
+
+ code.putln("{")
+ code.putln("PyObject *tmp_type, *tmp_value, *tmp_tb;")
+ code.putln("__Pyx_ExceptionSave(&tmp_type, &tmp_value, &tmp_tb);")
code.putln(
"/*try:*/ {")
self.body.generate_execution_code(code)
code.putln(
"}")
code.error_label = old_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
@@ -3418,10 +3427,22 @@ 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_return_label):
+ code.put_label(except_return_label)
+ code.putln("__Pyx_ExceptionReset(tmp_type, tmp_value, tmp_tb);")
+ code.put_goto(our_return_label)
+
+ if code.label_used(except_end_label):
+ code.put_label(except_end_label)
+ code.putln("__Pyx_ExceptionReset(tmp_type, tmp_value, tmp_tb);")
+ code.put_label(try_end_label)
+ code.putln("}")
+
+ code.return_label = our_return_label
def annotate(self, code):
self.body.annotate(code)
@@ -4638,3 +4659,40 @@ bad:
"""]
#------------------------------------------------------------------------------------
+
+reset_exception_utility_code = [
+"""
+void __Pyx_ExceptionReset(PyObject *type, PyObject *value, PyObject *tb); /*proto*/
+void INLINE __Pyx_ExceptionSave(PyObject **type, PyObject **value, PyObject **tb); /*proto*/
+""","""
+void __Pyx_ExceptionReset(PyObject *type, PyObject *value, PyObject *tb) {
+ PyObject *tmp_type, *tmp_value, *tmp_tb;
+ PyThreadState *tstate = PyThreadState_GET();
+
+ Py_XINCREF(type);
+ Py_XINCREF(value);
+ Py_XINCREF(tb);
+
+ PyErr_Clear();
+
+ 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);
+}
+
+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;
+}
+
+"""]
+
+#------------------------------------------------------------------------------------
diff -r 7edb772f74b9 tests/run/funcexcept.pyx
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/run/funcexcept.pyx Sat Aug 09 13:29:32 2008 +0200
@@ -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 -r 7edb772f74b9 tests/run/funcexceptchained.pyx
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/run/funcexceptchained.pyx Sat Aug 09 13:29:32 2008 +0200
@@ -0,0 +1,85 @@
+__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] == AttributeError or sys.exc_info()[0])
+... try: raise KeyError
+... except: pass
+... print((IS_PY3 and sys.exc_info()[0] is AttributeError) or
+... (not IS_PY3 and sys.exc_info()[0] == 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] == KeyError) or
+... sys.exc_info()[0])
+
+>>> print(sys.exc_info()[0]) # 0
+None
+
+>>> test_py(None)
+True
+True
+True
+>>> print(sys.exc_info()[0]) # test_py()
+None
+
+>>> test_c(None)
+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] == 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] == Exception) or
+... sys.exc_info()[0])
+
+>>> test_py2()
+True
+True
+True
+True
+True
+>>> print(sys.exc_info()[0]) # test_py2()
+None
+
+>>> test_c2()
+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] == AttributeError or sys.exc_info()[0])
+ try: raise KeyError
+ except: pass
+ print(sys.exc_info()[0] == 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] == Exception or sys.exc_info()[0])
+ print(sys.exc_info()[0] is None or sys.exc_info()[0])
diff -r 7edb772f74b9 tests/run/funcexceptcypy.pyx
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/run/funcexceptcypy.pyx Sat Aug 09 13:29:32 2008 +0200
@@ -0,0 +1,52 @@
+__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] == 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
+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] == TestException or sys.exc_info()[0])
+ print(sys.exc_info()[0] == error or sys.exc_info()[0])
diff -r 7edb772f74b9 tests/run/funcexceptreturn.pyx
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/run/funcexceptreturn.pyx Sat Aug 09 13:29:32 2008 +0200
@@ -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