Quuxplusone / LLVMBugzillaTest

0 stars 0 forks source link

ios_base::Init() gets called after user defined static constructor, causing a crash #37887

Open Quuxplusone opened 6 years ago

Quuxplusone commented 6 years ago
Bugzilla Link PR38914
Status NEW
Importance P normal
Reported by Dudu Arbel (duduarbel@gmail.com)
Reported on 2018-09-12 07:03:24 -0700
Last modified on 2018-10-11 10:42:05 -0700
Version 6.0
Hardware PC Linux
CC cbcode@gmail.com, dblaikie@gmail.com, dehuan@motorola.com, dgregor@apple.com, dimitry@andric.com, llvm-bugs@lists.llvm.org, mclow.lists@gmail.com, oliver@uptheinter.net, richard-llvm@metafoo.co.uk
Fixed by commit(s)
Attachments
Blocks
Blocked by
See also
This program crashes before starting.
on g++ - it runs as expected.
it seems like a bug:

#include <iostream>

struct InnerCls
{
    InnerCls() { std::cout<<__FUNCTION__<<std::endl;}
    ~InnerCls() { std::cout<<__FUNCTION__<<std::endl;}
    void foo() {std::cout<<__FUNCTION__<<std::endl;}
};

template <class T>
class Cls
{
public:

    Cls() { innerCls.foo(); }
    static InnerCls innerCls;

};

template<class T>
InnerCls Cls<T>::innerCls ;

int main()
{
    Cls<int> x;
}
Quuxplusone commented 6 years ago
I can reproduce this on both Fedora 28 and Ubuntu 18.04, but not on FreeBSD.
It seems to be because clang emits the initialization of InnerCls into the
.init_array segment *before* the GLOBAL iostream initializer, like gcc does.

E.g. clang 6.0.1 gives:

        .type   __cxx_global_var_init.1,@function
__cxx_global_var_init.1:                # @__cxx_global_var_init.1
        .cfi_startproc
# %bb.0:
        pushq   %rbx
        .cfi_def_cfa_offset 16
        .cfi_offset %rbx, -16
        cmpb    $0, guard variable for Cls<int>::innerCls(%rip)
        je      .LBB1_1
# %bb.6:
        popq    %rbx
        retq
.LBB1_1:
        movl    std::cout, %edi
        movl    $.L__FUNCTION__._ZN8InnerClsC2Ev, %esi
        movl    $8, %edx
        callq   std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::
char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char
const*, long)
[...]
        .section        .text.startup,"ax",@progbits
        .p2align        4, 0x90         # -- Begin function _GLOBAL__sub_I_pr38914_1.cpp
        .type   _GLOBAL__sub_I_pr38914_1.cpp,@function
_GLOBAL__sub_I_pr38914_1.cpp:           # @_GLOBAL__sub_I_pr38914_1.cpp
        .cfi_startproc
# %bb.0:
        pushq   %rax
        .cfi_def_cfa_offset 16
        movl    std::__ioinit, %edi
        callq   std::ios_base::Init::Init()
        movl    std::ios_base::Init::~Init(), %edi
        movl    std::__ioinit, %esi
        movl    $__dso_handle, %edx
        popq    %rax
        jmp     __cxa_atexit            # TAILCALL
[...]
        .section        .init_array,"aGw",@init_array,Cls<int>::innerCls,comdat
        .p2align        3
        .quad   __cxx_global_var_init.1
        .section        .init_array,"aw",@init_array
        .p2align        3
        .quad   _GLOBAL__sub_I_pr38914_1.cpp

while gcc 8.1.1 gives:

        .type   _GLOBAL__sub_I_main, @function
_GLOBAL__sub_I_main:
.LFB2042:
        .cfi_startproc
        subq    $8, %rsp
        .cfi_def_cfa_offset 16
        movl    std::__ioinit, %edi
        call    std::ios_base::Init::Init()
        movl    $__dso_handle, %edx
        movl    std::__ioinit, %esi
        movl    std::ios_base::Init::~Init(), %edi
        call    __cxa_atexit
        cmpb    $0, guard variable for Cls<int>::innerCls(%rip)
        je      .L16
        addq    $8, %rsp
        .cfi_remember_state
        .cfi_def_cfa_offset 8
        ret
.L16:
        .cfi_restore_state
        movl    $8, %edx
        movl    InnerCls::InnerCls()::__FUNCTION__, %esi
        movl    std::cout, %edi
        movb    $1, guard variable for Cls<int>::innerCls(%rip)
        call    std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long)
        movl    std::cout, %edi
        call    std::basic_ostream<char, std::char_traits<char> >& std::endl<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&)
        movl    $__dso_handle, %edx
        movl    Cls<int>::innerCls, %esi
        popq    %rax
        .cfi_def_cfa_offset 8
        movl    InnerCls::~InnerCls(), %edi
        jmp     __cxa_atexit
        .cfi_endproc
[...]
        .size   _GLOBAL__sub_I_main, .-_GLOBAL__sub_I_main
        .section        .init_array,"aw"
        .align 8
        .quad   _GLOBAL__sub_I_main

