Commit e79ace99 authored by Robert Bradshaw's avatar Robert Bradshaw Committed by GitHub

Merge pull request #2615 from robertwb/cpp-py-exception

Allow catching both C++ and Python exceptions.
parents c4c9683e 05a2cffa
...@@ -187,22 +187,35 @@ def infer_sequence_item_type(env, seq_node, index_node=None, seq_type=None): ...@@ -187,22 +187,35 @@ def infer_sequence_item_type(env, seq_node, index_node=None, seq_type=None):
return item_types.pop() return item_types.pop()
return None return None
# Returns a block of code to translate the exception,
# plus a boolean indicating whether to check for Python exceptions.
def get_exception_handler(exception_value): def get_exception_handler(exception_value):
if exception_value is None: if exception_value is None:
return "__Pyx_CppExn2PyErr();" return "__Pyx_CppExn2PyErr();", False
elif (exception_value.type == PyrexTypes.c_char_type
and exception_value.value == '*'):
return "__Pyx_CppExn2PyErr();", True
elif exception_value.type.is_pyobject: elif exception_value.type.is_pyobject:
return 'try { throw; } catch(const std::exception& exn) { PyErr_SetString(%s, exn.what()); } catch(...) { PyErr_SetNone(%s); }' % ( return 'try { throw; } catch(const std::exception& exn) { PyErr_SetString(%s, exn.what()); } catch(...) { PyErr_SetNone(%s); }' % (
exception_value.entry.cname, exception_value.entry.cname,
exception_value.entry.cname) exception_value.entry.cname), False
else: else:
return '%s(); if (!PyErr_Occurred()) PyErr_SetString(PyExc_RuntimeError , "Error converting c++ exception.");' % exception_value.entry.cname return '%s(); if (!PyErr_Occurred()) PyErr_SetString(PyExc_RuntimeError , "Error converting c++ exception.");' % exception_value.entry.cname, False
def maybe_check_py_error(code, check_py_exception, pos, nogil):
if check_py_exception:
if nogil:
code.putln(code.error_goto_if("__Pyx_ErrOccurredWithGIL()", pos))
else:
code.putln(code.error_goto_if("PyErr_Occurred()", pos))
def translate_cpp_exception(code, pos, inside, py_result, exception_value, nogil): def translate_cpp_exception(code, pos, inside, py_result, exception_value, nogil):
raise_py_exception = get_exception_handler(exception_value) raise_py_exception, check_py_exception = get_exception_handler(exception_value)
code.putln("try {") code.putln("try {")
code.putln("%s" % inside) code.putln("%s" % inside)
if py_result: if py_result:
code.putln(code.error_goto_if_null(py_result, pos)) code.putln(code.error_goto_if_null(py_result, pos))
maybe_check_py_error(code, check_py_exception, pos, nogil)
code.putln("} catch(...) {") code.putln("} catch(...) {")
if nogil: if nogil:
code.put_ensure_gil(declare_gilstate=True) code.put_ensure_gil(declare_gilstate=True)
...@@ -216,12 +229,14 @@ def translate_cpp_exception(code, pos, inside, py_result, exception_value, nogil ...@@ -216,12 +229,14 @@ def translate_cpp_exception(code, pos, inside, py_result, exception_value, nogil
# both have an exception declaration. # both have an exception declaration.
def translate_double_cpp_exception(code, pos, lhs_type, lhs_code, rhs_code, def translate_double_cpp_exception(code, pos, lhs_type, lhs_code, rhs_code,
lhs_exc_val, assign_exc_val, nogil): lhs_exc_val, assign_exc_val, nogil):
handle_lhs_exc = get_exception_handler(lhs_exc_val) handle_lhs_exc, lhc_check_py_exc = get_exception_handler(lhs_exc_val)
handle_assignment_exc = get_exception_handler(assign_exc_val) handle_assignment_exc, assignment_check_py_exc = get_exception_handler(assign_exc_val)
code.putln("try {") code.putln("try {")
code.putln(lhs_type.declaration_code("__pyx_local_lvalue = %s;" % lhs_code)) code.putln(lhs_type.declaration_code("__pyx_local_lvalue = %s;" % lhs_code))
maybe_check_py_error(code, lhc_check_py_exc, pos, nogil)
code.putln("try {") code.putln("try {")
code.putln("__pyx_local_lvalue = %s;" % rhs_code) code.putln("__pyx_local_lvalue = %s;" % rhs_code)
maybe_check_py_error(code, assignment_check_py_exc, pos, nogil)
# Catch any exception from the overloaded assignment. # Catch any exception from the overloaded assignment.
code.putln("} catch(...) {") code.putln("} catch(...) {")
if nogil: if nogil:
......
...@@ -741,9 +741,11 @@ class CFuncDeclaratorNode(CDeclaratorNode): ...@@ -741,9 +741,11 @@ class CFuncDeclaratorNode(CDeclaratorNode):
and not exc_val_type.is_pyobject and not exc_val_type.is_pyobject
and not (exc_val_type.is_cfunction and not (exc_val_type.is_cfunction
and not exc_val_type.return_type.is_pyobject and not exc_val_type.return_type.is_pyobject
and not exc_val_type.args)): and not exc_val_type.args)
and not (exc_val_type == PyrexTypes.c_char_type
and self.exception_value.value == '*')):
error(self.exception_value.pos, error(self.exception_value.pos,
"Exception value must be a Python exception or cdef function with no arguments.") "Exception value must be a Python exception or cdef function with no arguments or *.")
exc_val = self.exception_value exc_val = self.exception_value
else: else:
self.exception_value = self.exception_value.coerce_to( self.exception_value = self.exception_value.coerce_to(
......
...@@ -2937,6 +2937,9 @@ def p_exception_value_clause(s): ...@@ -2937,6 +2937,9 @@ def p_exception_value_clause(s):
name = s.systring name = s.systring
s.next() s.next()
exc_val = p_name(s, name) exc_val = p_name(s, name)
elif s.sy == '*':
exc_val = ExprNodes.CharNode(s.position(), value=u'*')
s.next()
else: else:
if s.sy == '?': if s.sy == '?':
exc_check = 1 exc_check = 1
......
...@@ -440,6 +440,13 @@ called, which allows one to do custom C++ to Python error "translations." If ...@@ -440,6 +440,13 @@ called, which allows one to do custom C++ to Python error "translations." If
raise_py_error does not actually raise an exception a RuntimeError will be raise_py_error does not actually raise an exception a RuntimeError will be
raised. raised.
There is also the special form::
cdef int raise_py_or_cpp() except +*
for those functions that may raise either a Python or a C++ exception.
Static member method Static member method
-------------------- --------------------
......
...@@ -22,6 +22,7 @@ cdef extern from "cpp_exceptions_helper.h": ...@@ -22,6 +22,7 @@ cdef extern from "cpp_exceptions_helper.h":
cdef void raise_underflow() except + cdef void raise_underflow() except +
cdef raise_or_throw(bint py) except + cdef raise_or_throw(bint py) except +
cdef int raise_or_throw_int(bint py) except +*
cdef cppclass Foo: cdef cppclass Foo:
int bar_raw "bar"(bint fire) except + int bar_raw "bar"(bint fire) except +
...@@ -113,6 +114,19 @@ def test_func_that_can_raise_or_throw(bint py): ...@@ -113,6 +114,19 @@ def test_func_that_can_raise_or_throw(bint py):
""" """
raise_or_throw(py) raise_or_throw(py)
def test_func_that_can_raise_or_throw_c_return(bint py):
"""
>>> test_func_that_can_raise_or_throw_c_return(0)
Traceback (most recent call last):
...
RuntimeError: oopsie
>>> test_func_that_can_raise_or_throw_c_return(1)
Traceback (most recent call last):
...
ValueError: oopsie
"""
raise_or_throw_int(py)
def test_int_raw(bint fire): def test_int_raw(bint fire):
""" """
>>> test_int_raw(False) >>> test_int_raw(False)
......
...@@ -70,3 +70,11 @@ PyObject *raise_or_throw(int py) { ...@@ -70,3 +70,11 @@ PyObject *raise_or_throw(int py) {
PyErr_SetString(PyExc_ValueError, "oopsie"); PyErr_SetString(PyExc_ValueError, "oopsie");
return NULL; return NULL;
} }
int raise_or_throw_int(int py) {
if (!py) {
throw std::runtime_error("oopsie");
}
PyErr_SetString(PyExc_ValueError, "oopsie");
return -1;
}
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