From 1bd0eaae0676f2d71f2b8e8b2181a56379d6b21b Mon Sep 17 00:00:00 2001 From: Ed Catmur Date: Tue, 28 Jun 2022 15:12:00 +0100 Subject: [PATCH 1/3] Expose exception cause as std::nested_exception --- include/pybind11/pytypes.h | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index f9625e77e..f5ba91206 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -546,6 +546,8 @@ struct error_fetch_and_normalize { return (PyErr_GivenExceptionMatches(m_type.ptr(), exc.ptr()) != 0); } + std::nested_exception get_cause_as_nested() const; + // Not protecting these for simplicity. object m_type, m_value, m_trace; @@ -572,13 +574,15 @@ PYBIND11_NAMESPACE_END(detail) /// thrown to propagate python-side errors back through C++ which can either be caught manually or /// else falls back to the function dispatcher (which then raises the captured error back to /// python). -class PYBIND11_EXPORT_EXCEPTION error_already_set : public std::exception { +class PYBIND11_EXPORT_EXCEPTION error_already_set : public std::exception, public std::nested_exception { public: /// Fetches the current Python exception (using PyErr_Fetch()), which will clear the /// current Python error indicator. error_already_set() : m_fetched_error{new detail::error_fetch_and_normalize("pybind11::error_already_set"), - m_fetched_error_deleter} {} + m_fetched_error_deleter} { + static_cast(*this) = m_fetched_error->get_cause_as_nested(); + } /// The what() result is built lazily on demand. /// WARNING: This member function needs to acquire the Python GIL. This can lead to @@ -2327,6 +2331,20 @@ bool object_api::rich_compare(object_api const &other, int value) const { return rv == 1; } +inline std::nested_exception error_fetch_and_normalize::get_cause_as_nested() const { + auto cause = reinterpret_steal(PyException_GetCause(m_value.ptr())); + if (not cause or cause.is_none()) + return std::nested_exception(); + auto typ = type::of(cause); + auto tb = reinterpret_steal(PyException_GetTraceback(cause.ptr())); + PyErr_Restore(typ.release().ptr(), cause.release().ptr(), tb.release().ptr()); + try { + throw error_already_set(); + } catch (...) { + return std::nested_exception(); + } +} + #define PYBIND11_MATH_OPERATOR_UNARY(op, fn) \ template \ object object_api::op() const { \ From a8046ffe2d47b044b0e043de8623adbc2f5ba560 Mon Sep 17 00:00:00 2001 From: Ed Catmur Date: Tue, 28 Jun 2022 15:50:28 +0100 Subject: [PATCH 2/3] don't set nested exception if already set --- include/pybind11/pytypes.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index f5ba91206..9205e2b38 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -581,7 +581,8 @@ public: error_already_set() : m_fetched_error{new detail::error_fetch_and_normalize("pybind11::error_already_set"), m_fetched_error_deleter} { - static_cast(*this) = m_fetched_error->get_cause_as_nested(); + if (not nested_ptr()) + static_cast(*this) = m_fetched_error->get_cause_as_nested(); } /// The what() result is built lazily on demand. From 059362bcab31353a97555ae0d3c674833cc2ab4d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 28 Jun 2022 14:52:40 +0000 Subject: [PATCH 3/3] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- include/pybind11/pytypes.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index 9205e2b38..4647d0e70 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -574,7 +574,8 @@ PYBIND11_NAMESPACE_END(detail) /// thrown to propagate python-side errors back through C++ which can either be caught manually or /// else falls back to the function dispatcher (which then raises the captured error back to /// python). -class PYBIND11_EXPORT_EXCEPTION error_already_set : public std::exception, public std::nested_exception { +class PYBIND11_EXPORT_EXCEPTION error_already_set : public std::exception, + public std::nested_exception { public: /// Fetches the current Python exception (using PyErr_Fetch()), which will clear the /// current Python error indicator. @@ -582,7 +583,7 @@ public: : m_fetched_error{new detail::error_fetch_and_normalize("pybind11::error_already_set"), m_fetched_error_deleter} { if (not nested_ptr()) - static_cast(*this) = m_fetched_error->get_cause_as_nested(); + static_cast(*this) = m_fetched_error->get_cause_as_nested(); } /// The what() result is built lazily on demand.