Clearly an issue with clang, since <iostream> was included properly, and thus
std::ios_base::Init::Init() should be called before any static constructors in
the main .cpp file.
Quuxplusone commented 6 years ago
(In reply to Dimitry Andric from comment #1)
> I can reproduce this on both Fedora 28 and Ubuntu 18.04, but not on FreeBSD.
> It seems to be because clang emits the initialization of InnerCls into the
> .init_array segment *before* the GLOBAL iostream initializer, like gcc does.
>

I can NOT reproduce this on Mac OS X 10.11 with either "Apple LLVM version
8.0.0" or clang ToT (clang version 8.0.0 (trunk 341841))
Quuxplusone commented 6 years ago
(In reply to Marshall Clow (home) from comment #2)
> (In reply to Dimitry Andric from comment #1)
> > I can reproduce this on both Fedora 28 and Ubuntu 18.04, but not on FreeBSD.
> > It seems to be because clang emits the initialization of InnerCls into the
> > .init_array segment *before* the GLOBAL iostream initializer, like gcc does.
> >
>
> I can NOT reproduce this on Mac OS X 10.11 with either "Apple LLVM version
> 8.0.0" or clang ToT (clang version 8.0.0 (trunk 341841))

Nor can I reproduce it with LLVM 5.0.0, 5.0.1, 5.0.2, 6.0.0 (all on my Mac).
Quuxplusone commented 6 years ago
(In reply to Dimitry Andric from comment #1)
> I can reproduce this on both Fedora 28 and Ubuntu 18.04, but not on FreeBSD.
> It seems to be because clang emits the initialization of InnerCls into the
> .init_array segment *before* the GLOBAL iostream initializer, like gcc does.

I just downloaded the 6.0.1 release onto Ubuntu 18.04, and tried it there.
Works fine for me; I guess I'm just lucky.

Dimitry, and you using any particular compiler flags?
Quuxplusone commented 6 years ago
(In reply to Marshall Clow (home) from comment #4)
> (In reply to Dimitry Andric from comment #1)
> > I can reproduce this on both Fedora 28 and Ubuntu 18.04, but not on FreeBSD.
> > It seems to be because clang emits the initialization of InnerCls into the
> > .init_array segment *before* the GLOBAL iostream initializer, like gcc does.
>
> I just downloaded the 6.0.1 release onto Ubuntu 18.04, and tried it there.
> Works fine for me; I guess I'm just lucky.
>
> Dimitry, and you using any particular compiler flags?

No, just:

clang++ -O2 pr38914-1.cpp -o pr38914-1

This is with libstdc++-7-dev 7.3.0-16ubuntu3 installed, btw, so clang picks
that by default:

$ clang++ -v
clang version 6.0.0-1ubuntu2 (tags/RELEASE_600/final)
Target: x86_64-pc-linux-gnu
Thread model: posix
InstalledDir: /usr/bin
Found candidate GCC installation: /usr/bin/../lib/gcc/i686-linux-gnu/8
Found candidate GCC installation: /usr/bin/../lib/gcc/i686-linux-gnu/8.0.1
Found candidate GCC installation: /usr/bin/../lib/gcc/x86_64-linux-gnu/7
Found candidate GCC installation: /usr/bin/../lib/gcc/x86_64-linux-gnu/7.3.0
Found candidate GCC installation: /usr/bin/../lib/gcc/x86_64-linux-gnu/8
Found candidate GCC installation: /usr/bin/../lib/gcc/x86_64-linux-gnu/8.0.1
Found candidate GCC installation: /usr/lib/gcc/i686-linux-gnu/8
Found candidate GCC installation: /usr/lib/gcc/i686-linux-gnu/8.0.1
Found candidate GCC installation: /usr/lib/gcc/x86_64-linux-gnu/7
Found candidate GCC installation: /usr/lib/gcc/x86_64-linux-gnu/7.3.0
Found candidate GCC installation: /usr/lib/gcc/x86_64-linux-gnu/8
Found candidate GCC installation: /usr/lib/gcc/x86_64-linux-gnu/8.0.1
Selected GCC installation: /usr/bin/../lib/gcc/x86_64-linux-gnu/7.3.0
Candidate multilib: .;@m64
Candidate multilib: 32;@m32
Candidate multilib: x32;@mx32
Selected multilib: .;@m64
Quuxplusone commented 6 years ago
(In reply to Dimitry Andric from comment #5)
> (In reply to Marshall Clow (home) from comment #4)
> > (In reply to Dimitry Andric from comment #1)
> > > I can reproduce this on both Fedora 28 and Ubuntu 18.04, but not on
FreeBSD.
> > > It seems to be because clang emits the initialization of InnerCls into the
> > > .init_array segment *before* the GLOBAL iostream initializer, like gcc
does.
> >
> > I just downloaded the 6.0.1 release onto Ubuntu 18.04, and tried it there.
> > Works fine for me; I guess I'm just lucky.
> >
> > Dimitry, and you using any particular compiler flags?
>
> No, just:
>
> clang++ -O2 pr38914-1.cpp -o pr38914-1
>
> This is with libstdc++-7-dev 7.3.0-16ubuntu3 installed, btw, so clang picks
> that by default:

Interesting. That explains why I'm not seeing it.
I was using libc++ (-stdlib=libc++)
Quuxplusone commented 6 years ago

Rebinning to clang.

Quuxplusone commented 6 years ago
Just tried with clang 8.0.0 r342058 on Fedora 28, same result:

        .section        .init_array,"aGw",@init_array,Cls<int>::innerCls,comdat
        .p2align        3
        .quad   __cxx_global_var_init.1
        .section        .init_array,"aw",@init_array
        .p2align        3
        .quad   _GLOBAL__sub_I_pr38914_1.cpp

        .ident  "clang version 8.0.0 (trunk 342058)"

E.g. _GLOBAL__sub_I_pr38914_1.cpp is emitted *after* __cxx_global_var_init.1,
while it should be the other way around.
Quuxplusone commented 6 years ago

This program is not guaranteed to work. Your global variable Cls::innerCls has unordered initialization, and so is permitted to be initialized before the ios_base::Init object in the same TU is constructed. Therefore there is no guarantee that std::cout is initialized prior to its use in the constructor for that variable.

Quuxplusone commented 6 years ago
(In reply to Richard Smith from comment #9)
> This program is not guaranteed to work. Your global variable
> Cls<int>::innerCls has unordered initialization, and so is permitted to be
> initialized before the ios_base::Init object in the same TU is constructed.

Aha, and that is specifically because Cls is a template class?
Quuxplusone commented 6 years ago
(In reply to Dimitry Andric from comment #10)
> Aha, and that is specifically because Cls is a template class?

Yes, exactly so. Compare: https://godbolt.org/z/kSd14o

struct A { A(); } a;
struct B { B(); };
template<typename T> B b;
void *p = &b<int>;
struct C { C(); } c;

GCC generates a single global initializer function to initialize ::a, then ::c,
then to conditionally initialize ::b<int> if no other TU got there first.

Clang generates two initializer functions: one to initialize ::a and ::c, and
another to initialize ::b<int>. (We generate two separate functions so that we
can put the ::b<int> initializer in a comdat with ::b, which is a neat
optimization that GCC lacks.)

And Clang happens to register the initializer function for ::a and ::c after
that for ::b<int>. We could change that, so the initializers for unordered
variables happen after the initializers for ordered variables in the same TU.
That's probably a good idea, even if for no other reason than to support code
such as that in comment#0.
Quuxplusone commented 6 years ago

I think Clang fails to be standards compliant - I believe since C++11, it should be guaranteed to work.

From my own testing of Clang 6.0.1 I found that if you pass -std=c++11 AND -stdlib=libc++ then it will work without crashing. If you allow it to link to libstdc++ then it fails.

I believe there is something GCC defines in order for the header to initialise correctly.

Quuxplusone commented 6 years ago
(In reply to Oliver from comment #12)
> I think Clang fails to be standards compliant - I believe since C++11, it
> should be guaranteed to work.

Please cite the part of the standard that you think justifies your claim.

To my reading, the standard says that you can use <iostream> once an
ios_base::Init object is constructed (see [iostream.objects.overview]p3) and
there's no guarantee you can do so earlier than that. In this case there is no
guarantee that an object of type ios_base::Init is constructed before
Cls<int>::innerCls runs, because Cls<int>::innerCls has unordered
initialization (see [basic.start.dynamic]).
Quuxplusone commented 6 years ago
(In reply to Richard Smith from comment #13)
> (In reply to Oliver from comment #12)
> > I think Clang fails to be standards compliant - I believe since C++11, it
> > should be guaranteed to work.
>
> Please cite the part of the standard that you think justifies your claim.
>
> To my reading, the standard says that you can use <iostream> once an
> ios_base::Init object is constructed (see [iostream.objects.overview]p3) and
> there's no guarantee you can do so earlier than that. In this case there is
> no guarantee that an object of type ios_base::Init is constructed before
> Cls<int>::innerCls runs, because Cls<int>::innerCls has unordered
> initialization (see [basic.start.dynamic]).

From N4750 - 30.4.2 [iostream.objects.overview] clause 3

> "The results of including <iostream> in a translation unit shall be as if
<iostream> defined an instance of ios_base::Init with static storage duration."

...Of course you mention [basic.start.dynamic] and perhaps I'm misunderstanding
what category OP's code falls into, but it states:

> "An explicitly specialized non-inline static data member or variable template
specialization has ordered initialization."

Does "InnerCls Cls<T>::innerCls" not fall into the first category?
Quuxplusone commented 6 years ago
(In reply to Oliver from comment #14)
> Does "InnerCls Cls<T>::innerCls" not fall into the first category?

Initialization of innerCls is unordered:

"Dynamic initialization of a non-local variable with static storage duration is
unordered if the variable is an implicitly or explicitly instantiated
specialization"
Quuxplusone commented 6 years ago

_Bug 39233 has been marked as a duplicate of this bug._

Quuxplusone commented 6 years ago

_Bug 39250 has been marked as a duplicate of this bug._