https://gcc.gnu.org/bugzilla/show_bug.cgi?id=88782

            Bug ID: 88782
           Summary: Crash when mixing make_shared from gcc <= 8.2 with
                    make_shared from gcc >= 8.3
           Product: gcc
           Version: 9.0
            Status: UNCONFIRMED
          Severity: normal
          Priority: P3
         Component: libstdc++
          Assignee: unassigned at gcc dot gnu.org
          Reporter: romain.geissler at amadeus dot com
  Target Milestone: ---

Hi,

The change introduced in r266380 makes newer gcc >= 8.3 and gcc 9 sometimes
incompatible with object files (archive libraries) generated with gcc <= 8.2,
even when all the generated objects are using -frtti.

See this example where mixing an old library build with an old gcc 8 and a new
library build with a new gcc 8 result in the end in a segfault:

cat > A.h <<END_OF_FILE
class A
{
    public:
        class Constructor1 {};
        class Constructor2 {};

        A(A::Constructor1) {};
        A(A::Constructor2) {};
};
END_OF_FILE

cat > library1.cpp <<END_OF_FILE
#include "A.h"
#include <memory>

void f1()
{
    std::make_shared<A>(A::Constructor1());
}
END_OF_FILE

cat > library2.cpp <<END_OF_FILE
#include "A.h"
#include <memory>

void f2()
{
    std::make_shared<A>(A::Constructor2());
}
END_OF_FILE

cat > main.cpp <<END_OF_FILE
#include <memory>

extern void f1();
extern void f2();

int main()
{
    f1();
    f2();
}
END_OF_FILE



### Built like this: ###

old-g++-8 -g -o library1.o -c library1.cpp
ar cr library1.a library1.o

new-g++-8 -g -o library2.o -c library2.cpp
ar cr library2.a library2.o

new-g++-8 -g -o main.o -c main.cpp
new-g++-8 -o main main.o library1.a library2.a

(in my case, old-g++-8 is actually named
/remote/tools/Linux/2.6/1A/toolchain/x86_64-2.6.32-v4.0.40/bin/g++ and
new-g++-8 is actually named
/remote/tools/Linux/2.6/1A/toolchain/x86_64-2.6.32-v4.0.46/bin/g++)



### When you run it (with gdb): ###
(gdb) r
Starting program: /tmp/reproduce-gcc-make-shared/main

Program received signal SIGSEGV, Segmentation fault.
0x00000000004011bd in std::type_info::operator== (this=0x403200
<std::_Sp_make_shared_tag::_S_ti()::__tag>, __arg=...)
    at
/remote/tools/Linux/2.6/1A/toolchain/x86_64-2.6.32-v4.0.40/include/c++/8.2.1/typeinfo:123
123                   || (__name[0] != '*' &&
(gdb) bt
#0  0x00000000004011bd in std::type_info::operator== (this=0x403200
<std::_Sp_make_shared_tag::_S_ti()::__tag>, __arg=...)
    at
/remote/tools/Linux/2.6/1A/toolchain/x86_64-2.6.32-v4.0.40/include/c++/8.2.1/typeinfo:123
#1  0x0000000000401d6f in std::_Sp_counted_ptr_inplace<A, std::allocator<A>,
(__gnu_cxx::_Lock_policy)2>::_M_get_deleter (this=0x418c20, __ti=...)
    at
/remote/tools/Linux/2.6/1A/toolchain/x86_64-2.6.32-v4.0.40/include/c++/8.2.1/bits/shared_ptr_base.h:569
#2  0x000000000040176e in
std::__shared_count<(__gnu_cxx::_Lock_policy)2>::_M_get_deleter
(this=0x7fffffffc4e8, __ti=...)
    at
/remote/tools/Linux/2.6/1A/toolchain/x86_64-2.6.32-v4.0.40/include/c++/8.2.1/bits/shared_ptr_base.h:749
#3  0x0000000000401f8c in std::__shared_ptr<A,
(__gnu_cxx::_Lock_policy)2>::__shared_ptr<std::allocator<A>, A::Constructor2>
(this=0x7fffffffc4e0, __tag=..., __a=...)
    at
/remote/tools/Linux/2.6/1A/toolchain/x86_64-2.6.32-v4.0.46/include/c++/8.2.1/bits/shared_ptr_base.h:1328
#4  0x0000000000401f1d in std::shared_ptr<A>::shared_ptr<std::allocator<A>,
A::Constructor2> (this=0x7fffffffc4e0, __tag=..., __a=...)
    at
/remote/tools/Linux/2.6/1A/toolchain/x86_64-2.6.32-v4.0.46/include/c++/8.2.1/bits/shared_ptr.h:360
#5  0x0000000000401ee0 in std::allocate_shared<A, std::allocator<A>,
A::Constructor2> (__a=..., __args#0=...)
    at
/remote/tools/Linux/2.6/1A/toolchain/x86_64-2.6.32-v4.0.46/include/c++/8.2.1/bits/shared_ptr.h:707
#6  0x0000000000401e68 in std::make_shared<A, A::Constructor2> (__args#0=...)
at
/remote/tools/Linux/2.6/1A/toolchain/x86_64-2.6.32-v4.0.46/include/c++/8.2.1/bits/shared_ptr.h:723
#7  0x0000000000401e00 in f2 () at library2.cpp:6
#8  0x0000000000401152 in main () at main.cpp:9


The reason for that is that the symbols and the vtable for the class
std::_Sp_counted_ptr_inplace comes from the first object that defines it, which
in this case is library1 built with the old gcc behavior. This class is common
both when you call make_shared with constructor 1 or constructor 2, and this is
where _M_get_deleter does it's check for the typeid(_Sp_make_shared_tag).

On the other side, there are two different callers of _M_get_deleter. One with
the old typeid(__tag) tag in the library 1 (when instantiating the call to
constructor 1) and one with the new _Sp_make_shared_tag::_S_ti() tag in the
library 2 (when instantiating the call to constructor 2). Because the linker
picked the "wrong" old _M_get_deleter, the second call ends it in seg fault.

Do we foresee a way to avoid rebuilding all libraries that were built with gcc
<= 8.2 when mixing them with libraries build with gcc >= 8.3 ? I am thinking
about doing something like this:

--- bits/shared_ptr_base.h
+++ bits/shared_ptr_base.h
@@ -509,8 +509,12 @@
     static const type_info&
     _S_ti() noexcept _GLIBCXX_VISIBILITY(default)
     {
+#if __cpp_rtti
+      return typeid(_Sp_make_shared_tag);
+#else 
       alignas(type_info) static constexpr char __tag[sizeof(type_info)] = { };
       return reinterpret_cast<const type_info&>(__tag);
+#endif
     }  
   };

@@ -567,12 +571,6 @@
        // as a real type_info object.
        if (&__ti == &_Sp_make_shared_tag::_S_ti())
          return const_cast<typename remove_cv<_Tp>::type*>(_M_ptr());
-#if __cpp_rtti
-       // Callers compiled with old libstdc++ headers and RTTI enabled
-       // might pass this instead:
-       else if (__ti == typeid(_Sp_make_shared_tag))  
-         return const_cast<typename remove_cv<_Tp>::type*>(_M_ptr());
-#endif
        return nullptr;
       }


but that may be quite wrong too (I can just confirm it works on my reduced test
case).

Cheers,
Romain

Reply via email to