Skip to content

[Clang][Sema] Fix incorrect rejection default construction of union with nontrivial member #82407

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,9 @@ Bug Fixes to C++ Support
Fixes (`#82941 <https://github.com/llvm/llvm-project/issues/82941>`_),
(`#42411 <https://github.com/llvm/llvm-project/issues/42411>`_), and
(`#18121 <https://github.com/llvm/llvm-project/issues/18121>`_).
- Fix for clang incorrectly rejecting the default construction of a union with
nontrivial member when another member has an initializer.
(`#81774 <https://github.com/llvm/llvm-project/issues/81774>`_)

Bug Fixes to AST Handling
^^^^^^^^^^^^^^^^^^^^^^^^^
Expand Down
18 changes: 15 additions & 3 deletions clang/lib/Sema/SemaDeclCXX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9442,9 +9442,21 @@ bool SpecialMemberDeletionInfo::shouldDeleteForSubobjectCall(

int DiagKind = -1;

if (SMOR.getKind() == Sema::SpecialMemberOverloadResult::NoMemberOrDeleted)
DiagKind = !Decl ? 0 : 1;
else if (SMOR.getKind() == Sema::SpecialMemberOverloadResult::Ambiguous)
if (SMOR.getKind() == Sema::SpecialMemberOverloadResult::NoMemberOrDeleted) {
if (CSM == Sema::CXXDefaultConstructor && Field &&
Field->getParent()->isUnion()) {
// [class.default.ctor]p2:
// A defaulted default constructor for class X is defined as deleted if
// - X is a union that has a variant member with a non-trivial default
// constructor and no variant member of X has a default member
// initializer
const auto *RD = cast<CXXRecordDecl>(Field->getParent());
if (!RD->hasInClassInitializer())
DiagKind = !Decl ? 0 : 1;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So what is going on here? this is the same value as its 'else' clause, and leaves DiagKind as -1 otherwise? I might suggest making this a single 'if' and getting rid of the 'else' here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have three different states I need to check, I can remove a check by setting DiagKind unconditionally and then check if(RD->hasInClassInitializer()) instead and in that case set it back to -1 but that does not express the intent as clearly. So I think what I have now is more maintainable although slightly more verbose.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW, I ran into exactly the same issue that Erich did in noticing that both branches do DiagKind = !Decl ? 0 : 1;. That two of us ran into the same readability issue suggests that this should be modified to be more clear.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can refactor it like this: MitalAshok@a119d89 and I would suggest changing to an enum for readability: MitalAshok@38ca82e (This can then be extracted out into a static function that returns the enum and each DiagKind = ... can turn into return ..., and the if/else if ladder can be dismantled)

} else {
DiagKind = !Decl ? 0 : 1;
}
} else if (SMOR.getKind() == Sema::SpecialMemberOverloadResult::Ambiguous)
DiagKind = 2;
else if (!isAccessible(Subobj, Decl))
DiagKind = 3;
Expand Down
17 changes: 17 additions & 0 deletions clang/test/CodeGen/union-non-trivial-member.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,40 @@ union UnionNonTrivial {
non_trivial_constructor b{};
};

struct Handle {
Handle(int) {}
};

union UnionNonTrivialEqualInit {
int NoState = 0;
Handle CustomState;
};

void f() {
UnionInt u1;
UnionNonTrivial u2;
UnionNonTrivialEqualInit u3;
}

// CHECK: define dso_local void @_Z1fv()
// CHECK: call void @_ZN8UnionIntC1Ev
// CHECK-NEXT: call void @_ZN15UnionNonTrivialC1Ev
// CHECK-NEXT: call void @_ZN24UnionNonTrivialEqualInitC1Ev

// CHECK: define {{.*}}void @_ZN8UnionIntC1Ev
// CHECK: call void @_ZN8UnionIntC2Ev

// CHECK: define {{.*}}void @_ZN15UnionNonTrivialC1Ev
// CHECK: call void @_ZN15UnionNonTrivialC2Ev

// CHECK: define {{.*}}void @_ZN24UnionNonTrivialEqualInitC1Ev
// CHECK: call void @_ZN24UnionNonTrivialEqualInitC2Ev

// CHECK: define {{.*}}void @_ZN8UnionIntC2Ev
// CHECK: store i32 1000

// CHECK: define {{.*}}void @_ZN15UnionNonTrivialC2Ev
// CHECK: call void @_ZN23non_trivial_constructorC1Ev

// CHECK: define {{.*}}void @_ZN24UnionNonTrivialEqualInitC2Ev
// CHECK: store i32 0
11 changes: 11 additions & 0 deletions clang/test/SemaCXX/cxx0x-nontrivial-union.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -188,3 +188,14 @@ static_assert(U2().b.x == 100, "");
static_assert(U3().b.x == 100, "");

} // namespace GH48416

namespace GH81774 {
struct Handle {
Handle(int) {}
};
// Should be well-formed because NoState has a brace-or-equal-initializer.
union a {
int NoState = 0;
Handle CustomState;
} b;
} // namespace GH81774