Search code examples
c++clang++designated-initializer

clang can remove function call when order of designated initializers does not correspond to fields declaration


this code:

#include <iostream>
struct Acc {
    int a;
};
struct Buu {
    int b;
};

struct Foo {
    const Acc& acc;
    Buu& buu;
};

void printInfo( const Foo& ) {
    std::cout << "hi!" << std::endl;
}

void call( Buu& buu ) {
    Acc acc = { 1 };
    Foo foo = {
        .acc = acc,
        .buu = buu,
    };
    std::cout << "before" << std::endl;
    printInfo( foo );
    std::cout << "after" << std::endl;
}
void noCall( Buu& buu ) {
    Acc acc = { 1 };
    Foo foo = {
        .buu = buu,
        .acc = acc,
    };
    std::cout << "before" << std::endl;
    printInfo( foo );
    std::cout << "after" << std::endl;
}

int main() {
    Buu buu = { 2 };
    call( buu );
    noCall( buu );
    return 0;
}

when compliled by clang (I tried 3.7.0, 3.7.1) will out:

before
hi!
after
before
after

The second call of printInfo was removed... Difference between call and noCall only in order of designated initializers.

With -pedantic option it will produce warnings that designated initializers is the feature of C99 but not C++ but still create code without the second call of printInfo.

Is it known bug?


Solution

  • I think that is at least unfair if not a bug, because the warning is only at pedandic level when Clang simply removes all references to foo in function nocall. We can confirm it by looking at assembly code in debug mode (c++ -S -g file.cpp) to see exactly how the compiler interprets each and every line.

    When we look at the .s genererated file, we can see that in call, lines 20 Foo foo = {... and 25 printInfo(foo) are generated:

        .loc    1 20 0                  # ess.cpp:20:0
        movq    %rcx, -64(%rbp)
        movq    -40(%rbp), %rcx
    .Ltmp45:
        movq    %rcx, -56(%rbp)
        .loc    1 24 0                  # ess.cpp:24:0
        movq    %rax, %rdi
        callq   _ZNSt3__1lsINS_11char_traitsIcEEEERNS_13basic_ostreamIcT_EES6_PKc
        leaq    -64(%rbp), %rdi
        leaq    _ZNSt3__14endlIcNS_11char_traitsIcEEEERNS_13basic_ostreamIT_T0_EES7_, %rcx
        movq    %rax, -24(%rbp)
        movq    %rcx, -32(%rbp)
        movq    -24(%rbp), %rax
        .loc    5 322 0                 # /usr/include/c++/v1/ostream:322:0
    .Ltmp46:
        movq    %rdi, -72(%rbp)         # 8-byte Spill
        movq    %rax, %rdi
        callq   *-32(%rbp)
    .Ltmp47:
        .loc    1 25 0                  # ess.cpp:25:0
        movq    -72(%rbp), %rdi         # 8-byte Reload
        movq    %rax, -80(%rbp)         # 8-byte Spill
        callq   _Z9printInfoRK3Foo
        leaq    _ZNSt3__14coutE, %rdi
        leaq    .L.str2, %rsi
    

    But for nocall, the corresponding lines (30 and 35) are not:

        .loc    1 29 0 prologue_end     # ess.cpp:29:0
    .Ltmp57:
        movl    .L_ZZ6noCallR3BuuE3acc, %ecx
        movl    %ecx, -48(%rbp)
        .loc    1 34 0                  # ess.cpp:34:0
        movq    %rax, %rdi
        callq   _ZNSt3__1lsINS_11char_traitsIcEEEERNS_13basic_ostreamIcT_EES6_PKc
        leaq    _ZNSt3__14coutE, %rdi
        leaq    .L.str2, %rsi
        leaq    _ZNSt3__14endlIcNS_11char_traitsIcEEEERNS_13basic_ostreamIT_T0_EES7_, %rdx
        movq    %rax, -24(%rbp)
        movq    %rdx, -32(%rbp)
        movq    -24(%rbp), %rax
        .loc    5 322 0                 # /usr/include/c++/v1/ostream:322:0
    .Ltmp58:
        movq    %rdi, -72(%rbp)         # 8-byte Spill
        movq    %rax, %rdi
        movq    %rsi, -80(%rbp)         # 8-byte Spill
        callq   *-32(%rbp)
    .Ltmp59:
        .loc    1 36 0                  # ess.cpp:36:0
        movq    -72(%rbp), %rdi         # 8-byte Reload
        movq    -80(%rbp), %rsi         # 8-byte Reload
        movq    %rax, -88(%rbp)         # 8-byte Spill
        callq   _ZNSt3__1lsINS_11char_traitsIcEEEERNS_13basic_ostreamIcT_EES6_PKc
        leaq    _ZNSt3__14endlIcNS_11char_traitsIcEEEERNS_13basic_ostreamIT_T0_EES7_, %rdx
        movq    %rax, -8(%rbp)
        movq    %rdx, -16(%rbp)
        movq    -8(%rbp), %rdi
        .loc    5 322 0                 # /usr/include/c++/v1/ostream:322:0
    .Ltmp60:
        callq   *-16(%rbp)
    .Ltmp61:
        .loc    1 37 0                  # ess.cpp:37:0
    

    Where the numbered lines in the cpp file are:

    18  void call( Buu& buu ) {
    19      Acc acc = { 1 };
    20      Foo foo = {
    21          .acc = acc,
    22          .buu = buu,
    23      };
    24      std::cout << "before" << std::endl;
    25      printInfo( foo );
    26      std::cout << "after" << std::endl;
    27  }
    28  void noCall( Buu& buu ) {
    29      Acc acc = { 1 };
    30      Foo foo = {
    31              .buu = buu,
    32              .acc = acc
    33      };
    34      std::cout << "before" << std::endl;
    35      printInfo( foo );
    36      std::cout << "after" << std::endl;
    37  }
    

    My understanding is that clang pretends to process the C99 syntax in C++ mode when it does not.


    IMHO, this is a bug that could be reported to clang, because at least a diagnostic should be issued per 1.4 Implementation compliance [intro.compliance]

    1 The set of diagnosable rules consists of all syntactic and semantic rules in this International Standard except for those rules containing an explicit notation that “no diagnostic is required” or which are described as resulting in “undefined behavior.”
    2 Although this International Standard states only requirements on C++ implementations, those requirements are often easier to understand if they are phrased as requirements on programs, parts of programs, or execution of programs. Such requirements have the following meaning:

    • If a program contains no violations of the rules in this International Standard, a conforming implementation shall, within its resource limits, accept and correctly execute2 that program.
    • If a program contains a violation of any diagnosable rule or an occurrence of a construct described in this Standard as “conditionally-supported” when the implementation does not support that construct, a conforming implementation shall issue at least one diagnostic message.

    ...
    8 A conforming implementation may have extensions (including additional library functions), provided they do not alter the behavior of any well-formed program. Implementations are required to diagnose programs that use such extensions that are ill-formed according to this International Standard. Having done so, however, they can compile and execute such programs.