Harden PYBIND11_MODULE_PYINIT and get_internals() against crashes during module init (#6018)

* Wrap ensure_internals() in try-catch in PYBIND11_MODULE_PYINIT

Previously, ensure_internals() was called without exception handling
in the PyInit_* function (PYBIND11_MODULE_PYINIT), while the same call
in PYBIND11_MODULE_EXEC was already wrapped in try-catch. On MSVC,
a C++ exception propagating through the extern "C" PyInit_* boundary
is undefined behavior, which can manifest as an access violation
instead of a clean error message. This is a potential contributor to
crashes like gh-5993. Wrap the entire PyInit body in try/catch using
the existing PYBIND11_CATCH_INIT_EXCEPTIONS pattern.

Made-with: Cursor

* Add nullptr guards in get_internals() for better crash diagnostics

Add explicit null checks after get_pp() and create_pp_content_once()
in get_internals(), calling pybind11_fail() with descriptive messages.
These guards convert potential null-pointer dereferences (which produce
unhelpful access-violation crashes, especially on Windows) into clear
runtime_error messages that can be caught and reported as ImportError
by the try-catch added in the previous commit.

Made-with: Cursor
This commit is contained in:
Ralf W. Grosse-Kunstleve
2026-03-29 22:03:41 +07:00
committed by Ralf W. Grosse-Kunstleve
parent ec875b63f0
commit 0184c0212f
2 changed files with 26 additions and 14 deletions

View File

@@ -449,19 +449,24 @@ PyModuleDef_Init should be treated like any other PyObject (so not shared across
static int PYBIND11_CONCAT(pybind11_exec_, name)(PyObject *); \
PYBIND11_PLUGIN_IMPL(name) { \
PYBIND11_CHECK_PYTHON_VERSION \
pybind11::detail::ensure_internals(); \
static ::pybind11::detail::slots_array mod_def_slots = ::pybind11::detail::init_slots( \
&PYBIND11_CONCAT(pybind11_exec_, name), ##__VA_ARGS__); \
static PyModuleDef def{/* m_base */ PyModuleDef_HEAD_INIT, \
/* m_name */ PYBIND11_TOSTRING(name), \
/* m_doc */ nullptr, \
/* m_size */ 0, \
/* m_methods */ nullptr, \
/* m_slots */ mod_def_slots.data(), \
/* m_traverse */ nullptr, \
/* m_clear */ nullptr, \
/* m_free */ nullptr}; \
return PyModuleDef_Init(&def); \
try { \
pybind11::detail::ensure_internals(); \
static ::pybind11::detail::slots_array mod_def_slots \
= ::pybind11::detail::init_slots(&PYBIND11_CONCAT(pybind11_exec_, name), \
##__VA_ARGS__); \
static PyModuleDef def{/* m_base */ PyModuleDef_HEAD_INIT, \
/* m_name */ PYBIND11_TOSTRING(name), \
/* m_doc */ nullptr, \
/* m_size */ 0, \
/* m_methods */ nullptr, \
/* m_slots */ mod_def_slots.data(), \
/* m_traverse */ nullptr, \
/* m_clear */ nullptr, \
/* m_free */ nullptr}; \
return PyModuleDef_Init(&def); \
} \
PYBIND11_CATCH_INIT_EXCEPTIONS \
return nullptr; \
}
#define PYBIND11_MODULE_EXEC(name, variable) \

View File

@@ -864,7 +864,11 @@ inline internals_pp_manager<internals> &get_internals_pp_manager() {
/// Return a reference to the current `internals` data
PYBIND11_NOINLINE internals &get_internals() {
auto &ppmgr = get_internals_pp_manager();
auto &internals_ptr = *ppmgr.get_pp();
auto *pp = ppmgr.get_pp();
if (!pp) {
pybind11_fail("get_internals: get_pp() returned nullptr");
}
auto &internals_ptr = *pp;
if (!internals_ptr) {
// Slow path, something needs fetched from the state dict or created
gil_scoped_acquire_simple gil;
@@ -872,6 +876,9 @@ PYBIND11_NOINLINE internals &get_internals() {
ppmgr.create_pp_content_once(&internals_ptr);
if (!internals_ptr) {
pybind11_fail("get_internals: create_pp_content_once() produced nullptr");
}
if (!internals_ptr->instance_base) {
// This calls get_internals, so cannot be called from within the internals constructor
// called above because internals_ptr must be set before get_internals is called again