Commit fd88308e authored by robertwb's avatar robertwb

Merge pull request #39 from larsmans/exceptions

Fine-grained handling of C++ exceptions
parents 91a18881 a1cfa834
...@@ -8216,19 +8216,34 @@ cpp_exception_utility_code = UtilityCode( ...@@ -8216,19 +8216,34 @@ cpp_exception_utility_code = UtilityCode(
proto = """ proto = """
#ifndef __Pyx_CppExn2PyErr #ifndef __Pyx_CppExn2PyErr
static void __Pyx_CppExn2PyErr() { static void __Pyx_CppExn2PyErr() {
// Catch a handful of different errors here and turn them into the
// equivalent Python errors.
try { try {
if (PyErr_Occurred()) if (PyErr_Occurred())
; // let the latest Python exn pass through and ignore the current one ; // let the latest Python exn pass through and ignore the current one
else else
throw; throw;
} catch (const std::bad_alloc& exn) {
PyErr_SetString(PyExc_MemoryError, exn.what());
} catch (const std::bad_cast& exn) {
PyErr_SetString(PyExc_TypeError, exn.what());
} catch (const std::domain_error& exn) {
PyErr_SetString(PyExc_ValueError, exn.what());
} catch (const std::invalid_argument& exn) { } catch (const std::invalid_argument& exn) {
// Catch a handful of different errors here and turn them into the
// equivalent Python errors.
// Change invalid_argument to ValueError
PyErr_SetString(PyExc_ValueError, exn.what()); PyErr_SetString(PyExc_ValueError, exn.what());
} catch (const std::ios_base::failure& exn) {
// Unfortunately, in standard C++ we have no way of distinguishing EOF
// from other errors here; be careful with the exception mask
PyErr_SetString(PyExc_IOError, exn.what());
} catch (const std::out_of_range& exn) { } catch (const std::out_of_range& exn) {
// Change out_of_range to IndexError // Change out_of_range to IndexError
PyErr_SetString(PyExc_IndexError, exn.what()); PyErr_SetString(PyExc_IndexError, exn.what());
} catch (const std::overflow_error& exn) {
PyErr_SetString(PyExc_OverflowError, exn.what());
} catch (const std::range_error& exn) {
PyErr_SetString(PyExc_ArithmeticError, exn.what());
} catch (const std::underflow_error& exn) {
PyErr_SetString(PyExc_ArithmeticError, exn.what());
} catch (const std::exception& exn) { } catch (const std::exception& exn) {
PyErr_SetString(PyExc_RuntimeError, exn.what()); PyErr_SetString(PyExc_RuntimeError, exn.what());
} }
......
...@@ -577,7 +577,10 @@ class CFuncDeclaratorNode(CDeclaratorNode): ...@@ -577,7 +577,10 @@ class CFuncDeclaratorNode(CDeclaratorNode):
exc_val = None exc_val = None
exc_check = 0 exc_check = 0
if self.exception_check == '+': if self.exception_check == '+':
env.add_include_file('ios') # for std::ios_base::failure
env.add_include_file('new') # for std::bad_alloc
env.add_include_file('stdexcept') env.add_include_file('stdexcept')
env.add_include_file('typeinfo') # for std::bad_cast
if return_type.is_pyobject \ if return_type.is_pyobject \
and (self.exception_value or self.exception_check) \ and (self.exception_value or self.exception_check) \
and self.exception_check != '+': and self.exception_check != '+':
......
...@@ -359,9 +359,37 @@ exception and converting it into a Python exception. For example, :: ...@@ -359,9 +359,37 @@ exception and converting it into a Python exception. For example, ::
cdef extern from "some_file.h": cdef extern from "some_file.h":
cdef int foo() except + cdef int foo() except +
This will translate try and the C++ error into an appropriate Python exception This will translate try and the C++ error into an appropriate Python exception.
(currently an IndexError on std::out_of_range and a RuntimeError otherwise The translation is performed according to the following table
(preserving the what() message). :: (the ``std::`` prefix is omitted from the C++ identifiers):
+-----------------------+---------------------+
| C++ | Python |
+=======================+=====================+
| ``bad_alloc`` | ``MemoryError`` |
+-----------------------+---------------------+
| ``bad_cast`` | ``TypeError`` |
+-----------------------+---------------------+
| ``domain_error`` | ``ValueError`` |
+-----------------------+---------------------+
| ``invalid_argument`` | ``ValueError`` |
+-----------------------+---------------------+
| ``ios_base::failure`` | ``IOError`` |
+-----------------------+---------------------+
| ``out_of_range`` | ``IndexError`` |
+-----------------------+---------------------+
| ``overflow_error`` | ``OverflowError`` |
+-----------------------+---------------------+
| ``range_error`` | ``ArithmeticError`` |
+-----------------------+---------------------+
| ``underflow_error`` | ``ArithmeticError`` |
+-----------------------+---------------------+
| (all others) | ``RuntimeError`` |
+-----------------------+---------------------+
The ``what()`` message, if any, is preserved. Note that a C++
``ios_base_failure`` can denote EOF, but does not carry enough information
for Cython to discern that, so watch out with exception masks on IO streams. ::
cdef int bar() except +MemoryError cdef int bar() except +MemoryError
......
...@@ -12,12 +12,93 @@ cdef extern from "cpp_exceptions_helper.h": ...@@ -12,12 +12,93 @@ cdef extern from "cpp_exceptions_helper.h":
cdef int raise_index_value "raise_index"(bint fire) except +ValueError cdef int raise_index_value "raise_index"(bint fire) except +ValueError
cdef int raise_index_custom "raise_index"(bint fire) except +raise_py_error cdef int raise_index_custom "raise_index"(bint fire) except +raise_py_error
cdef void raise_domain_error() except +
cdef void raise_ios_failure() except +
cdef void raise_memory() except +
cdef void raise_overflow() except +
cdef void raise_range_error() except +
cdef void raise_typeerror() except +
cdef void raise_underflow() except +
cdef cppclass Foo: cdef cppclass Foo:
int bar_raw "bar"(bint fire) except + int bar_raw "bar"(bint fire) except +
int bar_value "bar"(bint fire) except +ValueError int bar_value "bar"(bint fire) except +ValueError
int bar_custom "bar"(bint fire) except +raise_py_error int bar_custom "bar"(bint fire) except +raise_py_error
def test_domain_error():
"""
>>> test_domain_error()
Traceback (most recent call last):
...
ValueError: domain_error
"""
raise_domain_error()
def test_ios_failure():
"""
>>> test_ios_failure()
Traceback (most recent call last):
...
IOError: iostream failure
"""
raise_ios_failure()
def test_memory():
"""
>>> test_memory()
Traceback (most recent call last):
...
MemoryError
"""
# Re-raise the exception without a description string because we can't
# rely on the implementation-defined value of what() in the doctest.
try:
raise_memory()
except MemoryError:
raise MemoryError
def test_overflow():
"""
>>> test_overflow()
Traceback (most recent call last):
...
OverflowError: overflow_error
"""
raise_overflow()
def test_range_error():
"""
>>> test_range_error()
Traceback (most recent call last):
...
ArithmeticError: range_error
"""
raise_range_error()
def test_typeerror():
"""
>>> test_typeerror()
Traceback (most recent call last):
...
TypeError
"""
# Re-raise the exception without a description string because we can't
# rely on the implementation-defined value of what() in the doctest.
try:
raise_typeerror()
except TypeError:
raise TypeError
def test_underflow():
"""
>>> test_underflow()
Traceback (most recent call last):
...
ArithmeticError: underflow_error
"""
raise_underflow()
def test_int_raw(bint fire): def test_int_raw(bint fire):
""" """
>>> test_int_raw(False) >>> test_int_raw(False)
......
#include <ios>
#include <new>
#include <stdexcept> #include <stdexcept>
int raise_int(int fire) { int raise_int(int fire) {
...@@ -23,3 +25,39 @@ class Foo { ...@@ -23,3 +25,39 @@ class Foo {
return 0; return 0;
} }
}; };
void raise_domain_error() {
throw std::domain_error("domain_error");
}
void raise_ios_failure() {
throw std::ios_base::failure("iostream failure");
}
void raise_memory() {
// std::bad_alloc can only be default constructed,
// so we have no control over the error message
throw std::bad_alloc();
}
void raise_overflow() {
throw std::overflow_error("overflow_error");
}
void raise_range_error() {
throw std::range_error("range_error");
}
struct Base { virtual ~Base() {} };
struct Derived : Base { void use() const { abort(); } };
void raise_typeerror() {
Base foo;
Base &bar = foo; // prevents "dynamic_cast can never succeed" warning
Derived &baz = dynamic_cast<Derived &>(bar);
baz.use(); // not reached; prevents "unused variable" warning
}
void raise_underflow() {
throw std::underflow_error("underflow_error");
}
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