[cfe-dev] [RFC] Suppress C++ static destructor registration

David Blaikie via cfe-dev cfe-dev at lists.llvm.org
Tue Jul 19 13:33:53 PDT 2016


On Tue, Jul 19, 2016 at 10:34 AM Eric Fiselier <eric at efcs.ca> wrote:

> On Tue, Jul 19, 2016 at 9:13 AM, David Blaikie via cfe-dev <
> cfe-dev at lists.llvm.org> wrote:
>
>>
>>
>> On Mon, Jul 18, 2016 at 3:13 PM Greg Parker via cfe-dev <
>> cfe-dev at lists.llvm.org> wrote:
>>
>>> On Jul 18, 2016, at 2:08 PM, Richard Smith via cfe-dev <
>>> cfe-dev at lists.llvm.org> wrote:
>>>
>>> On Mon, Jul 18, 2016 at 1:39 PM, Bruno Cardoso Lopes via cfe-dev <
>>> cfe-dev at lists.llvm.org> wrote:
>>>
>>>> Hi,
>>>>
>>>> C++ static destructors can be problematic in multi-threaded
>>>> environment. Some of the issues users often complain about include:
>>>> 1. Teardown ordering: crashes when one thread is exiting the process
>>>> and calling destructors while another thread is still running and
>>>> accessing the destructing variables
>>>> 2. Shared code that is compiled both as an application and as a
>>>> library. When library mode is chosen, goto (1).
>>>> 3. Some projects currently override __cxa_atexit to avoid the behavior
>>>> in question.
>>>>
>>>> To get around that, I propose we add a compiler option (e.g.
>>>> -fno-cxx-static-destructors) to allow clang to suppress destructor
>>>> registration (currently done via __cxa_atexit, atexit):
>>>> https://reviews.llvm.org/D22474
>>>>
>>>> I'm opening this discussion here on cfe-dev to get some feedback on the
>>>> matter
>>>>
>>>> One can argue that dealing with C++ static destructors in
>>>> multi-threaded environment is solely the responsibility of the
>>>> developer, however since (AFAIK) we don't have any standard guaranteed
>>>> semantic for "global destruction vs. threads", it seems fair to me
>>>> that we could give developers some option.
>>>
>>>
>>> They already have options. They can use std::quick_exit, which was added
>>> specifically to address this problem, if they don't want destructors to be
>>> run at all.
>>>
>>>
>>> std::quick_exit() does not help. The destructor is in a library. The
>>> library author has no control over how other code in the process calls
>>> exit(). The authors of the app and other libraries are unaware that exit()
>>> is dangerous.
>>>
>>>
>>> There are standard techniques to avoid destructors being run for
>>> specific objects:
>>>
>>>   template<typename T> union not_destroyed {
>>>     T value;
>>>     template<typename ...U> constexpr not_destroyed(U &&...u) :
>>> value(std::forward<U>(u)...) {}
>>>     ~not_destroyed() {}
>>>   };
>>>   not_destroyed<std::string> my_str("foo"); // technically has object
>>> lifetime issues
>>>
>>>
>>> This is fragile. It's easy to accidentally define a static variable that
>>> does not have this template, thereby breaking exit() again.
>>>
>>
>>> -Werror=exit-time-destructors complains about ~not_destroyed(), so it
>>> can't help. Adding #pragma diagnostic around every use of not_destroyed
>>> would fix that and not be fragile, but it would be awfully ugly.
>>>
>>
>> I think not_destroyed could be written differently so as not to have a
>> non-trivial dtor. Make it more like std::optional (wrapping an object in a
>> pointer-like API) & just has a byte buffer member and no dtor declared.
>> Then it'd be trivially destructible and have no global dtor and be
>> -Wexit-time-destructors clean.
>>
>
>
> Clang seems to have no problem removing the call to the empty dtor at -O1.
>

Making correctness dependent on optimizations is a bit tricky (though since
the dtor does nothing, it's not actually a correctness issue - it's a
global dtor that can't really race with anything, etc). But that's hard to
detect in general - so we settle for a fairly blunt/defensible position of
"not actually executing any code".


> Using a byte buffer can have other problems, like losing the possibility
> of constant initialization.
>

Sure enough.


>
>
>>
>>
>>>
>>> A post-link test for references to symbol __cxa_atexit might help, but
>>> only if there are no intentional static destructors anywhere and only for
>>> optimized builds.
>>>
>>>
>>>   std::string &&s = *new std::string("foo");
>>>
>>>
>>> This didn't compile.
>>>
>>>     test.cxx:12:26: error: rvalue reference to type 'basic_string<[3 *
>>> ...]>' cannot bind to lvalue of type 'basic_string<[3 * ...]>'
>>>
>>>
>>> --
>>> Greg Parker     gparker at apple.com     Runtime Wrangler
>>>
>>>
>>> _______________________________________________
>>> cfe-dev mailing list
>>> cfe-dev at lists.llvm.org
>>> http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev
>>>
>>
>> _______________________________________________
>> cfe-dev mailing list
>> cfe-dev at lists.llvm.org
>> http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-dev
>>
>>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.llvm.org/pipermail/cfe-dev/attachments/20160719/8ea8fed1/attachment.html>


More information about the cfe-dev mailing list