Bug 117679 - Changing active member in a union with overlapping copying not detected in constant expression
Summary: Changing active member in a union with overlapping copying not detected in co...
Status: UNCONFIRMED
Alias: None
Product: gcc
Classification: Unclassified
Component: c++ (show other bugs)
Version: 14.2.1
: P3 normal
Target Milestone: ---
Assignee: Not yet assigned to anyone
URL:
Keywords: wrong-code
Depends on:
Blocks: constexpr
  Show dependency treegraph
 
Reported: 2024-11-19 16:43 UTC by Fedor Chelnokov
Modified: 2024-11-23 13:56 UTC (History)
2 users (show)

See Also:
Host:
Target:
Build:
Known to work:
Known to fail:
Last reconfirmed:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Fedor Chelnokov 2024-11-19 16:43:57 UTC
In this program
```
struct A {
    char u, v, w;
};

struct X { char x; };

struct B : X, A {
    using A::operator=;
};

constexpr A f() {
    union U {
        A a{ 1, 2, 3 };
        B b;
    } u;
    u.b = u.a;
    return u.b;
}

// ok in GCC and MSVC, fails in Clang
static_assert( f().w == 3 );

int main() {
    return f().w; //2 in GCC with -O0
}
```

constant evaluation of `f().w == 3` succeeds in GCC, but in runtime with -O0 switch `f().w` is evaluated as 2.

Clang here finds that `u.b = u.a;` is not a constant expression because it simultaneously accesses two members of a union during copying. Online demo: https://gcc.godbolt.org/z/Ks7oaoaz9

Related discussion: https://stackoverflow.com/q/79196052/7325599
Comment 1 Drea Pinski 2024-11-20 05:03:54 UTC
I am not sure if this is related to https://cplusplus.github.io/CWG/issues/2721.html at all.

because
Comment 2 Drea Pinski 2024-11-20 05:31:41 UTC
```
struct A {
    char u, v, w;
};

struct X { char x; };

struct B : X {
    A y;
};

constexpr A f() {
    union U {
        A a{ 1, 2, 3 };
        B b;
    } u;
    u.b.y = u.a; // when does the life time of u.b start, during the statement?
    return u.b.y;
}

// ok in GCC and MSVC, fails in Clang
static_assert( f().w == 3 );

int main() {
    return f().w; //2 in GCC with -O0
}
```

So I think this is similar as the above. This produces similar results as GCC, clang, and MSVC for the original testcase.

EDG also accepts this code and produces 3 for main.

Note I think only GCC -O0 code generation is wrong for main.

basic.life/1 says:
```
The lifetime of an object o of type T ends when:
...
the storage which the object occupies is released, or is reused by an object that is not nested within o (6.7.2 [intro.object]).
```

But it is not obvious.
Comment 3 Fedor Chelnokov 2024-11-21 13:45:39 UTC
Here is a better example. It looks valid (unlike above one) because of creating of copy `A( u.a )`:
```
#include <iostream>

struct A { int c[7]; };

struct X { int x; };

struct B : X, A {
    using A::operator=;
};

int main() {
    union U {
        A a{ 0, 1, 2, 3, 4, 5, 6 };
        B b;
    } u;
    
    u.b = A( u.a );

    for ( int i = 0; i < 7; ++i )
        std::cout << (int)u.b.c[i];
}
```

It prints `0123456` in Clang and MSVC. But in GCC 14 it prints `0122356` with any optimization level (-O0 through -O3). Online demo: https://gcc.godbolt.org/z/GKo8YcqqM