Skip to content

Fix segfault when reloading interpreter with external modules #1092

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jan 11, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 12 additions & 10 deletions include/pybind11/detail/internals.h
Original file line number Diff line number Diff line change
Expand Up @@ -127,29 +127,29 @@ struct type_info {

/// Each module locally stores a pointer to the `internals` data. The data
/// itself is shared among modules with the same `PYBIND11_INTERNALS_ID`.
inline internals *&get_internals_ptr() {
static internals *internals_ptr = nullptr;
return internals_ptr;
inline internals **&get_internals_pp() {
static internals **internals_pp = nullptr;
return internals_pp;
}

/// Return a reference to the current `internals` data
PYBIND11_NOINLINE inline internals &get_internals() {
auto *&internals_ptr = get_internals_ptr();
if (internals_ptr)
return *internals_ptr;
auto **&internals_pp = get_internals_pp();
if (internals_pp && *internals_pp)
return **internals_pp;

constexpr auto *id = PYBIND11_INTERNALS_ID;
auto builtins = handle(PyEval_GetBuiltins());
if (builtins.contains(id) && isinstance<capsule>(builtins[id])) {
internals_ptr = *static_cast<internals **>(capsule(builtins[id]));
internals_pp = 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.
//
// libstdc++ doesn't require this (types there are identified only by name)
#if !defined(__GLIBCXX__)
internals_ptr->registered_exception_translators.push_front(
(*internals_pp)->registered_exception_translators.push_front(
[](std::exception_ptr p) -> void {
try {
if (p) std::rethrow_exception(p);
Expand All @@ -160,6 +160,8 @@ PYBIND11_NOINLINE inline internals &get_internals() {
);
#endif
} else {
if (!internals_pp) internals_pp = new internals*();
auto *&internals_ptr = *internals_pp;
internals_ptr = new internals();
#if defined(WITH_THREAD)
PyEval_InitThreads();
Expand All @@ -168,7 +170,7 @@ PYBIND11_NOINLINE inline internals &get_internals() {
PyThread_set_key_value(internals_ptr->tstate, tstate);
internals_ptr->istate = tstate->interp;
#endif
builtins[id] = capsule(&internals_ptr);
builtins[id] = capsule(internals_pp);
internals_ptr->registered_exception_translators.push_front(
[](std::exception_ptr p) -> void {
try {
Expand All @@ -192,7 +194,7 @@ PYBIND11_NOINLINE inline internals &get_internals() {
internals_ptr->default_metaclass = make_default_metaclass();
internals_ptr->instance_base = make_object_base_type(internals_ptr->default_metaclass);
}
return *internals_ptr;
return **internals_pp;
}

/// Works like `internals.registered_types_cpp`, but for module-local registered types:
Expand Down
2 changes: 1 addition & 1 deletion include/pybind11/embed.h
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ inline void finalize_interpreter() {
// Get the internals pointer (without creating it if it doesn't exist). It's possible for the
// internals to be created during Py_Finalize() (e.g. if a py::capsule calls `get_internals()`
// during destruction), so we get the pointer-pointer here and check it after Py_Finalize().
detail::internals **internals_ptr_ptr = &detail::get_internals_ptr();
detail::internals **internals_ptr_ptr = detail::get_internals_pp();
// It could also be stashed in builtins, so look there too:
if (builtins.contains(id) && isinstance<capsule>(builtins[id]))
internals_ptr_ptr = capsule(builtins[id]);
Expand Down
5 changes: 5 additions & 0 deletions tests/test_embed/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,9 @@ target_link_libraries(test_embed PUBLIC ${CMAKE_THREAD_LIBS_INIT})

add_custom_target(cpptest COMMAND $<TARGET_FILE:test_embed>
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})

pybind11_add_module(external_module THIN_LTO external_module.cpp)
set_target_properties(external_module PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
add_dependencies(cpptest external_module)

add_dependencies(check cpptest)
23 changes: 23 additions & 0 deletions tests/test_embed/external_module.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#include <pybind11/pybind11.h>

namespace py = pybind11;

/* Simple test module/test class to check that the referenced internals data of external pybind11
* modules aren't preserved over a finalize/initialize.
*/

PYBIND11_MODULE(external_module, m) {
class A {
public:
A(int value) : v{value} {};
int v;
};

py::class_<A>(m, "A")
.def(py::init<int>())
.def_readwrite("value", &A::v);

m.def("internals_at", []() {
return reinterpret_cast<uintptr_t>(&py::detail::get_internals());
});
}
10 changes: 9 additions & 1 deletion tests/test_embed/test_interpreter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -94,14 +94,20 @@ bool has_pybind11_internals_builtin() {
};

bool has_pybind11_internals_static() {
return py::detail::get_internals_ptr() != nullptr;
auto **&ipp = py::detail::get_internals_pp();
return ipp && *ipp;
}

TEST_CASE("Restart the interpreter") {
// Verify pre-restart state.
REQUIRE(py::module::import("widget_module").attr("add")(1, 2).cast<int>() == 3);
REQUIRE(has_pybind11_internals_builtin());
REQUIRE(has_pybind11_internals_static());
REQUIRE(py::module::import("external_module").attr("A")(123).attr("value").cast<int>() == 123);

// local and foreign module internals should point to the same internals:
REQUIRE(reinterpret_cast<uintptr_t>(*py::detail::get_internals_pp()) ==
py::module::import("external_module").attr("internals_at")().cast<uintptr_t>());

// Restart the interpreter.
py::finalize_interpreter();
Expand All @@ -116,6 +122,8 @@ TEST_CASE("Restart the interpreter") {
pybind11::detail::get_internals();
REQUIRE(has_pybind11_internals_builtin());
REQUIRE(has_pybind11_internals_static());
REQUIRE(reinterpret_cast<uintptr_t>(*py::detail::get_internals_pp()) ==
py::module::import("external_module").attr("internals_at")().cast<uintptr_t>());

// Make sure that an interpreter with no get_internals() created until finalize still gets the
// internals destroyed
Expand Down