Skip to content

Commit 5ae5774

Browse files
[Sema] Fix handling of fields with initializers in nested anonymous unions. (#91692)
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 8e94f0a commit 5ae5774

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)