Author: Samarth Narang Date: 2025-06-25T14:49:15-04:00 New Revision: d760f97387878ed858273d3adc206ce4dca760f6
URL: https://github.com/llvm/llvm-project/commit/d760f97387878ed858273d3adc206ce4dca760f6 DIFF: https://github.com/llvm/llvm-project/commit/d760f97387878ed858273d3adc206ce4dca760f6.diff LOG: [Clang] Implement diagnostics for why `std::is_standard_layout` is false (#144161) Added: Modified: clang/include/clang/Basic/DiagnosticSemaKinds.td clang/lib/Sema/SemaTypeTraits.cpp clang/test/SemaCXX/type-traits-unsatisfied-diags-std.cpp clang/test/SemaCXX/type-traits-unsatisfied-diags.cpp Removed: ################################################################################ diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 6eba0619883d3..5062505cf3c01 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -1768,7 +1768,8 @@ def note_unsatisfied_trait "%TriviallyRelocatable{trivially relocatable}|" "%Replaceable{replaceable}|" "%TriviallyCopyable{trivially copyable}|" - "%Empty{empty}" + "%Empty{empty}|" + "%StandardLayout{standard-layout}" "}1">; def note_unsatisfied_trait_reason @@ -1792,6 +1793,12 @@ def note_unsatisfied_trait_reason "%VirtualFunction{has a virtual function %1}|" "%NonEmptyBase{has a base class %1 that is not empty}|" "%NonZeroLengthField{field %1 is a non-zero-length bit-field}|" + "%NonStandardLayoutBase{has a non-standard-layout base %1}|" + "%MixedAccess{has mixed access specifiers}|" + "%MixedAccessField{field %1 has a diff erent access specifier than field %2}|" + "%MultipleDataBase{has multiple base classes with data members}|" + "%NonStandardLayoutMember{has a non-standard-layout member %1 of type %2}|" + "%IndirectBaseWithFields{has an indirect base %1 with data members}|" "%DeletedDtr{has a %select{deleted|user-provided}1 destructor}|" "%UserProvidedCtr{has a user provided %select{copy|move}1 " "constructor}|" diff --git a/clang/lib/Sema/SemaTypeTraits.cpp b/clang/lib/Sema/SemaTypeTraits.cpp index a233cb6e912f4..cb3d9b77ee4dd 100644 --- a/clang/lib/Sema/SemaTypeTraits.cpp +++ b/clang/lib/Sema/SemaTypeTraits.cpp @@ -1959,6 +1959,7 @@ static std::optional<TypeTrait> StdNameToTypeTrait(StringRef Name) { .Case("is_trivially_copyable", TypeTrait::UTT_IsTriviallyCopyable) .Case("is_assignable", TypeTrait::BTT_IsAssignable) .Case("is_empty", TypeTrait::UTT_IsEmpty) + .Case("is_standard_layout", TypeTrait::UTT_IsStandardLayout) .Default(std::nullopt); } @@ -2382,6 +2383,150 @@ static void DiagnoseIsEmptyReason(Sema &S, SourceLocation Loc, QualType T) { } } +static bool hasMultipleDataBaseClassesWithFields(const CXXRecordDecl *D) { + int NumBasesWithFields = 0; + for (const CXXBaseSpecifier &Base : D->bases()) { + const CXXRecordDecl *BaseRD = Base.getType()->getAsCXXRecordDecl(); + if (!BaseRD || BaseRD->isInvalidDecl()) + continue; + + for (const FieldDecl *Field : BaseRD->fields()) { + if (!Field->isUnnamedBitField()) { + if (++NumBasesWithFields > 1) + return true; // found more than one base class with fields + break; // no need to check further fields in this base class + } + } + } + return false; +} + +static void DiagnoseNonStandardLayoutReason(Sema &SemaRef, SourceLocation Loc, + const CXXRecordDecl *D) { + for (const CXXBaseSpecifier &B : D->bases()) { + assert(B.getType()->getAsCXXRecordDecl() && "invalid base?"); + if (B.isVirtual()) { + SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason) + << diag::TraitNotSatisfiedReason::VBase << B.getType() + << B.getSourceRange(); + } + if (!B.getType()->isStandardLayoutType()) { + SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason) + << diag::TraitNotSatisfiedReason::NonStandardLayoutBase << B.getType() + << B.getSourceRange(); + } + } + // Check for mixed access specifiers in fields. + const FieldDecl *FirstField = nullptr; + AccessSpecifier FirstAccess = AS_none; + + for (const FieldDecl *Field : D->fields()) { + if (Field->isUnnamedBitField()) + continue; + + // Record the first field we see + if (!FirstField) { + FirstField = Field; + FirstAccess = Field->getAccess(); + continue; + } + + // Check if the field has a diff erent access specifier than the first one. + if (Field->getAccess() != FirstAccess) { + // Emit a diagnostic about mixed access specifiers. + SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason) + << diag::TraitNotSatisfiedReason::MixedAccess; + + SemaRef.Diag(FirstField->getLocation(), diag::note_defined_here) + << FirstField; + + SemaRef.Diag(Field->getLocation(), diag::note_unsatisfied_trait_reason) + << diag::TraitNotSatisfiedReason::MixedAccessField << Field + << FirstField; + + // No need to check further fields, as we already found mixed access. + break; + } + } + if (hasMultipleDataBaseClassesWithFields(D)) { + SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason) + << diag::TraitNotSatisfiedReason::MultipleDataBase; + } + if (D->isPolymorphic()) { + // Find the best location to point “defined here” at. + const CXXMethodDecl *VirtualMD = nullptr; + // First, look for a virtual method. + for (const auto *M : D->methods()) { + if (M->isVirtual()) { + VirtualMD = M; + break; + } + } + if (VirtualMD) { + SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason) + << diag::TraitNotSatisfiedReason::VirtualFunction << VirtualMD; + SemaRef.Diag(VirtualMD->getLocation(), diag::note_defined_here) + << VirtualMD; + } else { + // If no virtual method, point to the record declaration itself. + SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason) + << diag::TraitNotSatisfiedReason::VirtualFunction << D; + SemaRef.Diag(D->getLocation(), diag::note_defined_here) << D; + } + } + for (const FieldDecl *Field : D->fields()) { + if (!Field->getType()->isStandardLayoutType()) { + SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason) + << diag::TraitNotSatisfiedReason::NonStandardLayoutMember << Field + << Field->getType() << Field->getSourceRange(); + } + } + // Find any indirect base classes that have fields. + if (D->hasDirectFields()) { + const CXXRecordDecl *Indirect = nullptr; + D->forallBases([&](const CXXRecordDecl *BaseDef) { + if (BaseDef->hasDirectFields()) { + Indirect = BaseDef; + return false; // stop traversal + } + return true; // continue to the next base + }); + if (Indirect) { + SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason) + << diag::TraitNotSatisfiedReason::IndirectBaseWithFields << Indirect + << Indirect->getSourceRange(); + } + } +} + +static void DiagnoseNonStandardLayoutReason(Sema &SemaRef, SourceLocation Loc, + QualType T) { + SemaRef.Diag(Loc, diag::note_unsatisfied_trait) + << T << diag::TraitName::StandardLayout; + + // Check type-level exclusion first. + if (T->isVariablyModifiedType()) { + SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason) + << diag::TraitNotSatisfiedReason::VLA; + return; + } + + if (T->isReferenceType()) { + SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason) + << diag::TraitNotSatisfiedReason::Ref; + return; + } + T = T.getNonReferenceType(); + const CXXRecordDecl *D = T->getAsCXXRecordDecl(); + if (!D || D->isInvalidDecl()) + return; + + if (D->hasDefinition()) + DiagnoseNonStandardLayoutReason(SemaRef, Loc, D); + + SemaRef.Diag(D->getLocation(), diag::note_defined_here) << D; +} + void Sema::DiagnoseTypeTraitDetails(const Expr *E) { E = E->IgnoreParenImpCasts(); if (E->containsErrors()) @@ -2408,6 +2553,9 @@ void Sema::DiagnoseTypeTraitDetails(const Expr *E) { case UTT_IsEmpty: DiagnoseIsEmptyReason(*this, E->getBeginLoc(), Args[0]); break; + case UTT_IsStandardLayout: + DiagnoseNonStandardLayoutReason(*this, E->getBeginLoc(), Args[0]); + break; default: break; } diff --git a/clang/test/SemaCXX/type-traits-unsatisfied-diags-std.cpp b/clang/test/SemaCXX/type-traits-unsatisfied-diags-std.cpp index 89fbad0c5b9b8..cf33ac283ab42 100644 --- a/clang/test/SemaCXX/type-traits-unsatisfied-diags-std.cpp +++ b/clang/test/SemaCXX/type-traits-unsatisfied-diags-std.cpp @@ -35,6 +35,13 @@ struct is_empty { }; template <typename T> constexpr bool is_empty_v = __is_empty(T); + +template <typename T> +struct is_standard_layout { +static constexpr bool value = __is_standard_layout(T); +}; +template <typename T> +constexpr bool is_standard_layout_v = __is_standard_layout(T); #endif #ifdef STD2 @@ -79,6 +86,17 @@ template <typename T> using is_empty = __details_is_empty<T>; template <typename T> constexpr bool is_empty_v = __is_empty(T); + +template <typename T> +struct __details_is_standard_layout { +static constexpr bool value = __is_standard_layout(T); + + +}; +template <typename T> +using is_standard_layout = __details_is_standard_layout<T>; +template <typename T> +constexpr bool is_standard_layout_v = __is_standard_layout(T); #endif @@ -124,6 +142,13 @@ template <typename T> using is_empty = __details_is_empty<T>; template <typename T> constexpr bool is_empty_v = is_empty<T>::value; + +template <typename T> +struct __details_is_standard_layout : bool_constant<__is_standard_layout(T)> {}; +template <typename T> +using is_standard_layout = __details_is_standard_layout<T>; +template <typename T> +constexpr bool is_standard_layout_v = is_standard_layout<T>::value; #endif } @@ -150,6 +175,21 @@ static_assert(std::is_trivially_copyable_v<int&>); // expected-note@-1 {{'int &' is not trivially copyable}} \ // expected-note@-1 {{because it is a reference type}} + + // Direct tests + static_assert(std::is_standard_layout<int>::value); + static_assert(std::is_standard_layout_v<int>); + + static_assert(std::is_standard_layout<int&>::value); + // expected-error-re@-1 {{static assertion failed due to requirement 'std::{{.*}}is_standard_layout<int &>::value'}} \ + // expected-note@-1 {{'int &' is not standard-layout}} \ + // expected-note@-1 {{because it is a reference type}} + + static_assert(std::is_standard_layout_v<int&>); + // expected-error@-1 {{static assertion failed due to requirement 'std::is_standard_layout_v<int &>'}} \ + // expected-note@-1 {{'int &' is not standard-layout}} \ + // expected-note@-1 {{because it is a reference type}} + static_assert(!std::is_empty<int>::value); static_assert(std::is_empty<int&>::value); @@ -191,6 +231,16 @@ namespace test_namespace { // expected-note@-1 {{'int &' is not trivially copyable}} \ // expected-note@-1 {{because it is a reference type}} + static_assert(is_standard_layout<int&>::value); + // expected-error-re@-1 {{static assertion failed due to requirement '{{.*}}is_standard_layout<int &>::value'}} \ + // expected-note@-1 {{'int &' is not standard-layout}} \ + // expected-note@-1 {{because it is a reference type}} + + static_assert(is_standard_layout_v<int&>); + // expected-error@-1 {{static assertion failed due to requirement 'is_standard_layout_v<int &>'}} \ + // expected-note@-1 {{'int &' is not standard-layout}} \ + // expected-note@-1 {{because it is a reference type}} + static_assert(is_assignable<int&, void>::value); // expected-error-re@-1 {{static assertion failed due to requirement '{{.*}}is_assignable<int &, void>::value'}} \ // expected-error@-1 {{assigning to 'int' from incompatible type 'void'}} diff --git a/clang/test/SemaCXX/type-traits-unsatisfied-diags.cpp b/clang/test/SemaCXX/type-traits-unsatisfied-diags.cpp index f1624f0a6e553..cc923d206ab35 100644 --- a/clang/test/SemaCXX/type-traits-unsatisfied-diags.cpp +++ b/clang/test/SemaCXX/type-traits-unsatisfied-diags.cpp @@ -634,3 +634,136 @@ namespace is_empty_tests { // expected-note@#e-DependentBitField {{'DependentBitField<2>' defined here}} } + +namespace standard_layout_tests { +struct WithVirtual { // #sl-Virtual + virtual void foo(); // #sl-Virtual-Foo +}; +static_assert(__is_standard_layout(WithVirtual)); +// expected-error@-1 {{static assertion failed due to requirement '__is_standard_layout(standard_layout_tests::WithVirtual)'}} \ +// expected-note@-1 {{'WithVirtual' is not standard-layout}} \ +// expected-note@-1 {{because it has a virtual function 'foo'}} \ +// expected-note@#sl-Virtual-Foo {{'foo' defined here}} \ +// expected-note@#sl-Virtual {{'WithVirtual' defined here}} + +struct MixedAccess { // #sl-Mixed +public: + int a; // #sl-MixedF1 +private: + int b; // #sl-MixedF2 +}; +static_assert(__is_standard_layout(MixedAccess)); +// expected-error@-1 {{static assertion failed due to requirement '__is_standard_layout(standard_layout_tests::MixedAccess)'}} \ +// expected-note@-1 {{'MixedAccess' is not standard-layout}} \ +// expected-note@-1 {{because it has mixed access specifiers}} \ +// expected-note@#sl-MixedF1 {{'a' defined here}} +// expected-note@#sl-MixedF2 {{field 'b' has a diff erent access specifier than field 'a'}} +// expected-note@#sl-Mixed {{'MixedAccess' defined here}} + +struct VirtualBase { virtual ~VirtualBase(); }; // #sl-VirtualBase +struct VB : virtual VirtualBase {}; // #sl-VB +static_assert(__is_standard_layout(VB)); +// expected-error@-1 {{static assertion failed due to requirement '__is_standard_layout(standard_layout_tests::VB)'}} \ +// expected-note@-1 {{'VB' is not standard-layout}} \ +// expected-note@-1 {{because it has a virtual base 'VirtualBase'}} \ +// expected-note@-1 {{because it has a non-standard-layout base 'VirtualBase'}} \ +// expected-note@-1 {{because it has a virtual function '~VB'}} \ +// expected-note@#sl-VB {{'VB' defined here}} +// expected-note@#sl-VB {{'~VB' defined here}} + +union U { // #sl-U +public: + int x; // #sl-UF1 +private: + int y; // #sl-UF2 +}; +static_assert(__is_standard_layout(U)); +// expected-error@-1 {{static assertion failed due to requirement '__is_standard_layout(standard_layout_tests::U)'}} \ +// expected-note@-1 {{'U' is not standard-layout}} \ +// expected-note@-1 {{because it has mixed access specifiers}} +// expected-note@#sl-UF1 {{'x' defined here}} +// expected-note@#sl-UF2 {{field 'y' has a diff erent access specifier than field 'x'}} +// expected-note@#sl-U {{'U' defined here}} + +// Single base class is OK +struct BaseClass{ int a; }; // #sl-BaseClass +struct DerivedOK : BaseClass {}; // #sl-DerivedOK +static_assert(__is_standard_layout(DerivedOK)); + +// Primitive types should be standard layout +static_assert(__is_standard_layout(int)); // #sl-Int +static_assert(__is_standard_layout(float)); // #sl-Float + +// Multi-level inheritance: Non-standard layout +struct Base1 { int a; }; // #sl-Base1 +struct Base2 { int b; }; // #sl-Base2 +struct DerivedClass : Base1, Base2 {}; // #sl-DerivedClass +static_assert(__is_standard_layout(DerivedClass)); +// expected-error@-1 {{static assertion failed due to requirement '__is_standard_layout(standard_layout_tests::DerivedClass)'}} \ +// expected-note@-1 {{'DerivedClass' is not standard-layout}} \ +// expected-note@-1 {{because it has multiple base classes with data members}} \ +// expected-note@#sl-DerivedClass {{'DerivedClass' defined here}} + +// Inheritance hierarchy with multiple classes having data members +struct BaseA { int a; }; // #sl-BaseA +struct BaseB : BaseA {}; // inherits BaseA, has no new members +struct BaseC: BaseB { int c; }; // #sl-BaseC +static_assert(__is_standard_layout(BaseC)); +// expected-error@-1 {{static assertion failed due to requirement '__is_standard_layout(standard_layout_tests::BaseC)'}} \ +// expected-note@-1 {{'BaseC' is not standard-layout}} \ +// expected-note@-1 {{because it has an indirect base 'BaseA' with data members}} \ +// expected-note@#sl-BaseC {{'BaseC' defined here}} \ +// Multiple direct base classes with no data members --> standard layout +struct BaseX {}; // #sl-BaseX +struct BaseY {}; // #sl-BaseY +struct MultiBase : BaseX, BaseY {}; // #sl-MultiBase +static_assert(__is_standard_layout(MultiBase)); + +struct A { + int x; +}; + +struct B : A { +}; +// Indirect base with data members +struct C : B { int y; }; // #sl-C +static_assert(__is_standard_layout(C)); +// expected-error@-1 {{static assertion failed due to requirement '__is_standard_layout(standard_layout_tests::C)'}} \ +// expected-note@-1 {{'C' is not standard-layout}} \ +// expected-note@-1 {{because it has an indirect base 'A' with data members}} \ +// expected-note@#sl-C {{'C' defined here}} + +struct D { + union { int a; float b; }; + }; // #sl-D +static_assert(__is_standard_layout(D)); // no diagnostics + +// E inherits D but adds a new member +struct E : D { int x; }; // #sl-E +static_assert(__is_standard_layout(E)); +// expected-error@-1 {{static assertion failed due to requirement '__is_standard_layout(standard_layout_tests::E)'}} \ +// expected-note@-1 {{'E' is not standard-layout}} \ +// expected-note@-1 {{because it has an indirect base 'D' with data members}} \ +// expected-note@#sl-E {{'E' defined here}} + +// F inherits D but only an unnamed bitfield +// This should still fail because F ends up with a +// base class with a data member and its own unnamed bitfield +// which is not allowed in standard layout +struct F : D { int : 0; }; // #sl-F +static_assert(__is_standard_layout(F)); +// expected-error@-1 {{static assertion failed due to requirement '__is_standard_layout(standard_layout_tests::F)'}} \ +// expected-note@-1 {{'F' is not standard-layout}} \ +// expected-note@#sl-F {{'F' defined here}} + +struct Empty {}; +struct G { Empty a, b; }; // #sl-G +static_assert(__is_standard_layout(G)); // no diagnostics + +struct H { Empty a; int x; }; // #sl-H +static_assert(__is_standard_layout(H)); // no diagnostics + + struct I { Empty a; int : 0; int x; }; // #sl-I +static_assert(__is_standard_layout(I)); // no diagnostics +} + _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits