pybind / pybind11

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

[BUG] SIGSEGV when constructor of abstract interface is not called from python #3061

Open eullerborges opened 3 years ago

eullerborges commented 3 years ago

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
eullerborges commented 3 years ago

This comment on the release notes for v2.6 mention this issue should apparently have been fixed:

An error is now thrown when __init__ is forgotten on subclasses. This was incorrect before, but was not checked. Add a call to __init__ if it is missing.

But I am running this on v2.6.2 and can still reproduce it.