[BUG] SIGSEGV when constructor of abstract interface is not called from python
Nobody has claimed this yet.
- Dominant language
- C++
- Stars
- 18k
- Forks
- 2.3k
- Avg merge
- 5d 17h
- Merged PRs (30d)
- 10
Description
Issue description
When exposing a C++ abstract interface that should be implemented in Python-land, if the derived python class doesn't call super().__init__(), calling functions on the interface from the C++-land will result in an UBSan violation and a subsequent segmentation fault.
This is quite unintuitive since the base class is abstract and one would not expect it to be necessary to call the base constructor.
Reproducible example code
Define bindings:
#include <pybind11/pybind11.h>
namespace py = pybind11;
struct MyIfc {
virtual ~MyIfc() = default;
virtual void callback(float f) = 0;
};
class User {
public:
User(MyIfc& ifc) : mIfc(ifc) {}
void doAction() { mIfc.callback(3.0); }
private:
MyIfc& mIfc;
};
struct MyIfcPy final : MyIfc {
void callback(float f) override {
PYBIND11_OVERLOAD_PURE(void, MyIfc, callback, f);
}
};
PYBIND11_MODULE(pybug_test, module) {
py::class_<MyIfc, MyIfcPy>(module, "MyIfc")
.def(py::init())
.def("callback", &MyIfc::callback)
;
py::class_<User>(module, "User")
.def(py::init<MyIfc&>(), py::keep_alive<1, 2>())
.def("do_action", &User::doAction)
;
}
(note that keep_alive is used to guarantee this is not related to the temporary object going out of scope: the issue also happens when keeping the reference around)
Try to use on the python side:
import pybug_test
from pybug_test import MyIfc, User
class Ifc(MyIfc):
def __init__(self):
# super().__init__() # Uncomment and test passes.
pass
i = Ifc()
u = User(Ifc())
u.do_action()
Note that the python implementation doesn't even define do_action: we never really get to MyIfcPy::callback for the error to be thrown. This results in the following UBSan violation:
src/pybind_bug.cpp:13:40: runtime error: load of misaligned address 0xbebebebebebebece for type '<unknown> *', which requires 8 byte alignment
0xbebebebebebebece: note: pointer points here
<memory cannot be printed>
and the subsequent segmentation fault:
AddressSanitizer:DEADLYSIGNAL
=================================================================
==47815==ERROR: AddressSanitizer: SEGV on unknown address (pc 0x7f78468a6c2e bp 0x7ffea1fe9a40 sp 0x7ffea1fe9a20T0)
==47815==The signal is caused by a READ memory access.
==47815==Hint: this fault was caused by a dereference of a high value address (see register values below). Dissassemble the provided pc to learn which register was used.
#0 0x7f78468a6c2e in User::doAction() ./src/pybind_bug.cpp:13
#1 0x7f78468d4a2f in pybind11::cpp_function::cpp_function<void, User, , pybind11::name, pybind11::is_method, pybind11::sibling>(void (User::*)(), pybind11::name const&, pybind11::is_method const&, pybind11::sibling const&)::{lambda(User*)#1}::operator()(User*) const ./pybind11/include/pybind11/pybind11.h:78
#2 0x7f784690f81a in void pybind11::detail::argument_loader<User*>::call_impl<void, pybind11::cpp_function::cpp_function<void, User, , pybind11::name, pybind11::is_method, pybind11::sibling>(void (User::*)(), pybind11::name const&, pybind11::is_method const&, pybind11::sibling const&)::{lambda(User*)#1}&, 0ul, pybind11::detail::void_type>(pybind11::cpp_function::cpp_function<void, User, , pybind11::name, pybind11::is_method, pybind11::sibling>(void (User::*)(), pybind11::name const&, pybind11::is_method const&, pybind11::sibling const&)::{lambda(User*)#1}&, std::integer_sequence<unsigned long, 0ul>, pybind11::detail::void_type&&) && ./pybind11/include/pybind11/cast.h:1993
#3 0x7f78468fe1c4 in std::enable_if<std::is_void<void>::value, pybind11::detail::void_type>::type pybind11::detail::argument_loader<User*>::call<void, pybind11::detail::void_type, pybind11::cpp_function::cpp_function<void, User, , pybind11::name, pybind11::is_method, pybind11::sibling>(void (User::*)(), pybind11::name const&, pybind11::is_method const&, pybind11::sibling const&)::{lambda(User*)#1}&>(pybind11::cpp_function::cpp_function<void, User, , pybind11::name, pybind11::is_method, pybind11::sibling>(void (User::*)(), pybind11::name const&, pybind11::is_method const&, pybind11::sibling const&)::{lambda(User*)#1}&) && ./pybind11/include/pybind11/cast.h:1970
#4 0x7f78468eab4e in pybind11::cpp_function::initialize<pybind11::cpp_function::initialize<void, User, , pybind11::name, pybind11::is_method, pybind11::sibling>(void (User::*)(), pybind11::name const&, pybind11::is_method const&, pybind11::sibling const&)::{lambda(User*)#1}, void, User*, pybind11::name, pybind11::is_method, pybind11::sibling>(pybind11::cpp_function::initialize<void, User, , pybind11::name, pybind11::is_method, pybind11::sibling>(void (User::*)(), pybind11::name const&, pybind11::is_method const&, pybind11::sibling const&)::{lambda(User*)#1}&&, void (*)(User*), pybind11::name const&, pybind11::is_method const&, pybind11::sibling const&)::{lambda(pybind11::detail::function_call&)#3}::operator()(pybind11::detail::function_call) const ./pybind11/include/pybind11/pybind11.h:160
#5 0x7f78468eafe7 in pybind11::cpp_function::initialize<pybind11::cpp_function::initialize<void, User, , pybind11::name, pybind11::is_method, pybind11::sibling>(void (User::*)(), pybind11::name const&, pybind11::is_method const&, pybind11::sibling const&)::{lambda(User*)#1}, void, User*, pybind11::name, pybind11::is_method, pybind11::sibling>(pybind11::cpp_function::initialize<void, User, , pybind11::name, pybind11::is_method, pybind11::sibling>(void (User::*)(), pybind11::name const&, pybind11::is_method const&, pybind11::sibling const&)::{lambda(User*)#1}&&, void (*)(User*), pybind11::name const&, pybind11::is_method const&, pybind11::sibling const&)::{lambda(pybind11::detail::function_call&)#3}::_FUN(pybind11::detail::function_call) ./pybind11/include/pybind11/pybind11.h:137
#6 0x7f784689bb43 in pybind11::cpp_function::dispatcher(_object*, _object*, _object*) ./pybind11/include/pybind11/pybind11.h:633
#7 0x55c7033fa743 in _PyMethodDef_RawFastCallKeywords /tmp/build/80754af9/python_1565725737370/work/Objects/call.c:694
#8 0x55c7033fa860 in _PyCFunction_FastCallKeywords /tmp/build/80754af9/python_1565725737370/work/Objects/call.c:734
#9 0x55c7034666e7 in call_function /tmp/build/80754af9/python_1565725737370/work/Python/ceval.c:4619
#10 0x55c7034666e7 in _PyEval_EvalFrameDefault /tmp/build/80754af9/python_1565725737370/work/Python/ceval.c:3093
#11 0x55c7033aa538 in _PyEval_EvalCodeWithName /tmp/build/80754af9/python_1565725737370/work/Python/ceval.c:3930
#12 0x55c7033ab423 in PyEval_EvalCodeEx /tmp/build/80754af9/python_1565725737370/work/Python/ceval.c:3959
#13 0x55c7033ab44b in PyEval_EvalCode /tmp/build/80754af9/python_1565725737370/work/Python/ceval.c:524
#14 0x55c7034c0b73 in run_mod /tmp/build/80754af9/python_1565725737370/work/Python/pythonrun.c:1035
#15 0x55c7034caeb0 in PyRun_FileExFlags /tmp/build/80754af9/python_1565725737370/work/Python/pythonrun.c:988
#16 0x55c7034cb0a2 in PyRun_SimpleFileExFlags /tmp/build/80754af9/python_1565725737370/work/Python/pythonrun.c:429
#17 0x55c7034cc194 in pymain_run_file /tmp/build/80754af9/python_1565725737370/work/Modules/main.c:433
#18 0x55c7034cc194 in pymain_run_filename /tmp/build/80754af9/python_1565725737370/work/Modules/main.c:1612
#19 0x55c7034cc194 in pymain_run_python /tmp/build/80754af9/python_1565725737370/work/Modules/main.c:2873
#20 0x55c7034cc194 in pymain_main /tmp/build/80754af9/python_1565725737370/work/Modules/main.c:3413
#21 0x55c7034cc2bb in _Py_UnixMain /tmp/build/80754af9/python_1565725737370/work/Modules/main.c:3448
#22 0x7f784e1a9349 in __libc_start_main (/lib64/libc.so.6+0x24349)
#23 0x55c703471061 (/opt/anaconda3/bin/python3+0x1db061)
AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV ./src/pybind_bug.cpp:13 in User::doAction()
==47815==ABORTING
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by reproducing the failure from src/pybind_bug.cpp, focusing on User::doAction, the MyIfcPy trampoline, and the py::class_ binding using PYBIND11_OVERLOAD_PURE. Read the related pybind11/include headers and compare runs with and without super().init(). Done means the shown Python subclass can invoke do_action without a UBSan violation or segmentation fault.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp, python
- Domain
- api
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100