mirror of
https://github.com/pybind/pybind11.git
synced 2024-11-11 08:03:55 +00:00
Fix builtin exception handlers to work across modules
The builtin exception handler currently doesn't work across modules under clang/libc++ for builtin pybind exceptions like `pybind11::error_already_set` or `pybind11::stop_iteration`: under RTLD_LOCAL module loading clang considers each module's exception classes distinct types. This then means that the base exception translator fails to catch the exceptions and the fall through to the generic `std::exception` handler, which completely breaks things like `stop_iteration`: only the `stop_iteration` of the first module loaded actually works properly; later modules raise a RuntimeError with no message when trying to invoke their iterators. For example, two modules defined like this exhibit the behaviour under clang++/libc++: z1.cpp: #include <pybind11/pybind11.h> #include <pybind11/stl_bind.h> namespace py = pybind11; PYBIND11_MODULE(z1, m) { py::bind_vector<std::vector<long>>(m, "IntVector"); } z2.cpp: #include <pybind11/pybind11.h> #include <pybind11/stl_bind.h> namespace py = pybind11; PYBIND11_MODULE(z2, m) { py::bind_vector<std::vector<double>>(m, "FloatVector"); } Python: import z1, z2 for i in z2.FloatVector(): pass results in: Traceback (most recent call last): File "zs.py", line 2, in <module> for i in z2.FloatVector(): RuntimeError This commit fixes the issue by adding a new exception translator each time the internals pointer is initialized from python builtins: this generally means the internals data was initialized by some other module. (The extra translator(s) are skipped under libstdc++).
This commit is contained in:
parent
0bd5979c77
commit
d598172993
@ -73,6 +73,23 @@ PYBIND11_NOINLINE inline internals &get_internals() {
|
||||
const char *id = PYBIND11_INTERNALS_ID;
|
||||
if (builtins.contains(id) && isinstance<capsule>(builtins[id])) {
|
||||
internals_ptr = *static_cast<internals **>(capsule(builtins[id]));
|
||||
|
||||
// We loaded builtins through python's builtins, which means that our error_already_set and
|
||||
// builtin_exception may be different local classes than the ones set up in the initial
|
||||
// exception translator, below, so add another for our local exception classes.
|
||||
//
|
||||
// stdlibc++ doesn't require this (types there are identified only by name)
|
||||
#if !defined(__GLIBCXX__)
|
||||
internals_ptr->registered_exception_translators.push_front(
|
||||
[](std::exception_ptr p) -> void {
|
||||
try {
|
||||
if (p) std::rethrow_exception(p);
|
||||
} catch (error_already_set &e) { e.restore(); return;
|
||||
} catch (const builtin_exception &e) { e.set_error(); return;
|
||||
}
|
||||
}
|
||||
);
|
||||
#endif
|
||||
} else {
|
||||
internals_ptr = new internals();
|
||||
#if defined(WITH_THREAD)
|
||||
|
@ -71,6 +71,7 @@ string(REPLACE ".cpp" ".py" PYBIND11_PYTEST_FILES "${PYBIND11_TEST_FILES}")
|
||||
# built; if none of these are built (i.e. because TEST_OVERRIDE is used and
|
||||
# doesn't include them) the second module doesn't get built.
|
||||
set(PYBIND11_CROSS_MODULE_TESTS
|
||||
test_exceptions.py
|
||||
)
|
||||
|
||||
# Check if Eigen is available; if not, remove from PYBIND11_TEST_FILES (but
|
||||
|
@ -17,4 +17,11 @@ PYBIND11_MODULE(pybind11_cross_module_tests, m) {
|
||||
// Definitions here are tested by importing both this module and the
|
||||
// relevant pybind11_tests submodule from a test_whatever.py
|
||||
|
||||
// test_exceptions.py
|
||||
m.def("raise_runtime_error", []() { PyErr_SetString(PyExc_RuntimeError, "My runtime error"); throw py::error_already_set(); });
|
||||
m.def("raise_value_error", []() { PyErr_SetString(PyExc_ValueError, "My value error"); throw py::error_already_set(); });
|
||||
m.def("throw_pybind_value_error", []() { throw py::value_error("pybind11 value error"); });
|
||||
m.def("throw_pybind_type_error", []() { throw py::type_error("pybind11 type error"); });
|
||||
m.def("throw_stop_iteration", []() { throw py::stop_iteration(); });
|
||||
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import pytest
|
||||
|
||||
from pybind11_tests import exceptions as m
|
||||
import pybind11_cross_module_tests as cm
|
||||
|
||||
|
||||
def test_std_exception(msg):
|
||||
@ -19,6 +20,27 @@ def test_error_already_set(msg):
|
||||
assert msg(excinfo.value) == "foo"
|
||||
|
||||
|
||||
def test_cross_module_exceptions():
|
||||
with pytest.raises(RuntimeError) as excinfo:
|
||||
cm.raise_runtime_error()
|
||||
assert str(excinfo.value) == "My runtime error"
|
||||
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
cm.raise_value_error()
|
||||
assert str(excinfo.value) == "My value error"
|
||||
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
cm.throw_pybind_value_error()
|
||||
assert str(excinfo.value) == "pybind11 value error"
|
||||
|
||||
with pytest.raises(TypeError) as excinfo:
|
||||
cm.throw_pybind_type_error()
|
||||
assert str(excinfo.value) == "pybind11 type error"
|
||||
|
||||
with pytest.raises(StopIteration) as excinfo:
|
||||
cm.throw_stop_iteration()
|
||||
|
||||
|
||||
def test_python_call_in_catch():
|
||||
d = {}
|
||||
assert m.python_call_in_destructor(d) is True
|
||||
|
Loading…
Reference in New Issue
Block a user