From 4d90f1a1998b394e075660fea1dbaf9d24ea0039 Mon Sep 17 00:00:00 2001 From: jbarlow83 Date: Fri, 31 Jul 2020 17:46:12 -0700 Subject: [PATCH] Add error_scope to py::class_::dealloc() to protect destructor calls (#2342) Fixes issue #1878 --- include/pybind11/pybind11.h | 7 +++++++ tests/test_class.cpp | 11 +++++++++++ tests/test_class.py | 6 ++++++ 3 files changed, 24 insertions(+) diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index 87f4645e6..d34c92c24 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -1388,6 +1388,13 @@ private: /// Deallocates an instance; via holder, if constructed; otherwise via operator delete. static void dealloc(detail::value_and_holder &v_h) { + // We could be deallocating because we are cleaning up after a Python exception. + // If so, the Python error indicator will be set. We need to clear that before + // running the destructor, in case the destructor code calls more Python. + // If we don't, the Python API will exit with an exception, and pybind11 will + // throw error_already_set from the C++ destructor which is forbidden and triggers + // std::terminate(). + error_scope scope; if (v_h.holder_constructed()) { v_h.holder().~holder_type(); v_h.set_holder_constructed(false); diff --git a/tests/test_class.cpp b/tests/test_class.cpp index 7edcdce36..5369cb064 100644 --- a/tests/test_class.cpp +++ b/tests/test_class.cpp @@ -379,6 +379,17 @@ TEST_SUBMODULE(class_, m) { // test_non_final_final struct IsNonFinalFinal {}; py::class_(m, "IsNonFinalFinal", py::is_final()); + + struct PyPrintDestructor { + PyPrintDestructor() {} + ~PyPrintDestructor() { + py::print("Print from destructor"); + } + void throw_something() { throw std::runtime_error("error"); } + }; + py::class_(m, "PyPrintDestructor") + .def(py::init<>()) + .def("throw_something", &PyPrintDestructor::throw_something); } template class BreaksBase { public: diff --git a/tests/test_class.py b/tests/test_class.py index c38a5e8ce..bbf8481a4 100644 --- a/tests/test_class.py +++ b/tests/test_class.py @@ -323,3 +323,9 @@ def test_non_final_final(): class PyNonFinalFinalChild(m.IsNonFinalFinal): pass assert str(exc_info.value).endswith("is not an acceptable base type") + + +# https://github.com/pybind/pybind11/issues/1878 +def test_exception_rvalue_abort(): + with pytest.raises(RuntimeError): + m.PyPrintDestructor().throw_something()