pybind / pybind11

Seamless operability between C++11 and Python
https://pybind11.readthedocs.io/
Other
15.11k stars 2.05k forks source link

[BUG]: Python 3.9+ segfault in Debug build for pure virtual error message when GIL released #4878

Open feltech opened 9 months ago

feltech commented 9 months ago

Required prerequisites

What version (or hash if on master) of pybind11 are you using?

2.10.1

Problem description

Calling a pure virtual method that is bound via a PYBIND11_OVERRIDE_PURE trampoline implementation, where there is no corresponding Python implementation, calls into pybind11::pybind11_fail in order to throw an exception notifying the user that this pure virtual method has not been overridden.

The pybind11::pybind11_fail function has an assert(!PyErr_Occurred()) line, which obviously only (usually) affects Debug builds.

If the method releases the GIL before the C++ body (py::call_guard<py::gil_scoped_release>{}), then when that assertion is triggered in a Debug build, the CPython tstate is NULL and we get a segfault.

This appears to be caused by a change in CPython https://github.com/python/cpython/pull/17080 / https://bugs.python.org/issue38733 - which indicates this affects Python 3.9+. I.e.

IMHO PyErr_Occurred() must not be called if the GIL is released

[...] It can wait for Python 3.9.

Reproducible example code

Confirmed the following behaves as expected in Python 3.8, but segfaults in Python 3.9

CMakeLists.txt

cmake_minimum_required(VERSION 3.21)
project(segfaulty)
find_package(pybind11 REQUIRED)
pybind11_add_module(segfaulty MODULE)
target_sources(segfaulty PRIVATE segfaulty.cpp)

segfaulty.cpp

#include <pybind11/pybind11.h>

struct PureVirtual {
    virtual ~PureVirtual() = default;
    virtual void method() = 0;
};

struct PyPureVirtual : PureVirtual {
    void method() override {
        PYBIND11_OVERRIDE_PURE(void, PureVirtual, method);
    }
};

PYBIND11_MODULE(segfaulty, mod) {
    namespace py = pybind11;
    py::class_<PureVirtual, PyPureVirtual>{mod, "PureVirtual"}
            .def(py::init<>())
            .def("method", &PureVirtual::method, py::call_guard<py::gil_scoped_release>{});
}

Test, assuming pybind11 and Python are discoverable and Python 3.9 is the discovered Python version

cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug 
cmake --build build
cd build
python3.9 -c "import segfaulty; obj = segfaulty.PureVirtual(); obj.method()"

The yields

Segmentation fault (core dumped)

on Python 3.9, and

Traceback (most recent call last):
  File "<string>", line 1, in <module>
RuntimeError: Tried to call pure virtual function "PureVirtual::method"

on Python 3.8.

Is this a regression? Put the last known working version here if it is.

Not a regression