diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -48,6 +48,7 @@ #include "llvm/ADT/StringExtras.h" #include #include +#include #include using namespace clang; @@ -10321,21 +10322,30 @@ } } - for (const auto *FD : RD.fields()) { - // Ill-formed if the field is an ObjectiveC pointer or of a type that is - // non-trivial for the purpose of calls. - QualType FT = FD->getType(); - if (FT.getObjCLifetime() == Qualifiers::OCL_Weak) { - PrintDiagAndRemoveAttr(4); - return; - } - - if (const auto *RT = FT->getBaseElementTypeUnsafe()->getAs()) - if (!RT->isDependentType() && - !cast(RT->getDecl())->canPassInRegisters()) { - PrintDiagAndRemoveAttr(5); + std::queue RecordsWithFieldsToCheck; + RecordsWithFieldsToCheck.push(&RD); + while (!RecordsWithFieldsToCheck.empty()) { + for (const FieldDecl *FD : RecordsWithFieldsToCheck.front()->fields()) { + // Ill-formed if the field is an ObjectiveC pointer or of a type that is + // non-trivial for the purpose of calls. + QualType FT = FD->getType(); + if (FT.getObjCLifetime() == Qualifiers::OCL_Weak) { + PrintDiagAndRemoveAttr(4); return; } + + if (const auto *RT = + FT->getBaseElementTypeUnsafe()->getAs()) { + if (RT->getDecl()->isAnonymousStructOrUnion()) { + RecordsWithFieldsToCheck.push(RT->getDecl()); + } else if (!RT->isDependentType() && + !cast(RT->getDecl())->canPassInRegisters()) { + PrintDiagAndRemoveAttr(5); + return; + } + } + } + RecordsWithFieldsToCheck.pop(); } } diff --git a/clang/test/SemaCXX/attr-trivial-abi.cpp b/clang/test/SemaCXX/attr-trivial-abi.cpp --- a/clang/test/SemaCXX/attr-trivial-abi.cpp +++ b/clang/test/SemaCXX/attr-trivial-abi.cpp @@ -169,3 +169,94 @@ static_assert(__is_trivially_relocatable(S20), ""); #endif } // namespace deletedCopyMoveConstructor + +namespace anonymousUnionsAndStructs { + // Test helper: + struct [[clang::trivial_abi]] Trivial { + Trivial() {} + Trivial(Trivial&& other) {} + Trivial& operator=(Trivial&& other) { return *this; } + ~Trivial() {} + }; + static_assert(__is_trivially_relocatable(Trivial), ""); + + // Test helper: + struct Nontrivial { + Nontrivial() {} + Nontrivial(Nontrivial&& other) {} + Nontrivial& operator=(Nontrivial&& other) { return *this; } + ~Nontrivial() {} + }; + static_assert(!__is_trivially_relocatable(Nontrivial), ""); + + // Basic smoke test, not yet related to anonymous unions or structs: + struct [[clang::trivial_abi]] S1 { + S1(S1&& other) {} + S1& operator=(S1&& other) { return *this; } + ~S1() {} + Trivial field; + }; + static_assert(__is_trivially_relocatable(S1), ""); + + // `S2` is like `S1`, but `field` is wrapped in an anonymous union (and it + // seems that trivial relocatability of `S1` and `S2` should be the same). + // + // It's impossible to declare a constructor for an anonymous unions so to + // support applying `[[clang::trivial_abi]]` to structs containing anonymous + // unions, and therefore when processing fields of the struct containing the + // anonymous union, the trivial relocatability of the *union* is ignored and + // instead the union's fields are recursively inspected in + // `checkIllFormedTrivialABIStruct`. + struct [[clang::trivial_abi]] S2 { + S2(S2&& other) {} + S2& operator=(S2&& other) { return *this; } + ~S2() {} + union { Trivial field; }; + }; + static_assert(__is_trivially_relocatable(S2), ""); + + // `S3` is like `S2` but uses an anonymous `struct` rather than an anonymous + // `union. + struct [[clang::trivial_abi]] S3 { + S3(S3&& other) {} + S3& operator=(S3&& other) { return *this; } + ~S3() {} + struct { Trivial field; }; + }; + static_assert(__is_trivially_relocatable(S3), ""); + + // `S4` is like `S2` but with `[[clang::trivial_abi]]` also applied to the + // anonymous union. + // + // `S2` and `S3` examples above show that `[[clang::trivial_abi]]` + // *implicitly* propagates into anonymous union and structs. The example + // below shows that it is still *not* okay to *explicitly* apply + // `[[clang::trivial_abi]]` to anonymous unions. Handling this would require + // relaxing the `HasNonDeletedCopyOrMoveConstructor` check when + // `isAnonymousStructOrUnion` in `checkIllFormedTrivialABIStruct` but when + // that check runs `setAnonymousStructOrUnion` hasn't been called yet + // (i.e. at this point it's not possible to rely on + // `RD->isAnonymousStructOrUnion()`). + struct [[clang::trivial_abi]] S4 { + S4(S4&& other) {} + S4& operator=(S4&& other) { return *this; } + ~S4() {} + union [[clang::trivial_abi]] { // expected-warning {{'trivial_abi' cannot be applied to '(unnamed union}} expected-note {{copy constructors and move constructors are all deleted}} + Trivial field; + }; + }; + static_assert(__is_trivially_relocatable(S4), ""); + + // Like `S2`, but the field of the anonymous union is *not* trivial. + struct [[clang::trivial_abi]] S5 { // expected-warning {{'trivial_abi' cannot be applied to 'S5'}} expected-note {{trivial_abi' is disallowed on 'S5' because it has a field of a non-trivial class type}} + S5(S5&& other) {} + S5& operator=(S5&& other) { return *this; } + ~S5() {} + union { + Nontrivial field; + }; + }; + static_assert(!__is_trivially_relocatable(S5), ""); + +} // namespace anonymousStructsAndUnions +