Commit 707ce62b authored by Stefan Behnel's avatar Stefan Behnel

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.
parent e74368f3
......@@ -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__"
......
......@@ -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);
}
"""]
#------------------------------------------------------------------------------------
__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])
__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])
__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])
__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
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment