• Kirill Smelkov's avatar
    libgolang: Fix vtable/typeinfo of polymorphic interface classes to be emitted only once · 6eca3654
    Kirill Smelkov authored
    On Android it was discovered that e.g. test_pyx_errors_unwrap_cpp fails:
    
        __________________________________ test_pyx_errors_unwrap_cpp __________________________________
    
        func = <cyfunction test_errors_unwrap_cpp at 0x75ee34e740>
    
            def _(func=getattr(mod, f)):
        >       func()
    
        golang/golang_test.py:66:
        _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
        golang/_errors_test.pyx:61: in golang._errors_test.test_errors_unwrap_cpp
            _test_errors_unwrap_cpp()
        golang/_golang.pyx:77: in golang._golang.topyexc
            pypanic(pyarg)
        _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    
        >   raise _PanicError(arg)
        E   golang._golang._PanicError: golang/errors_test.cpp:76: errors::Unwrap(err2)
        E   have: 'nil'
        E   want: 'zzz'
    
        golang/_golang.pyx:61: _PanicError
    
    This issue was debugged down to that dynamic_cast<_errorWrapper*>
    running inside libgolang.so on the following type defined in
    errors_test.so
    
        struct _MyWrapError final : _errorWrapper, object {
    
    returns NULL.
    
    This happened because in errors_test.so the type_info for _errorWrapper
    was emitted in addition to _errorWrapper's type_info emitted in
    libgolang with both of them being weak symbols:
    
        ((1.venv) ) poco:~/pygolang$ nm -DC golang/_errors_test.cpython-312.so |grep 'typeinfo for'
        000000000004dba8 V typeinfo for _MyWrapError                    <-- NOTE
        000000000004dac0 V typeinfo for _MyError
        000000000004db10 V typeinfo for golang::_interface
        000000000004dbe0 V typeinfo for golang::_errorWrapper
        000000000004daf8 V typeinfo for golang::_error
        000000000004db20 V typeinfo for golang::object
                         U typeinfo for std::__ndk1::ios_base::failure
                         U typeinfo for std::bad_typeid
                         U typeinfo for std::range_error
                         U typeinfo for std::domain_error
                         U typeinfo for std::length_error
                         U typeinfo for std::out_of_range
                         U typeinfo for std::overflow_error
                         U typeinfo for std::underflow_error
                         U typeinfo for std::invalid_argument
                         U typeinfo for std::bad_array_new_length
                         U typeinfo for std::bad_cast
                         U typeinfo for std::bad_alloc
                         U typeinfo for std::exception
    
        ((1.venv) ) poco:~/pygolang$ nm -DC golang/runtime/liblibgolang.so |grep 'typeinfo for golang::'
        00000000000905c8 V typeinfo for golang::PanicError
        0000000000091128 V typeinfo for golang::_interface
        0000000000091c28 V typeinfo for golang::_errorWrapper           <-- NOTE
        00000000000905e0 V typeinfo for golang::Bug
        0000000000091cd0 V typeinfo for golang::fmt::_WrapError
        0000000000091110 V typeinfo for golang::_error
        0000000000091c68 V typeinfo for golang::errors::_TextError
        0000000000091138 V typeinfo for golang::object
        00000000000915e8 V typeinfo for golang::context::_CancelCtx
        0000000000091518 V typeinfo for golang::context::_Background
        0000000000091898 V typeinfo for golang::context::_TimeoutCtx
        00000000000914a0 V typeinfo for golang::context::_BaseCtx
        0000000000091488 V typeinfo for golang::context::_Context
        0000000000091828 V typeinfo for golang::context::_ValueCtx
        00000000000910d8 D typeinfo for golang::internal::syscall::_Errno
        0000000000091920 V typeinfo for golang::context::_TimeoutCtx::_TimeoutCtx(double, double, golang::refptr<golang::context::_Context>)::{lambda()#1}
        00000000000917c0 V typeinfo for golang::context::_BaseCtx::err()::{lambda()#1}
    
    and because of this typeid(_errorWrapper) returns different pointers in
    libgolang.so and errors_test.so .
    
    This did not matter with gcc and libstdc++ because, it seems,
    dynamic_cast is not checking only pointer equality for type_info, but on
    android/clang++ it made the difference.
    
    The problems happens not only for _errorWrapper, which did not have
    destructor, but also for _error and for _interface and _interface _did_
    have destructor. But the destructor of _interface was not virtual.
    
    -> Fix this by making _interface destructor virtual and by making sure
       that ~_interface(), and destructors of all descendent classes, are
       defined in cpp part instead of being implicitly defined at header.
    
       We also need to make sure the same is true for default constructor,
       because else the compiler will generate default constructor itself in
       every translation unit, and this will lead to duplicate typeinfo and
       vtable to be still emitted.
    
       Note that making destructor virtual is not necessary for _interface
       semantic correctness - there we already have decref virtual, and when
       the decref is called by ~refptr, it is called on the real object
       type, which invokes real destructor even without that being virtual. But
       making the dtor virtual will not harm and will take only a few cycles
       which is ok for runtime-introspectible interface type. Note that we
       still have destructors non-virtual for regular types directly
       derived from object.
    
    After the fix:
    
        ((1.venv) ) poco:~/pygolang$ nm -DC golang/_errors_test.cpython-312.so |grep 'typeinfo for'
        000000000004d768 V typeinfo for _MyWrapError
        000000000004d6e8 V typeinfo for _MyError
                         U typeinfo for golang::_errorWrapper
                         U typeinfo for golang::_error
        000000000004d720 V typeinfo for golang::object
                         U typeinfo for std::__ndk1::ios_base::failure
                         U typeinfo for std::bad_typeid
                         U typeinfo for std::range_error
                         U typeinfo for std::domain_error
                         U typeinfo for std::length_error
                         U typeinfo for std::out_of_range
                         U typeinfo for std::overflow_error
                         U typeinfo for std::underflow_error
                         U typeinfo for std::invalid_argument
                         U typeinfo for std::bad_array_new_length
                         U typeinfo for std::bad_cast
                         U typeinfo for std::bad_alloc
                         U typeinfo for std::exception
    
        ((1.venv) ) poco:~/pygolang$ nm -DC golang/runtime/liblibgolang.so |grep 'typeinfo for golang::'
        0000000000090b28 V typeinfo for golang::PanicError
        0000000000090b58 D typeinfo for golang::_interface
        0000000000090b80 D typeinfo for golang::_errorWrapper
        0000000000090b40 V typeinfo for golang::Bug
        00000000000922c0 V typeinfo for golang::fmt::_WrapError
        0000000000090b68 D typeinfo for golang::_error
        0000000000092250 V typeinfo for golang::errors::_TextError
        0000000000091748 V typeinfo for golang::object
        0000000000091dc8 V typeinfo for golang::context::_CancelCtx
        0000000000091d30 V typeinfo for golang::context::_Background
        0000000000092090 V typeinfo for golang::context::_TimeoutCtx
        0000000000091cb0 V typeinfo for golang::context::_BaseCtx
        0000000000091a50 D typeinfo for golang::context::_Context
        0000000000092018 V typeinfo for golang::context::_ValueCtx
        0000000000091710 D typeinfo for golang::internal::syscall::_Errno
        0000000000092118 V typeinfo for golang::context::_TimeoutCtx::_TimeoutCtx(double, double, golang::refptr<golang::context::_Context>)::{lambda()#1}
        0000000000091fa8 V typeinfo for golang::context::_BaseCtx::err()::{lambda()#1}
    
    i.e. typeinfo for _interface, _error and _errorWrapper turned to be
    D(defined) in libgolang.so and U(undefined) in _errors_test.so instead
    of previously being all V(weak) in both places.
    
    This fixes test_pyx_errors_unwrap_cpp and similar failures on android.
    
    Note that we still have typeinfo for object as V. This cannot be easily
    fixed without changing object to become polymorphic. It does not create
    problems in practice though.
    
    Note 2: types that are completely declared and defined in .cpp - e.g..
    PanicError, are ok to come as V and not having extra care in the sources
    because by the construct they appear only in one translation unit and so
    the symbols are emitted only once.
    
    /reviewed-by @jerome
    /reviewed-on !38
    6eca3654