https://github.com/snarang181 updated https://github.com/llvm/llvm-project/pull/143722
>From e96c9c940bbfaf9da752c3537ea8bd023b8b15aa Mon Sep 17 00:00:00 2001 From: Samarth Narang <snar...@umass.edu> Date: Mon, 2 Jun 2025 19:30:39 -0400 Subject: [PATCH 1/2] Add std layout diagnostics Add diagnostic test cases --- .../clang/Basic/DiagnosticSemaKinds.td | 8 +- clang/lib/Sema/SemaTypeTraits.cpp | 100 ++++++++++++++++++ .../type-traits-unsatisfied-diags-std.cpp | 54 ++++++++++ .../SemaCXX/type-traits-unsatisfied-diags.cpp | 80 ++++++++++++++ 4 files changed, 241 insertions(+), 1 deletion(-) diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 95d24e9f1e6b5..dfe68991afb8a 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}|" - "%Constructible{constructible with provided types}" + "%Constructible{constructible with provided types}|" + "%StandardLayout{standard-layout}" "}1">; def note_unsatisfied_trait_reason @@ -1788,6 +1789,11 @@ def note_unsatisfied_trait_reason "%NonReplaceableField{has a non-replaceable member %1 of type %2}|" "%NTCBase{has a non-trivially-copyable base %1}|" "%NTCField{has a non-trivially-copyable member %1 of type %2}|" + "%NonStdLayoutBase{has a non-standard-layout base %1}|" + "%MixedAccess{has mixed access specifiers}|" + "%MultipleDataBase{has multiple base classes with data members}|" + "%VirtualFunction{has virtual functions}|" + "%NonStdLayoutMember{has a non-standard-layout member %1 of type %2}|" "%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 22c690bedc1ed..3134487f7e76d 100644 --- a/clang/lib/Sema/SemaTypeTraits.cpp +++ b/clang/lib/Sema/SemaTypeTraits.cpp @@ -1949,6 +1949,7 @@ static std::optional<TypeTrait> StdNameToTypeTrait(StringRef Name) { .Case("is_replaceable", TypeTrait::UTT_IsReplaceable) .Case("is_trivially_copyable", TypeTrait::UTT_IsTriviallyCopyable) .Case("is_constructible", TypeTrait::TT_IsConstructible) + .Case("is_standard_layout", TypeTrait::UTT_IsStandardLayout) .Default(std::nullopt); } @@ -2340,6 +2341,102 @@ static void DiagnoseNonTriviallyCopyableReason(Sema &SemaRef, SemaRef.Diag(D->getLocation(), diag::note_defined_here) << D; } +static bool hasMixedAccessSpecifier(const CXXRecordDecl *D) { + AccessSpecifier FirstAccess = AS_none; + for (const FieldDecl *Field : D->fields()) { + if (Field->isUnnamedBitField()) + continue; + AccessSpecifier FieldAccess = Field->getAccess(); + if (FirstAccess == AS_none) { + FirstAccess = FieldAccess; + } else if (FieldAccess != FirstAccess) { + return true; + } + } + return false; +} + +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()) { + ++NumBasesWithFields; + break; // Only count the base once. + } + } + } + return NumBasesWithFields > 1; +} + +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::NonStdLayoutBase << B.getType() + << B.getSourceRange(); + } + } + if (hasMixedAccessSpecifier(D)) { + SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason) + << diag::TraitNotSatisfiedReason::MixedAccess; + } + if (hasMultipleDataBaseClassesWithFields(D)) { + SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason) + << diag::TraitNotSatisfiedReason::MultipleDataBase; + } + if (D->isPolymorphic()) { + SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason) + << diag::TraitNotSatisfiedReason::VirtualFunction; + } + for (const FieldDecl *Field : D->fields()) { + if (!Field->getType()->isStandardLayoutType()) { + SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason) + << diag::TraitNotSatisfiedReason::NonStdLayoutMember << Field + << Field->getType() << Field->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()) @@ -2363,6 +2460,9 @@ void Sema::DiagnoseTypeTraitDetails(const Expr *E) { case TT_IsConstructible: DiagnoseNonConstructibleReason(*this, E->getBeginLoc(), Args); 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 a403a0450607a..0aec240672d1c 100644 --- a/clang/test/SemaCXX/type-traits-unsatisfied-diags-std.cpp +++ b/clang/test/SemaCXX/type-traits-unsatisfied-diags-std.cpp @@ -28,6 +28,13 @@ struct is_constructible { template <typename... Args> constexpr bool is_constructible_v = __is_constructible(Args...); + +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 @@ -63,6 +70,15 @@ using is_constructible = __details_is_constructible<Args...>; template <typename... Args> constexpr bool is_constructible_v = __is_constructible(Args...); + +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 @@ -101,6 +117,15 @@ using is_constructible = __details_is_constructible<Args...>; template <typename... Args> constexpr bool is_constructible_v = is_constructible<Args...>::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 } @@ -128,6 +153,21 @@ static_assert(std::is_trivially_copyable_v<int&>); // 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_constructible<int, int>::value); static_assert(std::is_constructible<void>::value); @@ -163,9 +203,21 @@ namespace test_namespace { static_assert(is_constructible_v<void>); // expected-error@-1 {{static assertion failed due to requirement 'is_constructible_v<void>'}} \ // expected-note@-1 {{because it is a cv void 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}} + } + namespace concepts { template <typename T> requires std::is_trivially_relocatable<T>::value void f(); // #cand1 @@ -258,3 +310,5 @@ static_assert(std::is_replaceable_v<int&>); // expected-error@-1 {{static assertion failed due to requirement 'std::is_replaceable_v<int &>'}} \ // expected-note@-1 {{'int &' is not replaceable}} \ // expected-note@-1 {{because it is a reference type}} + + diff --git a/clang/test/SemaCXX/type-traits-unsatisfied-diags.cpp b/clang/test/SemaCXX/type-traits-unsatisfied-diags.cpp index d0b3f294fbcab..d3f9b64fa2bfc 100644 --- a/clang/test/SemaCXX/type-traits-unsatisfied-diags.cpp +++ b/clang/test/SemaCXX/type-traits-unsatisfied-diags.cpp @@ -550,3 +550,83 @@ static_assert(__is_constructible(void (int, float))); // expected-error@-1 {{static assertion failed due to requirement '__is_constructible(void (int, float))'}} \ // expected-note@-1 {{because it is a function type}} } + +namespace standard_layout_tests { + struct WithVirtual { // #sl-Virtual + virtual void 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 virtual functions}} \ +// expected-note@#sl-Virtual {{'WithVirtual' defined here}} + +struct MixedAccess { // #sl-Mixed +public: + int a; +private: + int b; +}; +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-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 virtual functions}} +// expected-note@#sl-VB {{'VB' defined here}} + +union U { // #sl-U +public: + int x; +private: + int y; +}; +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-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@#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)); + +} >From 0754b80e3c20eb6e5c550bb805c541ab7fdef936 Mon Sep 17 00:00:00 2001 From: Samarth Narang <snar...@umass.edu> Date: Thu, 12 Jun 2025 22:48:48 -0400 Subject: [PATCH 2/2] Add more descriptive messages for indirect base class inheritance --- .../clang/Basic/DiagnosticSemaKinds.td | 1 + clang/lib/Sema/SemaTypeTraits.cpp | 31 +++++++++++++++++++ .../SemaCXX/type-traits-unsatisfied-diags.cpp | 1 + 3 files changed, 33 insertions(+) diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index dfe68991afb8a..9d4695763b832 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -1794,6 +1794,7 @@ def note_unsatisfied_trait_reason "%MultipleDataBase{has multiple base classes with data members}|" "%VirtualFunction{has virtual functions}|" "%NonStdLayoutMember{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 3134487f7e76d..9b5c7e0f91dd6 100644 --- a/clang/lib/Sema/SemaTypeTraits.cpp +++ b/clang/lib/Sema/SemaTypeTraits.cpp @@ -2407,6 +2407,37 @@ static void DiagnoseNonStandardLayoutReason(Sema &SemaRef, SourceLocation Loc, << Field->getType() << Field->getSourceRange(); } } + + // if this class and an indirect base + // both have non-static data members, grab the first such base. + if (D->hasDirectFields()) { + SmallVector<const CXXRecordDecl *, 4> Records; + + // Recursive lambda to collect all bases that declare fields + std::function<void(const CXXRecordDecl *)> collect = + [&](const CXXRecordDecl *R) { + for (const CXXBaseSpecifier &B : R->bases()) { + const auto *BR = B.getType()->getAsCXXRecordDecl(); + if (!BR || !BR->hasDefinition()) + continue; + if (BR->hasDirectFields()) + Records.push_back(BR); + // Recurse into the base class. + collect(BR); + } + }; + + // Collect all bases that declare fields. + collect(D); + + // If more than one record has fields, then the layout is non-standard. + if (!Records.empty()) { + const CXXRecordDecl *Indirect = Records.front(); + SemaRef.Diag(Loc, diag::note_unsatisfied_trait_reason) + << diag::TraitNotSatisfiedReason::IndirectBaseWithFields << Indirect + << Indirect->getSourceRange(); + } + } } static void DiagnoseNonStandardLayoutReason(Sema &SemaRef, SourceLocation Loc, diff --git a/clang/test/SemaCXX/type-traits-unsatisfied-diags.cpp b/clang/test/SemaCXX/type-traits-unsatisfied-diags.cpp index d3f9b64fa2bfc..9a934187a13ee 100644 --- a/clang/test/SemaCXX/type-traits-unsatisfied-diags.cpp +++ b/clang/test/SemaCXX/type-traits-unsatisfied-diags.cpp @@ -621,6 +621,7 @@ 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 _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits