llvm / llvm-project

The LLVM Project is a collection of modular and reusable compiler and toolchain technologies.
http://llvm.org
Other
28.51k stars 11.79k forks source link

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

Open llvmbot opened 6 years ago

llvmbot commented 6 years ago
Bugzilla Link 38914
Version 6.0
OS Linux
Reporter LLVM Bugzilla Contributor
CC @dwblaikie,@DougGregor,@DimitryAndric,@mclow,@zygoloid

Extended Description

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;
}
ec04fc15-fa35-46f2-80e1-5d271f2ef708 commented 2 years ago

mentioned in issue llvm/llvm-bugzilla-archive#39250

ec04fc15-fa35-46f2-80e1-5d271f2ef708 commented 2 years ago

mentioned in issue llvm/llvm-bugzilla-archive#39233

ec04fc15-fa35-46f2-80e1-5d271f2ef708 commented 6 years ago

Bug llvm/llvm-bugzilla-archive#39250 has been marked as a duplicate of this bug.

ec04fc15-fa35-46f2-80e1-5d271f2ef708 commented 6 years ago

Bug llvm/llvm-bugzilla-archive#39233 has been marked as a duplicate of this bug.

ec04fc15-fa35-46f2-80e1-5d271f2ef708 commented 6 years ago

Does "InnerCls Cls::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

llvmbot commented 6 years ago

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 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::innerCls runs, because Cls::innerCls has unordered initialization (see [basic.start.dynamic]).

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

"The results of including in a translation unit shall be as if 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?

ec04fc15-fa35-46f2-80e1-5d271f2ef708 commented 6 years ago

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]).

llvmbot 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 <iostream> header to initialise correctly.

ec04fc15-fa35-46f2-80e1-5d271f2ef708 commented 6 years ago

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.

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

Aha, and that is specifically because Cls is a template class?

ec04fc15-fa35-46f2-80e1-5d271f2ef708 commented 6 years ago

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. Therefore there is no guarantee that std::cout is initialized prior to its use in the constructor for that variable.

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

mclow commented 6 years ago

Rebinning to clang.

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

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++)

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

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

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?

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

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

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

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))

DimitryAndric 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:                # @&#8203;__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:           # @&#8203;_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, @&#8203;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.