[cfe-dev] [LibC++] Bug in implementation of 'std::shared_ptr'

Martin J. O'Riordan via cfe-dev cfe-dev at lists.llvm.org
Thu Apr 27 09:27:13 PDT 2017


I have a test that has been failing for a long time, and finally got around
to investigating it.  The test is really simple:

 

#include <iostream>

#include <memory>

 

struct Test {

  int n;

  Test(int x) : n(x) { std::cerr << "Creating " << n << std::endl; }

  ~Test() { std::cerr << "Deleting " << n << std::endl; }

};

 

int main () {

  std::shared_ptr<Test> x(std::make_shared<Test>(42));

  x.reset();

  std::cerr << "Completed " << std::endl;

}

 

but it crashes immediately after printing the message from the destructor,
and before the message in 'main'.

 

After investigating, I found that the test works perfectly with RTTI
enabled, but crashes if it is disabled which puzzled me.  My LibC++ library
is built with RTTI enabled as instructed on 'libcxx.llvm.org' which says
that the library must be built with RTTI enabled, though it may be used with
RTTI disabled.

 

What I found was that the 'vtable' for the 'shared_ptr' specialisation is
different depending on whether RTTI is enabled or disabled.  With RTTI
disabled it is:

 

_ZTVNSt3__120__shared_ptr_emplaceI4TestNS_9allocatorIS1_EEEE:

    .word      0

    .word      0

    .word      _ZNSt3__120__shared_ptr_emplaceI4TestNS_9allocatorIS1_EEED1Ev

    .word      _ZNSt3__120__shared_ptr_emplaceI4TestNS_9allocatorIS1_EEED0Ev

    .word
_ZNSt3__120__shared_ptr_emplaceI4TestNS_9allocatorIS1_EEE16__on_zero_sharedE
v

    .word
_ZNSt3__120__shared_ptr_emplaceI4TestNS_9allocatorIS1_EEE21__on_zero_shared_
weakEv

 

but with RTTI enabled it is:

 

_ZTVNSt3__120__shared_ptr_emplaceI4TestNS_9allocatorIS1_EEEE:

    .word      0

    .word      _ZTINSt3__120__shared_ptr_emplaceI4TestNS_9allocatorIS1_EEEE

    .word      _ZNSt3__120__shared_ptr_emplaceI4TestNS_9allocatorIS1_EEED1Ev

    .word      _ZNSt3__120__shared_ptr_emplaceI4TestNS_9allocatorIS1_EEED0Ev

    .word
_ZNSt3__120__shared_ptr_emplaceI4TestNS_9allocatorIS1_EEE16__on_zero_sharedE
v

    .word      _ZNKSt3__119__shared_weak_count13__get_deleterERKSt9type_info

    .word
_ZNSt3__120__shared_ptr_emplaceI4TestNS_9allocatorIS1_EEE21__on_zero_shared_
weakEv

 

In the library, the implementation (in 'memory.cpp' compiler with '-frtti')
is attempting to call the function '__on_zero_shared_weak', but using offset
24, and the 'vtable' emitted in the test case (compiled with '-fno-rtti')
has this function at offset 20.

 

This is caused by the following lines in '<memory>':

 

    // Define the function out only if we build static libc++ without RTTI.

    // Otherwise we may break clients who need to compile their projects
with

    // -fno-rtti and yet link against a libc++.dylib compiled

    // without -fno-rtti.

#if !defined(_LIBCPP_NO_RTTI) || !defined(_LIBCPP_BUILD_STATIC)

    virtual const void* __get_deleter(const type_info&) const _NOEXCEPT;

#endif

 

and because the function is virtual, the layout of the 'vtable' is different
between RTTI enabled and disabled (we are building a static library, so
'_LIBCPP_BUILD_STATIC' is true).

 

There are a couple of places in '<memory>' and 'memory.cpp' where this
happens (always with '__get_deleter'), but after sanity checking the other
headers, I see that '<functional>' and '<__functional_03>' also have similar
issues where the layout of the 'vtable' is different depending on whether
RTTI is enabled or not; though I don't have any tests which show this.

 

I don't know what the best fix is for this because it was clearly introduced
to address some issue with dynamic libraries, but locally I have decided to
always define '__get_deleter', and make its implementation return 'nullptr'
when RTTI is disabled.  This preserves the 'vtable' layout independent of
RTTI.

 

Thanks,

 

            MartinO

 

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.llvm.org/pipermail/cfe-dev/attachments/20170427/d01505ae/attachment.html>


More information about the cfe-dev mailing list