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
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
_______________________________________________
Cython-dev mailing list
[email protected]
http://codespeak.net/mailman/listinfo/cython-dev