diff --git a/include/pybind11/detail/internals.h b/include/pybind11/detail/internals.h index 05e36ad18b..fbf51ad6f3 100644 --- a/include/pybind11/detail/internals.h +++ b/include/pybind11/detail/internals.h @@ -314,16 +314,45 @@ inline internals **&get_internals_pp() { return internals_pp; } -// forward decl -inline void translate_exception(std::exception_ptr); +const std::forward_list &get_exception_translators(); +const std::forward_list &get_local_exception_translators(); + +// Apply all the extensions translators from a list +// Return true if one of the translators completed without raising an exception +// itself. Return of false indicates that if there are other translators +// available, they should be tried. +inline bool apply_exception_translators(const std::forward_list &translators, + std::exception_ptr last_exception) { + for (const auto &translator : translators) { + try { + translator(last_exception); + return true; + } catch (...) { + last_exception = std::current_exception(); + } + } + return false; +} + +inline bool +apply_exception_translators(const std::forward_list &translators) { + return apply_exception_translators(translators, std::current_exception()); +} template >::value, int> = 0> bool handle_nested_exception(const T &exc, const std::exception_ptr &p) { std::exception_ptr nested = exc.nested_ptr(); if (nested != nullptr && nested != p) { - translate_exception(nested); - return true; + const auto &local_translators = get_local_exception_translators(); + if (apply_exception_translators(local_translators, nested)) { + return true; + } + + const auto &translators = get_exception_translators(); + if (apply_exception_translators(translators, nested)) { + return true; + } } return false; } @@ -493,6 +522,10 @@ PYBIND11_NOINLINE internals &get_internals() { return **internals_pp; } +inline const std::forward_list &get_exception_translators() { + return get_internals().registered_exception_translators; +} + // the internals struct (above) is shared between all the modules. local_internals are only // for a single module. Any changes made to internals may require an update to // PYBIND11_INTERNALS_VERSION, breaking backwards compatibility. local_internals is, by design, @@ -549,6 +582,10 @@ inline local_internals &get_local_internals() { return *locals; } +inline const std::forward_list &get_local_exception_translators() { + return get_local_internals().registered_exception_translators; +} + /// Constructs a std::string with the given arguments, stores it in `internals`, and returns its /// `c_str()`. Such strings objects have a long storage duration -- the internal strings are only /// cleared when the program exits or after interpreter shutdown (when embedding), and so are diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index 6205effd61..5f142ef54d 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -52,24 +52,6 @@ PYBIND11_WARNING_DISABLE_MSVC(4127) PYBIND11_NAMESPACE_BEGIN(detail) -// Apply all the extensions translators from a list -// Return true if one of the translators completed without raising an exception -// itself. Return of false indicates that if there are other translators -// available, they should be tried. -inline bool apply_exception_translators(std::forward_list &translators) { - auto last_exception = std::current_exception(); - - for (auto &translator : translators) { - try { - translator(last_exception); - return true; - } catch (...) { - last_exception = std::current_exception(); - } - } - return false; -} - #if defined(_MSC_VER) # define PYBIND11_COMPAT_STRDUP _strdup #else @@ -2541,7 +2523,7 @@ class exception : public object { } // Sets the current python exception to this exception object with the given message - void operator()(const char *message) { PyErr_SetString(m_ptr, message); } + void operator()(const char *message) { detail::raise_err(m_ptr, message); } }; PYBIND11_NAMESPACE_BEGIN(detail) @@ -2573,6 +2555,8 @@ register_exception_impl(handle scope, const char *name, handle base, bool isLoca try { std::rethrow_exception(p); } catch (const CppException &e) { + detail::handle_nested_exception(e, p); + detail::get_exception_object()(e.what()); } }); diff --git a/tests/test_exceptions.cpp b/tests/test_exceptions.cpp index f57e095068..b03fec3921 100644 --- a/tests/test_exceptions.cpp +++ b/tests/test_exceptions.cpp @@ -297,11 +297,35 @@ TEST_SUBMODULE(exceptions, m) { } }); - m.def("throw_nested_exception", []() { + m.def("throw_stl_nested_exception", []() { try { - throw std::runtime_error("Inner Exception"); + throw std::runtime_error("Inner STL Exception"); } catch (const std::runtime_error &) { - std::throw_with_nested(std::runtime_error("Outer Exception")); + std::throw_with_nested(std::runtime_error("Outer STL Exception")); + } + }); + + m.def("throw_stl_nested_exception_with_custom_exception", []() { + try { + throw std::runtime_error("Inner STL Exception"); + } catch (const std::runtime_error &) { + std::throw_with_nested(MyException5("Outer Custom Exception")); + } + }); + + m.def("throw_custom_nested_exception_with_stl_exception", []() { + try { + throw MyException5("Inner Custom Exception"); + } catch (const MyException5 &) { + std::throw_with_nested(std::runtime_error("Outer STL Exception")); + } + }); + + m.def("throw_custom_nested_exception_with_custom_exception", []() { + try { + throw MyException5("Inner Custom Exception"); + } catch (const MyException5 &) { + std::throw_with_nested(MyException5("Outer Custom Exception")); } }); diff --git a/tests/test_exceptions.py b/tests/test_exceptions.py index 0d2c808143..13f378941c 100644 --- a/tests/test_exceptions.py +++ b/tests/test_exceptions.py @@ -241,11 +241,32 @@ def pycatch(exctype, f, *args): assert str(excinfo.value) == "this is a helper-defined translated exception" -def test_throw_nested_exception(): +def test_throw_stl_nested_exception(): with pytest.raises(RuntimeError) as excinfo: - m.throw_nested_exception() - assert str(excinfo.value) == "Outer Exception" - assert str(excinfo.value.__cause__) == "Inner Exception" + m.throw_stl_nested_exception() + assert str(excinfo.value) == "Outer STL Exception" + assert str(excinfo.value.__cause__) == "Inner STL Exception" + + +def test_throw_stl_nested_exception_with_custom_exception(): + with pytest.raises(m.MyException5) as excinfo: + m.throw_stl_nested_exception_with_custom_exception() + assert str(excinfo.value) == "Outer Custom Exception" + assert str(excinfo.value.__cause__) == "Inner STL Exception" + + +def test_throw_custom_nested_exception_with_stl_exception(): + with pytest.raises(RuntimeError) as excinfo: + m.throw_custom_nested_exception_with_stl_exception() + assert str(excinfo.value) == "Outer STL Exception" + assert str(excinfo.value.__cause__) == "Inner Custom Exception" + + +def test_throw_custom_nested_exception_with_custom_exception(): + with pytest.raises(m.MyException5) as excinfo: + m.throw_custom_nested_exception_with_custom_exception() + assert str(excinfo.value) == "Outer Custom Exception" + assert str(excinfo.value.__cause__) == "Inner Custom Exception" # This can often happen if you wrap a pybind11 class in a Python wrapper