Skip to content

Commit 6123b5f

Browse files
committed
[Sema] Fix handling of fields with initializers in nested anonymous unions.
Make sure we count the anonymous union as an initialized field, so we properly construct the AST. Included bonus testcase Test3, which shows a remaining gap: an anonymous union can contain a partially initialized anonymous struct, and we handle that inconsistently. Fixes #91257
1 parent faef8b4 commit 6123b5f

File tree

3 files changed

+49
-14
lines changed

3 files changed

+49
-14
lines changed

clang/lib/Sema/SemaInit.cpp

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -814,19 +814,13 @@ InitListChecker::FillInEmptyInitializations(const InitializedEntity &Entity,
814814

815815
if (const RecordType *RType = ILE->getType()->getAs<RecordType>()) {
816816
const RecordDecl *RDecl = RType->getDecl();
817-
if (RDecl->isUnion() && ILE->getInitializedFieldInUnion())
818-
FillInEmptyInitForField(0, ILE->getInitializedFieldInUnion(),
819-
Entity, ILE, RequiresSecondPass, FillWithNoInit);
820-
else if (RDecl->isUnion() && isa<CXXRecordDecl>(RDecl) &&
821-
cast<CXXRecordDecl>(RDecl)->hasInClassInitializer()) {
822-
for (auto *Field : RDecl->fields()) {
823-
if (Field->hasInClassInitializer()) {
824-
FillInEmptyInitForField(0, Field, Entity, ILE, RequiresSecondPass,
825-
FillWithNoInit);
826-
break;
827-
}
828-
}
817+
if (RDecl->isUnion() && ILE->getInitializedFieldInUnion()) {
818+
FillInEmptyInitForField(0, ILE->getInitializedFieldInUnion(), Entity, ILE,
819+
RequiresSecondPass, FillWithNoInit);
829820
} else {
821+
assert((!RDecl->isUnion() || !isa<CXXRecordDecl>(RDecl) ||
822+
!cast<CXXRecordDecl>(RDecl)->hasInClassInitializer()) &&
823+
"We should have computed initialized fields already");
830824
// The fields beyond ILE->getNumInits() are default initialized, so in
831825
// order to leave them uninitialized, the ILE is expanded and the extra
832826
// fields are then filled with NoInitExpr.
@@ -2164,12 +2158,15 @@ void InitListChecker::CheckStructUnionTypes(
21642158
return;
21652159
for (RecordDecl::field_iterator FieldEnd = RD->field_end();
21662160
Field != FieldEnd; ++Field) {
2167-
if (Field->hasInClassInitializer()) {
2161+
if (Field->hasInClassInitializer() ||
2162+
(Field->isAnonymousStructOrUnion() &&
2163+
Field->getType()->getAsCXXRecordDecl()->hasInClassInitializer())) {
21682164
StructuredList->setInitializedFieldInUnion(*Field);
21692165
// FIXME: Actually build a CXXDefaultInitExpr?
21702166
return;
21712167
}
21722168
}
2169+
llvm_unreachable("Couldn't find in-class initializer");
21732170
}
21742171

21752172
// Value-initialize the first member of the union that isn't an unnamed

clang/test/AST/ast-dump-APValue-anon-union.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ void Test() {
3636

3737
constexpr U0 u0a{};
3838
// CHECK: | `-VarDecl {{.*}} <col:{{.*}}, col:{{.*}}> col:{{.*}} u0a 'const U0' constexpr listinit
39-
// CHECK-NEXT: | |-value: Union None
39+
// CHECK-NEXT: | |-value: Union .U0::(anonymous union at {{.*}}) Union .f Float 3.141500e+00
4040

4141
constexpr U0 u0b{3.1415f};
4242
// CHECK: | `-VarDecl {{.*}} <col:{{.*}}, col:{{.*}}> col:{{.*}} u0b 'const U0' constexpr listinit

clang/test/SemaCXX/cxx1y-initializer-aggregates.cpp

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,3 +77,41 @@ namespace use_self {
7777

7878
int fib(int n) { return FibTree{n}.v; }
7979
}
80+
81+
namespace nested_union {
82+
union Test1 {
83+
union {
84+
int inner { 42 };
85+
};
86+
int outer;
87+
};
88+
static_assert(Test1{}.inner == 42, "");
89+
struct Test2 {
90+
union {
91+
struct {
92+
int inner : 32 { 42 }; // expected-warning {{C++20 extension}}
93+
int inner_no_init;
94+
};
95+
int outer;
96+
};
97+
};
98+
static_assert(Test2{}.inner == 42, "");
99+
static_assert(Test2{}.inner_no_init == 0, "");
100+
struct Int { int x; };
101+
struct Test3 {
102+
int x;
103+
union {
104+
struct { // expected-note {{in implicit initialization}}
105+
const int& y; // expected-note {{uninitialized reference member is here}}
106+
int inner : 32 { 42 }; // expected-warning {{C++20 extension}}
107+
};
108+
int outer;
109+
};
110+
};
111+
Test3 test3 = {1}; // expected-error {{reference member of type 'const int &' uninitialized}}
112+
constexpr char f(Test3) { return 1; } // expected-note {{candidate function}}
113+
constexpr char f(Int) { return 2; } // expected-note {{candidate function}}
114+
// FIXME: This shouldn't be ambiguous; either we should reject the declaration
115+
// of Test3, or we should exclude f(Test3) as a candidate.
116+
static_assert(f({1}) == 2, ""); // expected-error {{call to 'f' is ambiguous}}
117+
}

0 commit comments

Comments
 (0)