Skip to content

Commit 4d90f1a

Browse files
authored
Add error_scope to py::class_::dealloc() to protect destructor calls (#2342)
Fixes issue #1878
1 parent b804724 commit 4d90f1a

File tree

3 files changed

+24
-0
lines changed

3 files changed

+24
-0
lines changed

include/pybind11/pybind11.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1388,6 +1388,13 @@ class class_ : public detail::generic_type {
13881388

13891389
/// Deallocates an instance; via holder, if constructed; otherwise via operator delete.
13901390
static void dealloc(detail::value_and_holder &v_h) {
1391+
// We could be deallocating because we are cleaning up after a Python exception.
1392+
// If so, the Python error indicator will be set. We need to clear that before
1393+
// running the destructor, in case the destructor code calls more Python.
1394+
// If we don't, the Python API will exit with an exception, and pybind11 will
1395+
// throw error_already_set from the C++ destructor which is forbidden and triggers
1396+
// std::terminate().
1397+
error_scope scope;
13911398
if (v_h.holder_constructed()) {
13921399
v_h.holder<holder_type>().~holder_type();
13931400
v_h.set_holder_constructed(false);

tests/test_class.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,17 @@ TEST_SUBMODULE(class_, m) {
379379
// test_non_final_final
380380
struct IsNonFinalFinal {};
381381
py::class_<IsNonFinalFinal>(m, "IsNonFinalFinal", py::is_final());
382+
383+
struct PyPrintDestructor {
384+
PyPrintDestructor() {}
385+
~PyPrintDestructor() {
386+
py::print("Print from destructor");
387+
}
388+
void throw_something() { throw std::runtime_error("error"); }
389+
};
390+
py::class_<PyPrintDestructor>(m, "PyPrintDestructor")
391+
.def(py::init<>())
392+
.def("throw_something", &PyPrintDestructor::throw_something);
382393
}
383394

384395
template <int N> class BreaksBase { public:

tests/test_class.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,3 +323,9 @@ def test_non_final_final():
323323
class PyNonFinalFinalChild(m.IsNonFinalFinal):
324324
pass
325325
assert str(exc_info.value).endswith("is not an acceptable base type")
326+
327+
328+
# https://github.com/pybind/pybind11/issues/1878
329+
def test_exception_rvalue_abort():
330+
with pytest.raises(RuntimeError):
331+
m.PyPrintDestructor().throw_something()

0 commit comments

Comments
 (0)