Skip to content

Commit 5d7357c

Browse files
authored
[Clang] Fix definition of layout-compatible to ignore empty classes (#92103)
Also changes the behaviour of `__builtin_is_layout_compatible` None of the historic nor the current definition of layout-compatible classes mention anything about base classes (other than implicitly through being standard-layout) and are defined in terms of members, not direct members.
1 parent 130c135 commit 5d7357c

File tree

5 files changed

+84
-50
lines changed

5 files changed

+84
-50
lines changed

clang/include/clang/AST/DeclCXX.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1206,6 +1206,13 @@ class CXXRecordDecl : public RecordDecl {
12061206
return D.HasPublicFields || D.HasProtectedFields || D.HasPrivateFields;
12071207
}
12081208

1209+
/// If this is a standard-layout class or union, any and all data members will
1210+
/// be declared in the same type.
1211+
///
1212+
/// This retrieves the type where any fields are declared,
1213+
/// or the current class if there is no class with fields.
1214+
const CXXRecordDecl *getStandardLayoutBaseWithFields() const;
1215+
12091216
/// Whether this class is polymorphic (C++ [class.virtual]),
12101217
/// which means that the class contains or inherits a virtual function.
12111218
bool isPolymorphic() const { return data().Polymorphic; }

clang/lib/AST/DeclCXX.cpp

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -561,6 +561,42 @@ void CXXRecordDecl::addedClassSubobject(CXXRecordDecl *Subobj) {
561561
data().StructuralIfLiteral = false;
562562
}
563563

564+
const CXXRecordDecl *CXXRecordDecl::getStandardLayoutBaseWithFields() const {
565+
assert(
566+
isStandardLayout() &&
567+
"getStandardLayoutBaseWithFields called on a non-standard-layout type");
568+
#ifdef EXPENSIVE_CHECKS
569+
{
570+
unsigned NumberOfBasesWithFields = 0;
571+
if (!field_empty())
572+
++NumberOfBasesWithFields;
573+
llvm::SmallPtrSet<const CXXRecordDecl *, 8> UniqueBases;
574+
forallBases([&](const CXXRecordDecl *Base) -> bool {
575+
if (!Base->field_empty())
576+
++NumberOfBasesWithFields;
577+
assert(
578+
UniqueBases.insert(Base->getCanonicalDecl()).second &&
579+
"Standard layout struct has multiple base classes of the same type");
580+
return true;
581+
});
582+
assert(NumberOfBasesWithFields <= 1 &&
583+
"Standard layout struct has fields declared in more than one class");
584+
}
585+
#endif
586+
if (!field_empty())
587+
return this;
588+
const CXXRecordDecl *Result = this;
589+
forallBases([&](const CXXRecordDecl *Base) -> bool {
590+
if (!Base->field_empty()) {
591+
// This is the base where the fields are declared; return early
592+
Result = Base;
593+
return false;
594+
}
595+
return true;
596+
});
597+
return Result;
598+
}
599+
564600
bool CXXRecordDecl::hasConstexprDestructor() const {
565601
auto *Dtor = getDestructor();
566602
return Dtor ? Dtor->isConstexpr() : defaultedDestructorIsConstexpr();

clang/lib/Sema/SemaChecking.cpp

Lines changed: 24 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -13706,10 +13706,11 @@ void Sema::DiagnoseSelfMove(const Expr *LHSExpr, const Expr *RHSExpr,
1370613706

1370713707
//===--- Layout compatibility ----------------------------------------------//
1370813708

13709-
static bool isLayoutCompatible(ASTContext &C, QualType T1, QualType T2);
13709+
static bool isLayoutCompatible(const ASTContext &C, QualType T1, QualType T2);
1371013710

1371113711
/// Check if two enumeration types are layout-compatible.
13712-
static bool isLayoutCompatible(ASTContext &C, EnumDecl *ED1, EnumDecl *ED2) {
13712+
static bool isLayoutCompatible(const ASTContext &C, const EnumDecl *ED1,
13713+
const EnumDecl *ED2) {
1371313714
// C++11 [dcl.enum] p8:
1371413715
// Two enumeration types are layout-compatible if they have the same
1371513716
// underlying type.
@@ -13720,8 +13721,8 @@ static bool isLayoutCompatible(ASTContext &C, EnumDecl *ED1, EnumDecl *ED2) {
1372013721
/// Check if two fields are layout-compatible.
1372113722
/// Can be used on union members, which are exempt from alignment requirement
1372213723
/// of common initial sequence.
13723-
static bool isLayoutCompatible(ASTContext &C, FieldDecl *Field1,
13724-
FieldDecl *Field2,
13724+
static bool isLayoutCompatible(const ASTContext &C, const FieldDecl *Field1,
13725+
const FieldDecl *Field2,
1372513726
bool AreUnionMembers = false) {
1372613727
[[maybe_unused]] const Type *Field1Parent =
1372713728
Field1->getParent()->getTypeForDecl();
@@ -13764,60 +13765,33 @@ static bool isLayoutCompatible(ASTContext &C, FieldDecl *Field1,
1376413765

1376513766
/// Check if two standard-layout structs are layout-compatible.
1376613767
/// (C++11 [class.mem] p17)
13767-
static bool isLayoutCompatibleStruct(ASTContext &C, RecordDecl *RD1,
13768-
RecordDecl *RD2) {
13769-
// If both records are C++ classes, check that base classes match.
13770-
if (const CXXRecordDecl *D1CXX = dyn_cast<CXXRecordDecl>(RD1)) {
13771-
// If one of records is a CXXRecordDecl we are in C++ mode,
13772-
// thus the other one is a CXXRecordDecl, too.
13773-
const CXXRecordDecl *D2CXX = cast<CXXRecordDecl>(RD2);
13774-
// Check number of base classes.
13775-
if (D1CXX->getNumBases() != D2CXX->getNumBases())
13776-
return false;
13768+
static bool isLayoutCompatibleStruct(const ASTContext &C, const RecordDecl *RD1,
13769+
const RecordDecl *RD2) {
13770+
// Get to the class where the fields are declared
13771+
if (const CXXRecordDecl *D1CXX = dyn_cast<CXXRecordDecl>(RD1))
13772+
RD1 = D1CXX->getStandardLayoutBaseWithFields();
1377713773

13778-
// Check the base classes.
13779-
for (CXXRecordDecl::base_class_const_iterator
13780-
Base1 = D1CXX->bases_begin(),
13781-
BaseEnd1 = D1CXX->bases_end(),
13782-
Base2 = D2CXX->bases_begin();
13783-
Base1 != BaseEnd1;
13784-
++Base1, ++Base2) {
13785-
if (!isLayoutCompatible(C, Base1->getType(), Base2->getType()))
13786-
return false;
13787-
}
13788-
} else if (const CXXRecordDecl *D2CXX = dyn_cast<CXXRecordDecl>(RD2)) {
13789-
// If only RD2 is a C++ class, it should have zero base classes.
13790-
if (D2CXX->getNumBases() > 0)
13791-
return false;
13792-
}
13774+
if (const CXXRecordDecl *D2CXX = dyn_cast<CXXRecordDecl>(RD2))
13775+
RD2 = D2CXX->getStandardLayoutBaseWithFields();
1379313776

1379413777
// Check the fields.
13795-
RecordDecl::field_iterator Field2 = RD2->field_begin(),
13796-
Field2End = RD2->field_end(),
13797-
Field1 = RD1->field_begin(),
13798-
Field1End = RD1->field_end();
13799-
for ( ; Field1 != Field1End && Field2 != Field2End; ++Field1, ++Field2) {
13800-
if (!isLayoutCompatible(C, *Field1, *Field2))
13801-
return false;
13802-
}
13803-
if (Field1 != Field1End || Field2 != Field2End)
13804-
return false;
13805-
13806-
return true;
13778+
return llvm::equal(RD1->fields(), RD2->fields(),
13779+
[&C](const FieldDecl *F1, const FieldDecl *F2) -> bool {
13780+
return isLayoutCompatible(C, F1, F2);
13781+
});
1380713782
}
1380813783

1380913784
/// Check if two standard-layout unions are layout-compatible.
1381013785
/// (C++11 [class.mem] p18)
13811-
static bool isLayoutCompatibleUnion(ASTContext &C, RecordDecl *RD1,
13812-
RecordDecl *RD2) {
13813-
llvm::SmallPtrSet<FieldDecl *, 8> UnmatchedFields;
13786+
static bool isLayoutCompatibleUnion(const ASTContext &C, const RecordDecl *RD1,
13787+
const RecordDecl *RD2) {
13788+
llvm::SmallPtrSet<const FieldDecl *, 8> UnmatchedFields;
1381413789
for (auto *Field2 : RD2->fields())
1381513790
UnmatchedFields.insert(Field2);
1381613791

1381713792
for (auto *Field1 : RD1->fields()) {
13818-
llvm::SmallPtrSet<FieldDecl *, 8>::iterator
13819-
I = UnmatchedFields.begin(),
13820-
E = UnmatchedFields.end();
13793+
auto I = UnmatchedFields.begin();
13794+
auto E = UnmatchedFields.end();
1382113795

1382213796
for ( ; I != E; ++I) {
1382313797
if (isLayoutCompatible(C, Field1, *I, /*IsUnionMember=*/true)) {
@@ -13834,8 +13808,8 @@ static bool isLayoutCompatibleUnion(ASTContext &C, RecordDecl *RD1,
1383413808
return UnmatchedFields.empty();
1383513809
}
1383613810

13837-
static bool isLayoutCompatible(ASTContext &C, RecordDecl *RD1,
13838-
RecordDecl *RD2) {
13811+
static bool isLayoutCompatible(const ASTContext &C, const RecordDecl *RD1,
13812+
const RecordDecl *RD2) {
1383913813
if (RD1->isUnion() != RD2->isUnion())
1384013814
return false;
1384113815

@@ -13846,7 +13820,7 @@ static bool isLayoutCompatible(ASTContext &C, RecordDecl *RD1,
1384613820
}
1384713821

1384813822
/// Check if two types are layout-compatible in C++11 sense.
13849-
static bool isLayoutCompatible(ASTContext &C, QualType T1, QualType T2) {
13823+
static bool isLayoutCompatible(const ASTContext &C, QualType T1, QualType T2) {
1385013824
if (T1.isNull() || T2.isNull())
1385113825
return false;
1385213826

clang/test/SemaCXX/type-traits.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1738,6 +1738,11 @@ struct CStructWithFMA2 {
17381738
int f[];
17391739
};
17401740

1741+
template<int N>
1742+
struct UniqueEmpty {};
1743+
template<typename... Bases>
1744+
struct D : Bases... {};
1745+
17411746
void is_layout_compatible(int n)
17421747
{
17431748
static_assert(__is_layout_compatible(void, void));
@@ -1841,6 +1846,12 @@ void is_layout_compatible(int n)
18411846
static_assert(!__is_layout_compatible(EnumClassLayout, int));
18421847
static_assert(!__is_layout_compatible(EnumForward, int));
18431848
static_assert(!__is_layout_compatible(EnumClassForward, int));
1849+
static_assert(__is_layout_compatible(CStruct, D<CStruct>));
1850+
static_assert(__is_layout_compatible(CStruct, D<UniqueEmpty<0>, CStruct>));
1851+
static_assert(__is_layout_compatible(CStruct, D<UniqueEmpty<0>, D<UniqueEmpty<1>, CStruct>, D<UniqueEmpty<2>>>));
1852+
static_assert(__is_layout_compatible(CStruct, D<CStructWithQualifiers>));
1853+
static_assert(__is_layout_compatible(CStruct, D<UniqueEmpty<0>, CStructWithQualifiers>));
1854+
static_assert(__is_layout_compatible(CStructWithQualifiers, D<UniqueEmpty<0>, D<UniqueEmpty<1>, CStruct>, D<UniqueEmpty<2>>>));
18441855
}
18451856

18461857
namespace IPIBO {

llvm/include/llvm/ADT/STLExtras.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2027,6 +2027,12 @@ template <typename L, typename R> bool equal(L &&LRange, R &&RRange) {
20272027
adl_end(RRange));
20282028
}
20292029

2030+
template <typename L, typename R, typename BinaryPredicate>
2031+
bool equal(L &&LRange, R &&RRange, BinaryPredicate P) {
2032+
return std::equal(adl_begin(LRange), adl_end(LRange), adl_begin(RRange),
2033+
adl_end(RRange), P);
2034+
}
2035+
20302036
/// Returns true if all elements in Range are equal or when the Range is empty.
20312037
template <typename R> bool all_equal(R &&Range) {
20322038
auto Begin = adl_begin(Range);

0 commit comments

Comments
 (0)