cat > main.cc << EOT #include <memory> extern int f(); // For the purposes of this demo, force a non-weak definition of // _Sp_counted_ptr_inplace::_M_get_deleter(const type_info&) to be emitted // in this translation unit: template class std::_Sp_counted_ptr_inplace<int, std::allocator<int>, std::_S_atomic>; int main() { return f(); } EOT cat > sp.cc << EOT #include <memory> int f() { return *std::make_shared<int>(); } EOT g++ -fno-rtti main.cc -c -g g++ -frtti sp.cc -c -g g++ main.o sp.o ./a.out This crashes: Segmentation fault (core dumped) The problem is that the definition of _Sp_counter_ptr_inplace::_M_get_deleter that the linker chooses was compiled with -fno-rtti but the caller was compiled with -frtti. The caller passes typeid(_Sp_make_shared_tag) which does not match the expected value in _M_get_deleter, so a null pointer is returned. That leaves the new shared_ptr holding a null pointer, so dereferencing it in f() crashes. This crash happens for all versions. A nastier failure happens with gcc-8 and trunk if we swap which object is built with RTTI and which without: g++ -frtti main.cc -c -g g++ -fno-rtti sp.cc -c -g g++ main.o sp.o ./a.out This crashes earlier, during construction of the shared_ptr: Program received signal SIGSEGV, Segmentation fault. 0x00000000004008cb in std::type_info::operator== (this=0x401700 <std::_Sp_make_shared_tag::_S_ti()::__tag>, __arg=...) at /home/jwakely/gcc/8/include/c++/8.2.1/typeinfo:123 (gdb) bt #0 0x00000000004008cb in std::type_info::operator== (this=0x401700 <std::_Sp_make_shared_tag::_S_ti()::__tag>, __arg=...) at /home/jwakely/gcc/8/include/c++/8.2.1/typeinfo:123 #1 0x0000000000400a31 in std::_Sp_counted_ptr_inplace<int, std::allocator<int>, (__gnu_cxx::_Lock_policy)2>::_M_get_deleter (this=0x615e70, type_info node=...) at /home/jwakely/gcc/8/include/c++/8.2.1/bits/shared_ptr_base.h:569 #2 0x0000000000401264 in std::__shared_count<(__gnu_cxx::_Lock_policy)2>::_M_get_deleter (this=0x7fffffffd858, type_info node=...) at /home/jwakely/gcc/8/include/c++/8.2.1/bits/shared_ptr_base.h:749 #3 0x00000000004010ba in std::__shared_ptr<int, (__gnu_cxx::_Lock_policy)2>::__shared_ptr<std::allocator<int>>(std::_Sp_make_shared_tag, std::allocator<int> const&) (this=0x7fffffffd850, __tag=..., __a=...) at /home/jwakely/gcc/8/include/c++/8.2.1/bits/shared_ptr_base.h:1329 #4 0x0000000000401055 in std::shared_ptr<int>::shared_ptr<std::allocator<int>>(std::_Sp_make_shared_tag, std::allocator<int> const&) (this=0x7fffffffd850, __tag=..., __a=...) at /home/jwakely/gcc/8/include/c++/8.2.1/bits/shared_ptr.h:360 #5 0x0000000000400f8a in std::allocate_shared<int, std::allocator<int>>(std::allocator<int> const&) (__a=...) at /home/jwakely/gcc/8/include/c++/8.2.1/bits/shared_ptr.h:707 #6 0x0000000000400ed5 in std::make_shared<int> () at /home/jwakely/gcc/8/include/c++/8.2.1/bits/shared_ptr.h:723 #7 0x0000000000400d80 in f () at sp.cc:5 #8 0x000000000040089b in main () at main.cc:13 This is due to the fix for PR 80285. The __shared_ptr constructor is built without RTTI so calls _M_get_deleter(_Sp_make_shared_tag::_S_ti()). The definition of _M_get_deleter is built with RTTI so expects a real typeinfo object. When accessing the fake _S_ti() typeinfo object we crash, because it's an invalid reference that isn't bound to a real typeinfo.
A possible (partial) fix would be: --- a/libstdc++-v3/include/bits/shared_ptr_base.h +++ b/libstdc++-v3/include/bits/shared_ptr_base.h @@ -500,7 +500,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION struct _Sp_make_shared_tag { -#if !__cpp_rtti private: template<typename _Tp, _Lock_policy _Lp> friend class __shared_ptr; @@ -513,7 +512,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION alignas(type_info) static constexpr _Sp_make_shared_tag __tag; return reinterpret_cast<const type_info&>(__tag); } -#endif }; template<typename _Tp, typename _Alloc, _Lock_policy _Lp> @@ -562,16 +560,19 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION this->~_Sp_counted_ptr_inplace(); } - // Sneaky trick so __shared_ptr can get the managed pointer + // Sneaky trick so __shared_ptr can get the managed pointer. virtual void* - _M_get_deleter(const std::type_info& __ti) noexcept + _M_get_deleter(const std::type_info& __ti) noexcept override { -#if __cpp_rtti - if (__ti == typeid(_Sp_make_shared_tag)) -#else + // Check for the fake type_info first, so we don't try to access it + // as a real type_info object. if (&__ti == &_Sp_make_shared_tag::_S_ti()) -#endif return const_cast<typename remove_cv<_Tp>::type*>(_M_ptr()); +#if __cpp_rtti + // Callers compiled with RTTI enabled pass this instead: + else if (__ti == typeid(_Sp_make_shared_tag)) + return const_cast<typename remove_cv<_Tp>::type*>(_M_ptr()); +#endif return nullptr; } @@ -1323,11 +1324,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION { // _M_ptr needs to point to the newly constructed object. // This relies on _Sp_counted_ptr_inplace::_M_get_deleter. -#if __cpp_rtti - void* __p = _M_refcount._M_get_deleter(typeid(__tag)); -#else void* __p = _M_refcount._M_get_deleter(_Sp_make_shared_tag::_S_ti()); -#endif _M_ptr = static_cast<_Tp*>(__p); _M_enable_shared_from_this_with(_M_ptr); } This always passes the fake _S_ti() even when rtti is enabled. Inside _M_get_deleter we check for the fake tag first, and so avoid trying to dereference it as a real typeinfo object. Old code already built with rtti could still call the function with typeid(_Sp_make_shared_tag) and so we still need to check for that (which will still fail and return a null pointer if the definition of _M_get_deleter is built without rtti). A more complete fix would be to avoid using the virtual _M_get_deleter backchannel entirely, as suggested in PR 87514 (that would need to be done in addition to the patch above, so that existing callers of the virtual function will still be able to use it).
Related to bug 43105
(In reply to Jonathan Wakely from comment #1) > A possible (partial) fix would be: > > --- a/libstdc++-v3/include/bits/shared_ptr_base.h > +++ b/libstdc++-v3/include/bits/shared_ptr_base.h I can confirm that this also fixes our actual "in the wild" issue. I would certainly appreciate this being included in 8.3. Thank you for the reproducer and the suggested patch, Jonathan!
(In reply to Jonathan Wakely from comment #1) > @@ -513,7 +512,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > alignas(type_info) static constexpr _Sp_make_shared_tag __tag; This needs to be fixed to have the right size as well as alignment, so the reference binds to suitable storage (as per core DR 453).
Author: redi Date: Thu Nov 22 13:42:39 2018 New Revision: 266376 URL: https://gcc.gnu.org/viewcvs?rev=266376&root=gcc&view=rev Log: PR libstdc++/87520 Always pass type-punned type_info reference The implementations of std::make_shared for -frtti and -fno-rtti are not compatible, because they pass different arguments to _Sp_counted_ptr_inplace::_M_get_deleter and so can't interoperate. Either the argument doesn't match the expected value, and so the shared_ptr::_M_ptr member is never set, or the type-punned reference is treated as a real std::type_info object and gets dereferenced. This patch removes the differences between -frtti and -fno-rtti, so that typeid is never used, and the type-punned reference is used in both cases. For backwards compatibility with existing code that passes typeid(_Sp_make_shared_tag) that still needs to be handled, but only after checking that the argument is not the type-punned reference (so it's safe to treat as a real std::type_info object). The reference is bound to an object of literal type, so that it doesn't need a guard variable to make its initialization thread-safe. This patch also fixes 87520 by ensuring that the type-punned reference is bound to "a region of storage of suitable size and alignment to contain an object of the reference's type" (as per the proposed resolution of Core DR 453). If all objects are built with the fixed version of GCC then -frtti and -fno-rtti can be mixed freely and std::make_shared will work correctly. If some objects are built with unfixed GCC versions then problems can still arise, depending on which template instantiations are kept by the linker. PR libstdc++/85930 PR libstdc++/87520 * include/bits/shared_ptr_base.h (_Sp_make_shared_tag::_S_ti) [__cpp_rtti]: Define even when RTTI is enabled. Use array of sizeof(type_info) so that type-punned reference binds to an object of the correct size as well as correct alignment. (_Sp_counted_ptr_inplace::_M_get_deleter) [__cpp_rtti]: Check for _S_ti() reference even when RTTI is enabled. (__shared_ptr(_Sp_make_shared_tag, const _Alloc&, _Args&&...)) [__cpp_rtti]: Pass _S_ti() instead of typeid(_Sp_make_shared_tag). Modified: trunk/libstdc++-v3/ChangeLog trunk/libstdc++-v3/include/bits/shared_ptr_base.h
Fixed on trunk so far.
Author: redi Date: Thu Nov 22 15:02:46 2018 New Revision: 266380 URL: https://gcc.gnu.org/viewcvs?rev=266380&root=gcc&view=rev Log: PR libstdc++/87520 Always pass type-punned type_info reference The implementations of std::make_shared for -frtti and -fno-rtti are not compatible, because they pass different arguments to _Sp_counted_ptr_inplace::_M_get_deleter and so can't interoperate. Either the argument doesn't match the expected value, and so the shared_ptr::_M_ptr member is never set, or the type-punned reference is treated as a real std::type_info object and gets dereferenced. This patch removes the differences between -frtti and -fno-rtti, so that typeid is never used, and the type-punned reference is used in both cases. For backwards compatibility with existing code that passes typeid(_Sp_make_shared_tag) that still needs to be handled, but only after checking that the argument is not the type-punned reference (so it's safe to treat as a real std::type_info object). The reference is bound to an object of literal type, so that it doesn't need a guard variable to make its initialization thread-safe. This patch also fixes 87520 by ensuring that the type-punned reference is bound to "a region of storage of suitable size and alignment to contain an object of the reference's type" (as per the proposed resolution of Core DR 453). If all objects are built with the fixed version of GCC then -frtti and -fno-rtti can be mixed freely and std::make_shared will work correctly. If some objects are built with unfixed GCC versions then problems can still arise, depending on which template instantiations are kept by the linker. PR libstdc++/85930 PR libstdc++/87520 * include/bits/shared_ptr_base.h (_Sp_make_shared_tag::_S_ti) [__cpp_rtti]: Define even when RTTI is enabled. Use array of sizeof(type_info) so that type-punned reference binds to an object of the correct size as well as correct alignment. (_Sp_counted_ptr_inplace::_M_get_deleter) [__cpp_rtti]: Check for _S_ti() reference even when RTTI is enabled. (__shared_ptr(_Sp_make_shared_tag, const _Alloc&, _Args&&...)) [__cpp_rtti]: Pass _S_ti() instead of typeid(_Sp_make_shared_tag). Modified: branches/gcc-8-branch/libstdc++-v3/ChangeLog branches/gcc-8-branch/libstdc++-v3/include/bits/shared_ptr_base.h
Fixed for GCC 8.3
*** Bug 88177 has been marked as a duplicate of this bug. ***
Author: redi Date: Fri Jan 18 21:28:48 2019 New Revision: 268086 URL: https://gcc.gnu.org/viewcvs?rev=268086&root=gcc&view=rev Log: PR libstdc++/88782 avoid ODR problems in std::make_shared The old version of _Sp_counted_ptr_inplace::_M_get_deleter (up to GCC 8.2.0) expects to be passed a real std::typeinfo object, so mixing that with the new definition of the __shared_ptr constructor (which always passes the fake tag) leads to accessing the fake object as a real std::typeinfo. Instead of trying to make it safe to mix the old and new definitions, just stop using that function. By passing a reference to __shared_ptr::_M_ptr to the __shared_count constructor it can be set directly, without needing to obtain the pointer via the _M_get_deleter back-channel. This avoids a virtual dispatch (which fixes PR 87514). This means that code built against new libstdc++ headers doesn't use _M_get_deleter at all, and so make_shared works the same whether RTTI is enabled or not. Also change _M_get_deleter so that it checks for a real type_info object even when RTTI is disabled, by calling a library function. Unless libstdc++ itself is built without RTTI that library function will be able to test if it's the right type_info. This means the new definition of _M_get_deleter can handle both the fake type_info tag and a real type_info object, even if built without RTTI. If linking to objects built against older versions of libstdc++ then if all objects use -frtti or all use -fno-rtti, then the caller of _M_get_deleter and the definition of _M_get_deleter will be consistent and it will work. If mixing -frtti with -fno-rtti it can still fail if the linker picks an old definition of _M_get_deleter and an old __shared_ptr constructor that are incompatible. In that some or all objects might need to be recompiled. PR libstdc++/87514 PR libstdc++/87520 PR libstdc++/88782 * config/abi/pre/gnu.ver (GLIBCXX_3.4.26): Export new symbol. * include/bits/shared_ptr.h (shared_ptr(_Sp_make_shared_tag, const Alloc&, Args&&...)) (allocate_shared): Change to use new tag type. * include/bits/shared_ptr_base.h (_Sp_make_shared_tag::_S_eq): Declare new member function. (_Sp_alloc_shared_tag): Define new type. (_Sp_counted_ptr_inplace): Declare __shared_count<_Lp> as a friend. (_Sp_counted_ptr_inplace::_M_get_deleter) [!__cpp_rtti]: Use _Sp_make_shared_tag::_S_eq to check type_info. (__shared_count(Ptr, Deleter),__shared_count(Ptr, Deleter, Alloc)): Constrain to prevent being called with _Sp_alloc_shared_tag. (__shared_count(_Sp_make_shared_tag, const _Alloc&, Args&&...)): Replace constructor with ... (__shared_count(Tp*&, _Sp_alloc_shared_tag<_Alloc>, Args&&...)): Use reference parameter so address of the new object can be returned to the caller. Obtain the allocator from the tag type. (__shared_ptr(_Sp_make_shared_tag, const Alloc&, Args&&...)): Replace constructor with ... (__shared_ptr(_Sp_alloc_shared_tag<Alloc>, Args&&...)): Pass _M_ptr to the __shared_count constructor. (__allocate_shared): Change to use new tag type. * src/c++11/shared_ptr.cc (_Sp_make_shared_tag::_S_eq): Define. Modified: trunk/libstdc++-v3/ChangeLog trunk/libstdc++-v3/config/abi/pre/gnu.ver trunk/libstdc++-v3/include/bits/shared_ptr.h trunk/libstdc++-v3/include/bits/shared_ptr_base.h trunk/libstdc++-v3/src/c++11/shared_ptr.cc
Author: redi Date: Mon Jan 21 13:16:25 2019 New Revision: 268114 URL: https://gcc.gnu.org/viewcvs?rev=268114&root=gcc&view=rev Log: PR libstdc++/88782 avoid ODR problems in std::make_shared The old version of _Sp_counted_ptr_inplace::_M_get_deleter (up to GCC 8.2.0) expects to be passed a real std::typeinfo object, so mixing that with the new definition of the __shared_ptr constructor (which always passes the fake tag) leads to accessing the fake object as a real std::typeinfo. Instead of trying to make it safe to mix the old and new definitions, just stop using that function. By passing a reference to __shared_ptr::_M_ptr to the __shared_count constructor it can be set directly, without needing to obtain the pointer via the _M_get_deleter back-channel. This avoids a virtual dispatch (which fixes PR 87514). This means that code built against new libstdc++ headers doesn't use _M_get_deleter at all, and so make_shared works the same whether RTTI is enabled or not. Unlike on trunk, where _M_get_deleter calls a new library function that can detect the real type_info object even when RTTI is disabled, this commit for gcc-8-branch cannot add a new symbol to the shared library. That means _M_get_deleter still returns null if compiled without RTTI and it gets called from a translation unit that was compiled with RTTI. If linking to objects built against older versions of libstdc++ then if all objects use -frtti or all use -fno-rtti, then the caller of _M_get_deleter and the definition of _M_get_deleter will be consistent and it will work. If mixing -frtti with -fno-rtti it can still fail if the linker picks an old definition of _M_get_deleter and an old __shared_ptr constructor that are incompatible. In that case some or all objects might need to be recompiled (or just relinked in a different order). Backport from mainline 2019-01-18 Jonathan Wakely <jwakely@redhat.com> PR libstdc++/87514 PR libstdc++/87520 PR libstdc++/88782 * config/abi/pre/gnu.ver (GLIBCXX_3.4.26): Export new symbol. * include/bits/shared_ptr.h (shared_ptr(_Sp_make_shared_tag, const Alloc&, Args&&...)) (allocate_shared): Change to use new tag type. * include/bits/shared_ptr_base.h (_Sp_make_shared_tag::_S_eq): Declare new member function. (_Sp_alloc_shared_tag): Define new type. (_Sp_counted_ptr_inplace): Declare __shared_count<_Lp> as a friend. (_Sp_counted_ptr_inplace::_M_get_deleter) [!__cpp_rtti]: Use _Sp_make_shared_tag::_S_eq to check type_info. (__shared_count(Ptr, Deleter),__shared_count(Ptr, Deleter, Alloc)): Constrain to prevent being called with _Sp_alloc_shared_tag. (__shared_count(_Sp_make_shared_tag, const _Alloc&, Args&&...)): Replace constructor with ... (__shared_count(Tp*&, _Sp_alloc_shared_tag<_Alloc>, Args&&...)): Use reference parameter so address of the new object can be returned to the caller. Obtain the allocator from the tag type. (__shared_ptr(_Sp_make_shared_tag, const Alloc&, Args&&...)): Replace constructor with ... (__shared_ptr(_Sp_alloc_shared_tag<Alloc>, Args&&...)): Pass _M_ptr to the __shared_count constructor. (__allocate_shared): Change to use new tag type. * src/c++11/shared_ptr.cc (_Sp_make_shared_tag::_S_eq): Define. Modified: branches/gcc-8-branch/libstdc++-v3/ChangeLog branches/gcc-8-branch/libstdc++-v3/include/bits/shared_ptr.h branches/gcc-8-branch/libstdc++-v3/include/bits/shared_ptr_base.h
A more reliable fix has been committed for 8.3 and 9.1