[Clang] Implement diagnostics for why std::is_standard_layout is false (#144161)
This commit is contained in:
@@ -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 different 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}|"
|
||||
|
||||
@@ -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 different 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;
|
||||
}
|
||||
|
||||
@@ -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'}}
|
||||
|
||||
@@ -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 different 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 different 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
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user