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

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

#include

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 x(std::make_shared(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_sharedEv

.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_sharedEv

.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 ‘’:

// 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 ‘’ and ‘memory.cpp’ where this happens (always with ‘__get_deleter’), but after sanity checking the other headers, I see that ‘’ 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

Martin,

How the '_LIBCPP_BUILD_STATIC' define got set in your case? It should
only appear when building libc++ itself.

Hi Anton,

Sorry, I did 'Reply' instead of 'Reply All'

Actually, I should've added that the '__shared_ptr_pointer::__get_deleter' is also similarly affected, but is not qualified by '_LIBCPP_BUILD_STATIC', and that the same is true for the 'functional implementation although I have no test cases yet which illustrate this as a problem.

Thanks,

  MartinO

Martin,

You're seeing the problem with __shared_weak_count::__get_deleter(),
however, it depends on both _LIBCPP_BUILD_STATIC and -fno-rtti
settings. The former could only be set when building libc++ itself and
not the user applications.

So, how the things appeared to be broken in your case provided that
you built libc++ with RTTI enabled?

There should be no issues wrt __shared_ptr_pointer::__get_deleter
because it's not exported from libc++.a. Same for <functional> stuff.

Thanks Anton,

Yes I am building the library, with '-frtti', but I was running the test with '-fno-rtti'. Problem was that I was passing '_LIBCPP_BUILD_STATIC' when building the library and running the test. Despite the name, I had not copped this was a library build-time only option (the others are '_LIBCPP_BUILDING_*' and thought it was for static library usage.

  MartinO