https://github.com/love1angel updated https://github.com/llvm/llvm-project/pull/185830
>From 394beb2809e0c939632807a73450f83bffb07c5d Mon Sep 17 00:00:00 2001 From: Peng Xie <[email protected]> Date: Wed, 11 Mar 2026 14:45:40 +0800 Subject: [PATCH 1/2] [Clang] Implement P3074R7: trivial unions (C++26) P3074R7 makes union special member functions trivial by default in C++26, regardless of variant member triviality. A defaulted destructor for a union is now defined as deleted only if its default constructor is user-provided (or deleted/ambiguous), or if a variant member has both a default member initializer and a non-trivial destructor. The pre-existing rule that a deleted or inaccessible member destructor causes the union destructor to be deleted (p7.2) is preserved. This patch does not implement the implicit-lifetime-start semantics from P3074R7 p4, which was reworked by P3726R1 into __builtin_start_lifetime. Defines __cpp_trivial_union=202502L (bumped to 202602L by P3726R1). --- .../clang/Basic/DiagnosticSemaKinds.td | 6 + clang/lib/AST/DeclCXX.cpp | 31 ++- clang/lib/Frontend/InitPreprocessor.cpp | 4 + clang/lib/Sema/SemaDeclCXX.cpp | 135 +++++++++- clang/test/CXX/drs/cwg14xx.cpp | 60 +++-- clang/test/CXX/special/class.ctor/p5-0x.cpp | 16 +- clang/test/CXX/special/class.ctor/p6-0x.cpp | 31 +-- clang/test/CXX/special/class.dtor/p5-0x.cpp | 33 +-- clang/test/SemaCXX/cxx26-trivial-union.cpp | 253 ++++++++++++++++++ 9 files changed, 500 insertions(+), 69 deletions(-) create mode 100644 clang/test/SemaCXX/cxx26-trivial-union.cpp diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 0c25eb2443d5e..0cb33bc9511c3 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -6406,6 +6406,12 @@ def note_enforce_read_only_placement : Note<"type was declared read-only here">; def note_deleted_dtor_no_operator_delete : Note< "virtual destructor requires an unambiguous, accessible 'operator delete'">; +def note_deleted_dtor_default_ctor : Note< + "destructor of union %0 is implicitly deleted because " + "%select{it has no default constructor|" + "its default constructor is a deleted function|" + "overload resolution to default-initialize it is ambiguous|" + "its default constructor is not trivial}1">; def note_deleted_special_member_class_subobject : Note< "%select{default constructor of|copy constructor of|move constructor of|" "copy assignment operator of|move assignment operator of|destructor of|" diff --git a/clang/lib/AST/DeclCXX.cpp b/clang/lib/AST/DeclCXX.cpp index 083c53e28cb91..7c1287e31df17 100644 --- a/clang/lib/AST/DeclCXX.cpp +++ b/clang/lib/AST/DeclCXX.cpp @@ -1180,7 +1180,12 @@ void CXXRecordDecl::addedMember(Decl *D) { // C++11 [class]p5: // A default constructor is trivial if [...] no non-static data member // of its class has a brace-or-equal-initializer. - data().HasTrivialSpecialMembers &= ~SMF_DefaultConstructor; + // C++26 [class.default.ctor]p3: + // A default constructor is trivial if [...] either X is a union or + // no non-static data member of its class has a + // brace-or-equal-initializer. + if (!(isUnion() && getASTContext().getLangOpts().CPlusPlus26)) + data().HasTrivialSpecialMembers &= ~SMF_DefaultConstructor; // C++11 [dcl.init.aggr]p1: // An aggregate is a [...] class with [...] no @@ -1239,7 +1244,11 @@ void CXXRecordDecl::addedMember(Decl *D) { if (FieldRec->hasNonTrivialMoveAssignment()) data().DefaultedMoveAssignmentIsDeleted = true; if (FieldRec->hasNonTrivialDestructor()) { - data().DefaultedDestructorIsDeleted = true; + // In C++26, the destructor of a union is not deleted merely + // because a variant member has a non-trivial destructor. + // Deletion is determined later by Sema (see C++26 [class.dtor]p7). + if (!Context.getLangOpts().CPlusPlus26) + data().DefaultedDestructorIsDeleted = true; // C++20 [dcl.constexpr]p5: // The definition of a constexpr destructor whose function-body is // not = delete shall additionally satisfy... @@ -1267,7 +1276,13 @@ void CXXRecordDecl::addedMember(Decl *D) { // -- for all the non-static data members of its class that are of // class type (or array thereof), each such class has a trivial // default constructor. - if (!FieldRec->hasTrivialDefaultConstructor()) + // C++26 [class.default.ctor]p3: + // A default constructor is trivial if [...] either X is a union + // or for all the non-static data members of its class that are + // of class type [...] each such class has a trivial default + // constructor. + if (!FieldRec->hasTrivialDefaultConstructor() && + !(isUnion() && Context.getLangOpts().CPlusPlus26)) data().HasTrivialSpecialMembers &= ~SMF_DefaultConstructor; // C++0x [class.copy]p13: @@ -1305,9 +1320,15 @@ void CXXRecordDecl::addedMember(Decl *D) { if (!FieldRec->hasTrivialMoveAssignment()) data().HasTrivialSpecialMembers &= ~SMF_MoveAssignment; - if (!FieldRec->hasTrivialDestructor()) + // C++26 [class.dtor]p8: + // A destructor is trivial if [...] either X is a union or for + // all the non-static data members of its class that are of class + // type [...] each such class has a trivial destructor. + if (!FieldRec->hasTrivialDestructor() && + !(isUnion() && Context.getLangOpts().CPlusPlus26)) data().HasTrivialSpecialMembers &= ~SMF_Destructor; - if (!FieldRec->hasTrivialDestructorForCall()) + if (!FieldRec->hasTrivialDestructorForCall() && + !(isUnion() && Context.getLangOpts().CPlusPlus26)) data().HasTrivialSpecialMembersForCall &= ~SMF_Destructor; if (!FieldRec->hasIrrelevantDestructor()) data().HasIrrelevantDestructor = false; diff --git a/clang/lib/Frontend/InitPreprocessor.cpp b/clang/lib/Frontend/InitPreprocessor.cpp index 1ccd74314f373..a00b34625510b 100644 --- a/clang/lib/Frontend/InitPreprocessor.cpp +++ b/clang/lib/Frontend/InitPreprocessor.cpp @@ -746,6 +746,10 @@ static void InitializeCPlusPlusFeatureTestMacros(const LangOptions &LangOpts, Builder.defineMacro("__cpp_variadic_friend", "202403L"); Builder.defineMacro("__cpp_trivial_relocatability", "202502L"); + // C++26 features. + if (LangOpts.CPlusPlus26) + Builder.defineMacro("__cpp_trivial_union", "202502L"); + if (LangOpts.Char8) Builder.defineMacro("__cpp_char8_t", "202207L"); Builder.defineMacro("__cpp_impl_destroying_delete", "201806L"); diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp index 2ae6e5de0e3ee..45bea4656ddad 100644 --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -9615,6 +9615,11 @@ bool SpecialMemberDeletionInfo::shouldDeleteForSubobjectCall( if (SMOR.getKind() == Sema::SpecialMemberOverloadResult::NoMemberOrDeleted) { if (CSM == CXXSpecialMemberKind::DefaultConstructor && Field && Field->getParent()->isUnion()) { + // In C++26, a union's defaulted default constructor is never + // deleted due to a variant member with a non-trivial default + // constructor (see C++26 [class.default.ctor]p3). + if (S.getLangOpts().CPlusPlus26) + return false; // [class.default.ctor]p2: // A defaulted default constructor for class X is defined as deleted if // - X is a union that has a variant member with a non-trivial default @@ -9637,6 +9642,11 @@ bool SpecialMemberDeletionInfo::shouldDeleteForSubobjectCall( // destructor is never actually called, but is semantically checked as // if it were. if (CSM == CXXSpecialMemberKind::DefaultConstructor) { + // In C++26, a union's defaulted default constructor is never + // deleted due to a variant member with a non-trivial default + // constructor (see C++26 [class.default.ctor]p3). + if (S.getLangOpts().CPlusPlus26) + return false; // [class.default.ctor]p2: // A defaulted default constructor for class X is defined as deleted if // - X is a union that has a variant member with a non-trivial default @@ -9645,6 +9655,12 @@ bool SpecialMemberDeletionInfo::shouldDeleteForSubobjectCall( const auto *RD = cast<CXXRecordDecl>(Field->getParent()); if (!RD->hasInClassInitializer()) DiagKind = NonTrivialDecl; + } else if (CSM == CXXSpecialMemberKind::Destructor && + S.getLangOpts().CPlusPlus26) { + // In C++26, a union's destructor is not deleted merely because a + // variant member has a non-trivial destructor. Deletion is determined + // by the new union-specific rules (see C++26 [class.dtor]p7). + return false; } else { DiagKind = NonTrivialDecl; } @@ -9806,6 +9822,13 @@ bool SpecialMemberDeletionInfo::shouldDeleteForField(FieldDecl *FD) { if (inUnion() && shouldDeleteForVariantPtrAuthMember(FD)) return true; + // In C++26, a union's defaulted default constructor is trivially defined + // and never deleted due to variant member properties + // (see C++26 [class.default.ctor]p3). + if (inUnion() && S.getLangOpts().CPlusPlus26 && + CSM == CXXSpecialMemberKind::DefaultConstructor) + return false; + if (CSM == CXXSpecialMemberKind::DefaultConstructor) { // For a default constructor, all references must be initialized in-class // and, if a union, it must have a non-const member. @@ -9884,7 +9907,8 @@ bool SpecialMemberDeletionInfo::shouldDeleteForField(FieldDecl *FD) { // At least one member in each anonymous union must be non-const if (CSM == CXXSpecialMemberKind::DefaultConstructor && - AllVariantFieldsAreConst && !FieldRecord->field_empty()) { + AllVariantFieldsAreConst && !FieldRecord->field_empty() && + !S.getLangOpts().CPlusPlus26) { if (Diagnose) S.Diag(FieldRecord->getLocation(), diag::note_deleted_default_ctor_all_const) @@ -9914,6 +9938,8 @@ bool SpecialMemberDeletionInfo::shouldDeleteForAllConstMembers() { // default constructor. Don't do that. if (CSM == CXXSpecialMemberKind::DefaultConstructor && inUnion() && AllFieldsAreConst) { + if (S.getLangOpts().CPlusPlus26) + return false; bool AnyFields = false; for (auto *F : MD->getParent()->fields()) if ((AnyFields = !F->isUnnamedBitField())) @@ -10034,6 +10060,102 @@ bool Sema::ShouldDeleteSpecialMember(CXXMethodDecl *MD, SpecialMemberDeletionInfo SMI(*this, MD, CSM, ICI, Diagnose); + // C++26 [class.dtor]p7: + // A defaulted destructor for a union X is defined as deleted if + // - X has a default constructor and overload resolution to select a + // constructor to default-initialize an object of type X either fails + // or selects a constructor that is either deleted or not trivial, or + // - X has a variant member of class type M (or possibly + // multi-dimensional array thereof) with a default member initializer + // and M has a non-trivial destructor. + // + // The pre-existing p7.2 (deleted/inaccessible member dtor) still applies. + // We check the two union-specific bullets here and fall through to the + // normal per-member visit for p7.2. shouldDeleteForSubobjectCall already + // returns false for non-trivial-but-not-deleted dtors in C++26 unions, + // so only truly deleted/inaccessible dtors will cause deletion through + // the member visit. + if (getLangOpts().CPlusPlus26 && RD->isUnion() && + CSM == CXXSpecialMemberKind::Destructor) { + // Check p7 bullet 1: overload resolution for default initialization. + SpecialMemberOverloadResult SMOR = LookupSpecialMember( + RD, CXXSpecialMemberKind::DefaultConstructor, + /*ConstArg=*/false, /*VolatileArg=*/false, /*RValueThis=*/false, + /*ConstThis=*/false, /*VolatileThis=*/false); + bool CtorOK = false; + if (SMOR.getKind() == SpecialMemberOverloadResult::Success) { + auto *Ctor = cast<CXXConstructorDecl>(SMOR.getMethod()); + // In C++26, union default ctors are trivial unless user-provided + // (see C++26 [class.default.ctor]p3). We use !isUserProvided() + // rather than isTrivial() because the triviality flag may not be set + // yet for + // explicitly defaulted ctors at the point DeclareImplicitDestructor + // runs during class completion. + CtorOK = !Ctor->isDeleted() && !Ctor->isUserProvided(); + } else if (SMOR.getKind() == + SpecialMemberOverloadResult::NoMemberOrDeleted) { + if (!SMOR.getMethod()) { + // No default constructor exists (e.g. suppressed by user-declared + // constructors). The union cannot be default-initialized, so + // p7 bullet 1 does not apply. + CtorOK = true; + } + // else: a deleted default ctor was selected → CtorOK stays false. + } + // Ambiguous → CtorOK stays false. + if (!CtorOK) { + // p7 bullet 1: overload resolution is ambiguous, selects a deleted + // ctor, or selects a non-trivial (user-provided) ctor. + if (Diagnose) { + unsigned Reason; + if (SMOR.getKind() == SpecialMemberOverloadResult::Ambiguous) + Reason = 2; // ambiguous + else if (SMOR.getKind() == + SpecialMemberOverloadResult::NoMemberOrDeleted) { + auto *Ctor = SMOR.getMethod(); + Reason = (Ctor && Ctor->isDeleted()) ? 1 : 0; + } else { + Reason = 3; // not trivial + } + Diag(RD->getLocation(), diag::note_deleted_dtor_default_ctor) + << RD << Reason; + } + return true; + } + // Ctor is OK. Check p7 bullet 2: walk variant members (including + // through anonymous structs) for DMI + non-trivial dtor. + std::function<bool(const CXXRecordDecl *)> HasDMINonTrivDtor = + [&](const CXXRecordDecl *Record) -> bool { + for (const auto *FD : Record->fields()) { + if (FD->hasInClassInitializer()) { + QualType FT = Context.getBaseElementType(FD->getType()); + if (const auto *FR = FT->getAsCXXRecordDecl()) { + if (FR->hasNonTrivialDestructor()) { + if (Diagnose) + Diag(FD->getLocation(), + diag::note_deleted_special_member_class_subobject) + << getSpecialMember(MD) << RD << /*IsField*/ true << FD + << /*NonTrivialDecl*/ 4 << /*IsDtorCallInCtor*/ false + << /*IsObjCPtr*/ false; + return true; + } + } + } + if (FD->isAnonymousStructOrUnion()) { + if (const auto *SubRD = FD->getType()->getAsCXXRecordDecl()) { + if (HasDMINonTrivDtor(SubRD)) + return true; + } + } + } + return false; + }; + if (HasDMINonTrivDtor(RD)) + return true; + // Neither p7 bullet 1 nor bullet 2 triggered. Fall through to the + // normal per-member visit for p7.2 (deleted/inaccessible dtor). + } + // Per DR1611, do not consider virtual bases of constructors of abstract // classes, since we are not going to construct them. // Per DR1658, do not consider virtual bases of destructors of abstract @@ -10467,7 +10589,16 @@ bool Sema::SpecialMemberIsTrivial(CXXMethodDecl *MD, CXXSpecialMemberKind CSM, // -- for all of the non-static data members of its class that are of class // type (or array thereof), each such class has a trivial [default // constructor or destructor] - if (!checkTrivialClassMembers(*this, RD, CSM, ConstArg, TAH, Diagnose)) + // + // C++26 [class.default.ctor]p3, [class.dtor]p8: + // A default constructor [destructor] is trivial if [...] either X is + // a union or for all the non-static data members [...] each such class + // has a trivial default constructor [destructor]. + if (RD->isUnion() && getLangOpts().CPlusPlus26 && + (CSM == CXXSpecialMemberKind::DefaultConstructor || + CSM == CXXSpecialMemberKind::Destructor)) { + // Skip member triviality checks for unions in C++26. + } else if (!checkTrivialClassMembers(*this, RD, CSM, ConstArg, TAH, Diagnose)) return false; // C++11 [class.dtor]p5: diff --git a/clang/test/CXX/drs/cwg14xx.cpp b/clang/test/CXX/drs/cwg14xx.cpp index dba1850ce8df9..fa9b866d6bfcd 100644 --- a/clang/test/CXX/drs/cwg14xx.cpp +++ b/clang/test/CXX/drs/cwg14xx.cpp @@ -1,18 +1,18 @@ // RUN: %clang_cc1 -std=c++98 %s -verify=expected -fexceptions -fcxx-exceptions -pedantic-errors -// RUN: %clang_cc1 -std=c++11 %s -verify=expected,cxx11-17,since-cxx11, -fexceptions -fcxx-exceptions -pedantic-errors -// RUN: %clang_cc1 -std=c++14 %s -verify=expected,cxx14-17,cxx11-17,since-cxx11,since-cxx14 -fexceptions -fcxx-exceptions -pedantic-errors -// RUN: %clang_cc1 -std=c++17 %s -verify=expected,cxx14-17,cxx11-17,since-cxx11,since-cxx14 -fexceptions -fcxx-exceptions -pedantic-errors -// RUN: %clang_cc1 -std=c++20 %s -verify=expected,since-cxx11,since-cxx14,since-cxx20 -fexceptions -fcxx-exceptions -pedantic-errors -// RUN: %clang_cc1 -std=c++23 %s -verify=expected,since-cxx11,since-cxx14,since-cxx20 -fexceptions -fcxx-exceptions -pedantic-errors -// RUN: %clang_cc1 -std=c++2c %s -verify=expected,since-cxx11,since-cxx14,since-cxx20 -fexceptions -fcxx-exceptions -pedantic-errors +// RUN: %clang_cc1 -std=c++11 %s -verify=expected,cxx11-17,since-cxx11,precxx26 -fexceptions -fcxx-exceptions -pedantic-errors +// RUN: %clang_cc1 -std=c++14 %s -verify=expected,cxx14-17,cxx11-17,since-cxx11,since-cxx14,precxx26 -fexceptions -fcxx-exceptions -pedantic-errors +// RUN: %clang_cc1 -std=c++17 %s -verify=expected,cxx14-17,cxx11-17,since-cxx11,since-cxx14,precxx26 -fexceptions -fcxx-exceptions -pedantic-errors +// RUN: %clang_cc1 -std=c++20 %s -verify=expected,since-cxx11,since-cxx14,since-cxx20,precxx26 -fexceptions -fcxx-exceptions -pedantic-errors +// RUN: %clang_cc1 -std=c++23 %s -verify=expected,since-cxx11,since-cxx14,since-cxx20,precxx26 -fexceptions -fcxx-exceptions -pedantic-errors +// RUN: %clang_cc1 -std=c++2c %s -verify=expected,since-cxx11,since-cxx14,since-cxx20,since-cxx26 -fexceptions -fcxx-exceptions -pedantic-errors // RUN: %clang_cc1 -std=c++98 %s -verify=expected -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter -// RUN: %clang_cc1 -std=c++11 %s -verify=expected,cxx11-17,since-cxx11, -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter -// RUN: %clang_cc1 -std=c++14 %s -verify=expected,cxx14-17,cxx11-17,since-cxx11,since-cxx14 -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter -// RUN: %clang_cc1 -std=c++17 %s -verify=expected,cxx14-17,cxx11-17,since-cxx11,since-cxx14 -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter -// RUN: %clang_cc1 -std=c++20 %s -verify=expected,since-cxx11,since-cxx14,since-cxx20 -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter -// RUN: %clang_cc1 -std=c++23 %s -verify=expected,since-cxx11,since-cxx14,since-cxx20 -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter -// RUN: %clang_cc1 -std=c++2c %s -verify=expected,since-cxx11,since-cxx14,since-cxx20 -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter +// RUN: %clang_cc1 -std=c++11 %s -verify=expected,cxx11-17,since-cxx11,precxx26 -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter +// RUN: %clang_cc1 -std=c++14 %s -verify=expected,cxx14-17,cxx11-17,since-cxx11,since-cxx14,precxx26 -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter +// RUN: %clang_cc1 -std=c++17 %s -verify=expected,cxx14-17,cxx11-17,since-cxx11,since-cxx14,precxx26 -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter +// RUN: %clang_cc1 -std=c++20 %s -verify=expected,since-cxx11,since-cxx14,since-cxx20,precxx26 -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter +// RUN: %clang_cc1 -std=c++23 %s -verify=expected,since-cxx11,since-cxx14,since-cxx20,precxx26 -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter +// RUN: %clang_cc1 -std=c++2c %s -verify=expected,since-cxx11,since-cxx14,since-cxx20,since-cxx26 -fexceptions -fcxx-exceptions -pedantic-errors -fexperimental-new-constant-interpreter namespace cwg1413 { // cwg1413: 12 template<int> struct Check { @@ -128,7 +128,7 @@ struct A { namespace cwg1460 { // cwg1460: 3.5 #if __cplusplus >= 201103L namespace DRExample { - union A { + union A { // #cwg1460-DRExample-A union {}; // since-cxx11-error@-1 {{declaration does not declare anything}} union {}; @@ -136,6 +136,8 @@ namespace cwg1460 { // cwg1460: 3.5 constexpr A() {} }; constexpr A a = A(); + // since-cxx26-error@-1 {{attempt to use a deleted function}} + // since-cxx26-note@#cwg1460-DRExample-A {{destructor of union 'A' is implicitly deleted because its default constructor is not trivial}} union B { union {}; @@ -277,32 +279,42 @@ namespace cwg1460 { // cwg1460: 3.5 }; static_assert(A().a == 1 && A().b == 2 && A().c == 3, ""); - union B { + union B { // #cwg1460-Overriding-B int a, b = 2, c; constexpr B() : a(1) {} constexpr B(char) : b(4) {} constexpr B(int) : c(3) {} constexpr B(const char*) {} }; + // since-cxx26-note@#cwg1460-Overriding-B 9 {{destructor of union 'B' is implicitly deleted because its default constructor is not trivial}} static_assert(B().a == 1, ""); + // since-cxx26-error@-1 {{attempt to use a deleted function}} static_assert(B().b == 2, ""); - // since-cxx11-error@-1 {{static assertion expression is not an integral constant expression}} - // since-cxx11-note@-2 {{read of member 'b' of union with active member 'a' is not allowed in a constant expression}} + // precxx26-error@-1 {{static assertion expression is not an integral constant expression}} + // precxx26-note@-2 {{read of member 'b' of union with active member 'a' is not allowed in a constant expression}} + // since-cxx26-error@-3 {{attempt to use a deleted function}} static_assert(B('x').a == 0, ""); - // since-cxx11-error@-1 {{static assertion expression is not an integral constant expression}} - // since-cxx11-note@-2 {{read of member 'a' of union with active member 'b' is not allowed in a constant expression}} + // precxx26-error@-1 {{static assertion expression is not an integral constant expression}} + // precxx26-note@-2 {{read of member 'a' of union with active member 'b' is not allowed in a constant expression}} + // since-cxx26-error@-3 {{attempt to use a deleted function}} static_assert(B('x').b == 4, ""); + // since-cxx26-error@-1 {{attempt to use a deleted function}} static_assert(B(123).b == 2, ""); - // since-cxx11-error@-1 {{static assertion expression is not an integral constant expression}} - // since-cxx11-note@-2 {{read of member 'b' of union with active member 'c' is not allowed in a constant expression}} + // precxx26-error@-1 {{static assertion expression is not an integral constant expression}} + // precxx26-note@-2 {{read of member 'b' of union with active member 'c' is not allowed in a constant expression}} + // since-cxx26-error@-3 {{attempt to use a deleted function}} static_assert(B(123).c == 3, ""); + // since-cxx26-error@-1 {{attempt to use a deleted function}} static_assert(B("").a == 1, ""); - // since-cxx11-error@-1 {{static assertion expression is not an integral constant expression}} - // since-cxx11-note@-2 {{read of member 'a' of union with active member 'b' is not allowed in a constant expression}} + // precxx26-error@-1 {{static assertion expression is not an integral constant expression}} + // precxx26-note@-2 {{read of member 'a' of union with active member 'b' is not allowed in a constant expression}} + // since-cxx26-error@-3 {{attempt to use a deleted function}} static_assert(B("").b == 2, ""); + // since-cxx26-error@-1 {{attempt to use a deleted function}} static_assert(B("").c == 3, ""); - // since-cxx11-error@-1 {{static assertion expression is not an integral constant expression}} - // since-cxx11-note@-2 {{read of member 'c' of union with active member 'b' is not allowed in a constant expression}} + // precxx26-error@-1 {{static assertion expression is not an integral constant expression}} + // precxx26-note@-2 {{read of member 'c' of union with active member 'b' is not allowed in a constant expression}} + // since-cxx26-error@-3 {{attempt to use a deleted function}} struct C { union { int a, b = 2, c; }; diff --git a/clang/test/CXX/special/class.ctor/p5-0x.cpp b/clang/test/CXX/special/class.ctor/p5-0x.cpp index e0c53058f892b..f00ec4ddc6350 100644 --- a/clang/test/CXX/special/class.ctor/p5-0x.cpp +++ b/clang/test/CXX/special/class.ctor/p5-0x.cpp @@ -1,4 +1,6 @@ -// RUN: %clang_cc1 -fsyntax-only -verify %s -std=c++11 -Wno-deprecated-builtins -Wno-defaulted-function-deleted +// RUN: %clang_cc1 -fsyntax-only -verify=expected,cxx11-23 %s -std=c++11 -Wno-deprecated-builtins -Wno-defaulted-function-deleted +// RUN: %clang_cc1 -fsyntax-only -verify=expected,cxx11-23 %s -std=c++23 -Wno-deprecated-builtins -Wno-defaulted-function-deleted +// RUN: %clang_cc1 -fsyntax-only -verify=expected %s -std=c++26 -Wno-deprecated-builtins -Wno-defaulted-function-deleted struct DefaultedDefCtor1 {}; struct DefaultedDefCtor2 { DefaultedDefCtor2() = default; }; @@ -23,8 +25,8 @@ int n; // - X is a union-like class that has a variant member with a non-trivial // default constructor, -union Deleted1a { UserProvidedDefCtor u; }; // expected-note {{default constructor of 'Deleted1a' is implicitly deleted because variant field 'u' has a non-trivial default constructor}} -Deleted1a d1a; // expected-error {{implicitly-deleted default constructor}} +union Deleted1a { UserProvidedDefCtor u; }; // cxx11-23-note {{default constructor of 'Deleted1a' is implicitly deleted because variant field 'u' has a non-trivial default constructor}} +Deleted1a d1a; // cxx11-23-error {{implicitly-deleted default constructor}} union NotDeleted1a { DefaultedDefCtor1 nu; }; NotDeleted1a nd1a; union NotDeleted1b { DefaultedDefCtor2 nu; }; @@ -86,19 +88,19 @@ NotDeleted3i nd3i; union Deleted4a { const int a; const int b; - const UserProvidedDefCtor c; // expected-note {{because variant field 'c' has a non-trivial default constructor}} + const UserProvidedDefCtor c; // cxx11-23-note {{because variant field 'c' has a non-trivial default constructor}} }; -Deleted4a d4a; // expected-error {{implicitly-deleted default constructor}} +Deleted4a d4a; // cxx11-23-error {{implicitly-deleted default constructor}} union NotDeleted4a { const int a; int b; }; NotDeleted4a nd4a; // - X is a non-union class and all members of any anonymous union member are of // const-qualified type (or array thereof), struct Deleted5a { - union { const int a; }; // expected-note {{because all data members of an anonymous union member are const-qualified}} + union { const int a; }; // cxx11-23-note {{because all data members of an anonymous union member are const-qualified}} union { int b; }; }; -Deleted5a d5a; // expected-error {{implicitly-deleted default constructor}} +Deleted5a d5a; // cxx11-23-error {{implicitly-deleted default constructor}} struct NotDeleted5a { union { const int a; int b; }; union { const int c; int d; }; }; NotDeleted5a nd5a; diff --git a/clang/test/CXX/special/class.ctor/p6-0x.cpp b/clang/test/CXX/special/class.ctor/p6-0x.cpp index 156a2b20c6b52..474e7aad91207 100644 --- a/clang/test/CXX/special/class.ctor/p6-0x.cpp +++ b/clang/test/CXX/special/class.ctor/p6-0x.cpp @@ -1,11 +1,12 @@ -// RUN: %clang_cc1 -fsyntax-only -verify %s -std=c++11 +// RUN: %clang_cc1 -fsyntax-only -verify=expected,cxx11-23 %s -std=c++11 +// RUN: %clang_cc1 -fsyntax-only -verify=expected,cxx26 %s -std=c++26 // Implicitly-defined default constructors are constexpr if the implicit // definition would be. -struct NonConstexpr1 { // expected-note {{here}} +struct NonConstexpr1 { // cxx11-23-note {{here}} cxx26-note {{previous declaration is here}} int a; }; -struct NonConstexpr2 { // expected-note {{here}} +struct NonConstexpr2 { // cxx11-23-note {{here}} cxx26-note {{previous declaration is here}} NonConstexpr1 nl; }; struct NonConstexpr2a : NonConstexpr1 { }; @@ -15,8 +16,8 @@ constexpr NonConstexpr2a nc2a = NonConstexpr2a(); // ok, does not call construct constexpr int nc2_a = NonConstexpr2().nl.a; // ok constexpr int nc2a_a = NonConstexpr2a().a; // ok struct Helper { - friend constexpr NonConstexpr1::NonConstexpr1(); // expected-error {{follows non-constexpr declaration}} - friend constexpr NonConstexpr2::NonConstexpr2(); // expected-error {{follows non-constexpr declaration}} + friend constexpr NonConstexpr1::NonConstexpr1(); // cxx11-23-error {{follows non-constexpr declaration}} cxx26-error {{missing exception specification}} + friend constexpr NonConstexpr2::NonConstexpr2(); // cxx11-23-error {{follows non-constexpr declaration}} cxx26-error {{missing exception specification}} }; struct Constexpr1 {}; @@ -31,14 +32,14 @@ constexpr Constexpr2 c2 = Constexpr2(); // ok int n; struct Member { - Member() : a(n) {} + Member() : a(n) {} // cxx26-note {{here}} constexpr Member(int&a) : a(a) {} int &a; }; -struct NonConstexpr4 { // expected-note {{here}} +struct NonConstexpr4 { // cxx11-23-note {{here}} cxx26-note {{non-constexpr constructor}} Member m; }; -constexpr NonConstexpr4 nc4 = NonConstexpr4(); // expected-error {{constant expression}} expected-note {{non-constexpr constructor 'NonConstexpr4'}} +constexpr NonConstexpr4 nc4 = NonConstexpr4(); // expected-error {{constant expression}} cxx11-23-note {{non-constexpr constructor 'NonConstexpr4'}} cxx26-note {{in call to}} struct Constexpr3 { constexpr Constexpr3() : m(n) {} Member m; @@ -53,11 +54,11 @@ constexpr Constexpr4 c4 = Constexpr4(); // ok // This rule breaks some legal C++98 programs! struct A {}; // expected-note {{here}} struct B { - friend A::A(); // expected-error {{non-constexpr declaration of 'A' follows constexpr declaration}} + friend A::A(); // cxx11-23-error {{non-constexpr declaration of 'A' follows constexpr declaration}} cxx26-error {{missing exception specification}} }; namespace UnionCtors { - union A { // expected-note {{here}} + union A { // cxx11-23-note {{here}} int a; int b; }; @@ -79,7 +80,7 @@ namespace UnionCtors { int d = 5; }; }; - struct E { // expected-note {{here}} + struct E { // cxx11-23-note {{here}} union { int a; int b; @@ -87,11 +88,11 @@ namespace UnionCtors { }; struct Test { - friend constexpr A::A() noexcept; // expected-error {{follows non-constexpr declaration}} + friend constexpr A::A() noexcept; // cxx11-23-error {{follows non-constexpr declaration}} friend constexpr B::B() noexcept; friend constexpr C::C() noexcept; friend constexpr D::D() noexcept; - friend constexpr E::E() noexcept; // expected-error {{follows non-constexpr declaration}} + friend constexpr E::E() noexcept; // cxx11-23-error {{follows non-constexpr declaration}} }; } @@ -122,6 +123,6 @@ namespace PR48763 { struct G { G(); }; struct H : D { using D::D; H(int); G g; }; - union V { H h; }; // expected-note {{field 'h' has a non-trivial default constructor}} - V v; // expected-error {{deleted}} + union V { H h; }; // cxx11-23-note {{field 'h' has a non-trivial default constructor}} + V v; // cxx11-23-error {{deleted}} } diff --git a/clang/test/CXX/special/class.dtor/p5-0x.cpp b/clang/test/CXX/special/class.dtor/p5-0x.cpp index ae14dcdaf102a..286e980710009 100644 --- a/clang/test/CXX/special/class.dtor/p5-0x.cpp +++ b/clang/test/CXX/special/class.dtor/p5-0x.cpp @@ -1,10 +1,11 @@ -// RUN: %clang_cc1 -verify -std=c++11 %s -Wno-defaulted-function-deleted -triple x86_64-linux-gnu +// RUN: %clang_cc1 -verify=expected,precxx26 -std=c++11 %s -Wno-defaulted-function-deleted -triple x86_64-linux-gnu +// RUN: %clang_cc1 -verify=expected,cxx26 -std=c++26 %s -Wno-defaulted-function-deleted -triple x86_64-linux-gnu struct NonTrivDtor { ~NonTrivDtor(); }; struct DeletedDtor { - ~DeletedDtor() = delete; // expected-note 5 {{deleted here}} + ~DeletedDtor() = delete; // expected-note 4 {{deleted here}} precxx26-note {{deleted here}} }; class InaccessibleDtor { ~InaccessibleDtor() = default; @@ -14,30 +15,30 @@ class InaccessibleDtor { // -- X is a union-like class that has a variant member with a non-trivial // destructor. -union A1 { +union A1 { // cxx26-note {{not trivial}} A1(); - NonTrivDtor n; // expected-note {{destructor of 'A1' is implicitly deleted because variant field 'n' has a non-trivial destructor}} + NonTrivDtor n; // precxx26-note {{destructor of 'A1' is implicitly deleted because variant field 'n' has a non-trivial destructor}} }; A1 a1; // expected-error {{deleted function}} struct A2 { A2(); union { - NonTrivDtor n; // expected-note {{because variant field 'n' has a non-trivial destructor}} + NonTrivDtor n; // precxx26-note {{because variant field 'n' has a non-trivial destructor}} }; }; -A2 a2; // expected-error {{deleted function}} -union A3 { +A2 a2; // precxx26-error {{deleted function}} +union A3 { // cxx26-note {{not trivial}} A3(); - NonTrivDtor n[3]; // expected-note {{because variant field 'n' has a non-trivial destructor}} + NonTrivDtor n[3]; // precxx26-note {{because variant field 'n' has a non-trivial destructor}} }; A3 a3; // expected-error {{deleted function}} struct A4 { A4(); union { - NonTrivDtor n[3]; // expected-note {{because variant field 'n' has a non-trivial destructor}} + NonTrivDtor n[3]; // precxx26-note {{because variant field 'n' has a non-trivial destructor}} }; }; -A4 a4; // expected-error {{deleted function}} +A4 a4; // precxx26-error {{deleted function}} // -- any of the non-static data members has class type M (or array thereof) and // M has a deleted or inaccessible destructor. @@ -61,17 +62,17 @@ struct B4 { InaccessibleDtor a[4]; // expected-note {{because field 'a' has an inaccessible destructor}} }; B4 b4; // expected-error {{deleted function}} -union B5 { +union B5 { // cxx26-note {{not trivial}} B5(); - union { // expected-note-re {{because field 'B5::(anonymous union at {{.+}})' has a deleted destructor}} - DeletedDtor a; // expected-note {{because field 'a' has a deleted destructor}} + union { // precxx26-note-re {{because field 'B5::(anonymous union at {{.+}})' has a deleted destructor}} + DeletedDtor a; // precxx26-note {{because field 'a' has a deleted destructor}} }; }; B5 b5; // expected-error {{deleted function}} -union B6 { +union B6 { // cxx26-note {{not trivial}} B6(); - union { // expected-note-re {{because field 'B6::(anonymous union at {{.+}})' has a deleted destructor}} - InaccessibleDtor a; // expected-note {{because field 'a' has an inaccessible destructor}} + union { // precxx26-note-re {{because field 'B6::(anonymous union at {{.+}})' has a deleted destructor}} + InaccessibleDtor a; // precxx26-note {{because field 'a' has an inaccessible destructor}} }; }; B6 b6; // expected-error {{deleted function}} diff --git a/clang/test/SemaCXX/cxx26-trivial-union.cpp b/clang/test/SemaCXX/cxx26-trivial-union.cpp new file mode 100644 index 0000000000000..8265c4f4d7670 --- /dev/null +++ b/clang/test/SemaCXX/cxx26-trivial-union.cpp @@ -0,0 +1,253 @@ +// RUN: %clang_cc1 -std=c++26 -fsyntax-only -verify=cxx26 %s +// RUN: %clang_cc1 -std=c++23 -fsyntax-only -verify=precxx26 %s + +// P3074R7: trivial unions + +struct NonTrivial { + NonTrivial(); + NonTrivial(const NonTrivial&); + NonTrivial& operator=(const NonTrivial&); + ~NonTrivial(); +}; + +struct NonTrivialDtor { + ~NonTrivialDtor(); +}; + +// ===== Test 1: Basic union with non-trivial member ===== +// P3074: default ctor and dtor should be trivial, not deleted. +union U1 { + NonTrivial nt; // precxx26-note 2{{non-trivial}} +}; + +#if __cplusplus > 202302L +static_assert(__is_trivially_constructible(U1)); +static_assert(__is_trivially_destructible(U1)); +U1 test_u1; +#else +U1 test_u1_pre; // precxx26-error {{deleted}} +void destroy_u1(U1 *p) { p->~U1(); } // precxx26-error {{deleted}} +#endif + +// ===== Test 2: Union with non-trivial member and int ===== +union U2 { + NonTrivial nt; + int k; +}; + +#if __cplusplus > 202302L +static_assert(__is_trivially_constructible(U2)); +static_assert(__is_trivially_destructible(U2)); +#endif + +// ===== Test 3: Union with DMI on member with non-trivial dtor ===== +// P3074: dtor is deleted because DMI + non-trivial dtor on same member. +union U3_deleted_dtor { + NonTrivialDtor ntd = {}; // cxx26-note {{non-trivial}} precxx26-note {{non-trivial}} +}; + +void test_u3_destroy(U3_deleted_dtor *p) { + p->~U3_deleted_dtor(); // cxx26-error {{deleted}} precxx26-error {{deleted}} +} + +// ===== Test 4: Union with DMI on non-class member ===== +// DMI on int, but NonTrivial has no DMI => dtor should NOT be deleted. +union U4 { + NonTrivial nt; // precxx26-note {{non-trivial}} + int k = 42; +}; + +#if __cplusplus > 202302L +// Despite non-trivial default ctor (due to DMI), destructor is NOT deleted +// because the member with DMI (k) is int (trivially destructible). +static_assert(__is_trivially_destructible(U4)); +#else +void destroy_u4(U4 *p) { p->~U4(); } // precxx26-error {{deleted}} +#endif + +// ===== Test 5: Union with user-provided default constructor ===== +// Dtor is deleted: user-provided default ctor is non-trivial +// (see C++26 [class.dtor]p7). +union U5 { // cxx26-note {{not trivial}} + U5() : nt() {} + NonTrivialDtor nt; // precxx26-note {{non-trivial}} +}; + +void test_u5_destroy(U5 *p) { p->~U5(); } // cxx26-error {{deleted}} precxx26-error {{deleted}} + +// ===== Test 6: Feature test macro ===== +#if __cplusplus > 202302L +static_assert(__cpp_trivial_union >= 202502L); +#else +#ifdef __cpp_trivial_union +#error "should not have __cpp_trivial_union in C++23" +#endif +#endif + +// ===== Test 7: Trivial union (no change from status quo) ===== +union U7 { + int a; + float b; +}; + +static_assert(__is_trivially_constructible(U7)); +static_assert(__is_trivially_destructible(U7)); + +// ===== Test 8: Array member in union ===== +union U8 { + NonTrivial arr[4]; +}; + +#if __cplusplus > 202302L +static_assert(__is_trivially_constructible(U8)); +static_assert(__is_trivially_destructible(U8)); +#endif + +// ===== Test 9: Paper example - string with DMI ===== +struct FakeString { + FakeString(const char*); + FakeString(const FakeString&); + FakeString& operator=(const FakeString&); + ~FakeString(); +}; + +union PaperU2 { + FakeString s = "hello"; // cxx26-note {{non-trivial}} precxx26-note {{non-trivial}} +}; + +void test_paper_u2(PaperU2 *p) { + p->~PaperU2(); // cxx26-error {{deleted}} precxx26-error {{deleted}} +} + +// ===== Test 10: Paper example U4 - DMI on pointer, non-trivial string ===== +union PaperU4 { + FakeString s; // precxx26-note {{non-trivial}} + PaperU4 *next = nullptr; +}; + +#if __cplusplus > 202302L +static_assert(__is_trivially_destructible(PaperU4)); +#else +void destroy_paper_u4(PaperU4 *p) { p->~PaperU4(); } // precxx26-error {{deleted}} +#endif + +// ===== Test 11: No default ctor (suppressed by user-declared ctor) ===== +// When the implicit default ctor is suppressed (not deleted), the +// [class.dtor]p7 bullet 1 rule does not apply. The union can't be +// default-initialized, so no risk of indeterminate active member. +// Destructor is trivial (see C++26 [class.dtor]p7). +#if __cplusplus > 202302L +union U11 { + U11(int); + NonTrivialDtor nt; +}; +static_assert(__is_trivially_destructible(U11)); +U11 u11(1); + +// ===== Test 12: Deleted default ctor (dtor deleted per [class.dtor]p7) ===== +union U12 { // cxx26-note {{deleted function}} + U12() = delete; + U12(int); + NonTrivialDtor nt; +}; +U12 u12(1); // cxx26-error {{deleted}} + +// ===== Test 13: Defaulted ctor => trivial, dtor NOT deleted ===== +union U13 { + U13() = default; + NonTrivialDtor nt; + U13 *next = nullptr; +}; +U13 u13; + +// ===== Test 14: Array member with DMI + non-trivial dtor ([class.dtor]p7 bullet 2) ===== +struct NonTrivialInt { + int i; + constexpr NonTrivialInt(int i) : i(i) {} + constexpr ~NonTrivialInt() {} +}; + +union U14 { + NonTrivialInt arr[2] = {1, 2}; // cxx26-note {{non-trivial}} +}; +U14 u14; // cxx26-error {{deleted}} + +// ===== Test 15: Anonymous struct in union, no DMI => NOT deleted ===== +union U15 { + struct { + NonTrivialDtor x; + }; +}; +U15 u15; + +// ===== Test 16: Anonymous struct in union, with DMI => deleted ===== +union U16 { + struct { + NonTrivialInt x = 1; // cxx26-note {{non-trivial}} + }; +}; +U16 u16; // cxx26-error {{deleted}} + +// ===== Test 17: struct containing anonymous union ===== +struct S17 { + union { + NonTrivialDtor x; + }; +}; +S17 s17; + +// ===== Test 18: Deeply nested union-struct-union-struct-union ===== +union U18 { + struct { + union { + struct { + union { + NonTrivialDtor x; + }; + }; + }; + }; +}; +U18 u18; + +// ===== Test 19: struct-union-struct-union-struct nesting ===== +struct S19 { + union { + struct { + union { + struct { + NonTrivialDtor x; + }; + }; + }; + }; +}; +S19 s19; + +// ===== Test 20: Anonymous union inside union ===== +union U20 { + union { + NonTrivialDtor x; + }; +}; +U20 u20; + +// ===== Test 21: Deleted destructor member (p7.2, not union-specific) ===== +// p7.2 still applies: deleted/inaccessible member dtor => union dtor deleted. +struct DeletedDtor { + ~DeletedDtor() = delete; // cxx26-note {{deleted here}} precxx26-note {{deleted here}} +}; + +union U21 { + DeletedDtor a; // cxx26-note {{deleted destructor}} precxx26-note {{deleted destructor}} +}; +void test_u21(U21 *p) { p->~U21(); } // cxx26-error {{deleted}} precxx26-error {{deleted}} + +// ===== Test 22: Constexpr evaluation of trivial union ===== +constexpr int constexpr_test() { + U2 u; + u.k = 42; + return u.k; +} +static_assert(constexpr_test() == 42); +#endif >From ac3cf1761d96269e742b902618aff4bf1f4bd41a Mon Sep 17 00:00:00 2001 From: Peng Xie <[email protected]> Date: Wed, 11 Mar 2026 15:26:16 +0800 Subject: [PATCH 2/2] [Clang] Implement P3726R1: Adjustments to Union Lifetime Rules Follow-up to P3074R7 (trivial unions). P3726R1 adjusts the union lifetime model with three changes: - Add __builtin_start_lifetime(void*) as a consteval builtin that begins the lifetime of an object at the given address. This replaces the implicit-lifetime-start semantics originally in P3074R7 p4. - Extend the constituent values rule so that reading from a union array member is valid when each element has a constituent value, matching the existing scalar/struct member behavior. - Bump __cpp_trivial_union from 202502L to 202602L. --- clang/include/clang/Basic/Builtins.td | 6 + .../include/clang/Basic/DiagnosticASTKinds.td | 5 + .../clang/Basic/DiagnosticSemaKinds.td | 4 + clang/lib/AST/ExprConstant.cpp | 120 +++++++++++++++++- clang/lib/CodeGen/CGBuiltin.cpp | 5 + clang/lib/Frontend/InitPreprocessor.cpp | 2 +- clang/lib/Sema/SemaChecking.cpp | 53 ++++++++ clang/test/SemaCXX/cxx26-start-lifetime.cpp | 115 +++++++++++++++++ clang/test/SemaCXX/cxx26-trivial-union.cpp | 2 +- 9 files changed, 305 insertions(+), 7 deletions(-) create mode 100644 clang/test/SemaCXX/cxx26-start-lifetime.cpp diff --git a/clang/include/clang/Basic/Builtins.td b/clang/include/clang/Basic/Builtins.td index dd5bd689c08d2..4a8286a3db76c 100644 --- a/clang/include/clang/Basic/Builtins.td +++ b/clang/include/clang/Basic/Builtins.td @@ -1000,6 +1000,12 @@ def IsWithinLifetime : LangBuiltin<"CXX_LANG"> { let Prototype = "bool(void*)"; } +def StartLifetime : LangBuiltin<"CXX_LANG"> { + let Spellings = ["__builtin_start_lifetime"]; + let Attributes = [NoThrow, CustomTypeChecking, Consteval]; + let Prototype = "void(void*)"; +} + def GetVtablePointer : LangBuiltin<"CXX_LANG"> { let Spellings = ["__builtin_get_vtable_pointer"]; let Attributes = [CustomTypeChecking, NoThrow, Const]; diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td index bde418695f647..37261fc3d6407 100644 --- a/clang/include/clang/Basic/DiagnosticASTKinds.td +++ b/clang/include/clang/Basic/DiagnosticASTKinds.td @@ -437,6 +437,11 @@ def err_invalid_is_within_lifetime : Note< "a pointer to an object whose lifetime has not yet begun}1" >; +def err_invalid_start_lifetime : Note< + "'%0' cannot be called with " + "%select{a null pointer|a one-past-the-end pointer}1" +>; + // inline asm related. let CategoryName = "Inline Assembly Issue" in { def err_asm_invalid_escape : Error< diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 0cb33bc9511c3..b2f19513b6026 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -13243,6 +13243,10 @@ def err_builtin_is_within_lifetime_invalid_arg : Error< "%select{non-|function }0pointer argument to '__builtin_is_within_lifetime' " "is not allowed">; +def err_builtin_start_lifetime_invalid_arg : Error< + "'__builtin_start_lifetime' argument must be a pointer to a complete " + "implicit-lifetime aggregate type, but got %0">; + // A multi-component builtin type diagnostic. The first component broadly // selects a scalar or container type (scalar, vector or matrix). The second // component selects integer types and the third component selects diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 429fef0a1afa8..25a95198fdf6c 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -2452,17 +2452,31 @@ static bool CheckEvaluationResult(CheckEvaluationResultKind CERK, // expression. if (Value.isArray()) { QualType EltTy = Type->castAsArrayTypeUnsafe()->getElementType(); + + // C++26 [expr.const]p2: + // An inactive union subobject includes [...] an element E of an array + // member of a union where E is not within its lifetime. + // Skip such elements during constituent values checking. + bool IsUnionArrayMember = + Info.getLangOpts().CPlusPlus26 && SubobjectDecl && + SubobjectDecl->getDeclContext()->isRecord() && + cast<RecordDecl>(SubobjectDecl->getDeclContext())->isUnion(); + for (unsigned I = 0, N = Value.getArrayInitializedElts(); I != N; ++I) { - if (!CheckEvaluationResult(CERK, Info, DiagLoc, EltTy, - Value.getArrayInitializedElt(I), Kind, + const APValue &Elt = Value.getArrayInitializedElt(I); + if (IsUnionArrayMember && !Elt.hasValue()) + continue; + if (!CheckEvaluationResult(CERK, Info, DiagLoc, EltTy, Elt, Kind, SubobjectDecl, CheckedTemps)) return false; } if (!Value.hasArrayFiller()) return true; - return CheckEvaluationResult(CERK, Info, DiagLoc, EltTy, - Value.getArrayFiller(), Kind, SubobjectDecl, - CheckedTemps); + const APValue &Filler = Value.getArrayFiller(); + if (IsUnionArrayMember && !Filler.hasValue()) + return true; + return CheckEvaluationResult(CERK, Info, DiagLoc, EltTy, Filler, Kind, + SubobjectDecl, CheckedTemps); } if (Value.isUnion() && Value.getUnionField()) { return CheckEvaluationResult( @@ -6795,6 +6809,45 @@ struct StartLifetimeOfUnionMemberHandler { const AccessKinds StartLifetimeOfUnionMemberHandler::AccessKind; +namespace { +/// Handler for __builtin_start_lifetime (see C++26 [obj.lifetime]). +/// Starts the lifetime of the target object without initializing subobjects. +struct BuiltinStartLifetimeHandler { + EvalInfo &Info; + static const AccessKinds AccessKind = AK_Construct; + typedef bool result_type; + bool failed() { return false; } + bool found(APValue &Subobj, QualType SubobjType) { + // C++26 [obj.lifetime]p3: + // If the object referenced by r is already within its lifetime, + // there are no effects. + if (Subobj.hasValue()) + return true; + + // Begin the lifetime of the object without initializing subobjects. + if (auto *RD = SubobjType->getAsCXXRecordDecl()) { + if (RD->isUnion()) { + Subobj = APValue((const FieldDecl *)nullptr); + } else { + Subobj = APValue(APValue::UninitStruct(), RD->getNumBases(), + std::distance(RD->field_begin(), RD->field_end())); + } + } else if (auto *AT = dyn_cast_or_null<ConstantArrayType>( + SubobjType->getAsArrayTypeUnsafe())) { + Subobj = APValue(APValue::UninitArray(), 0, AT->getZExtSize()); + // Leave array filler absent — no element lifetimes started. + } else { + Subobj = APValue::IndeterminateValue(); + } + return true; + } + bool found(APSInt &, QualType) { return true; } + bool found(APFloat &, QualType) { return true; } +}; +} // end anonymous namespace + +const AccessKinds BuiltinStartLifetimeHandler::AccessKind; + /// Handle a builtin simple-assignment or a call to a trivial assignment /// operator whose left-hand side might involve a union member access. If it /// does, implicitly start the lifetime of any accessed union elements per @@ -20446,6 +20499,8 @@ static bool EvaluateAtomic(const Expr *E, const LValue *This, APValue &Result, // comma operator //===----------------------------------------------------------------------===// +static bool EvaluateBuiltinStartLifetime(EvalInfo &Info, const CallExpr *E); + namespace { class VoidExprEvaluator : public ExprEvaluatorBase<VoidExprEvaluator> { @@ -20479,6 +20534,9 @@ class VoidExprEvaluator case Builtin::BI__builtin_operator_delete: return HandleOperatorDeleteCall(Info, E); + case Builtin::BI__builtin_start_lifetime: + return EvaluateBuiltinStartLifetime(Info, E); + default: return false; } @@ -22186,3 +22244,55 @@ std::optional<bool> EvaluateBuiltinIsWithinLifetime(IntExprEvaluator &IEE, return findSubobject(Info, E, CO, Val.getLValueDesignator(), handler); } } // namespace + +/// Evaluate __builtin_start_lifetime(ptr) (see C++26 [obj.lifetime]). +/// Starts the lifetime of the object pointed to by ptr without initialization. +/// If the object is a union member, it becomes the active member. +static bool EvaluateBuiltinStartLifetime(EvalInfo &Info, const CallExpr *E) { + if (!Info.InConstantContext) + return false; + + assert(E->getBuiltinCallee() == Builtin::BI__builtin_start_lifetime); + const Expr *Arg = E->getArg(0); + if (Arg->isValueDependent()) + return false; + + LValue Val; + if (!EvaluatePointer(Arg, Val, Info)) + return false; + + auto Error = [&](int Diag) { + bool CalledFromStd = false; + const auto *Callee = Info.CurrentCall->getCallee(); + if (Callee && Callee->isInStdNamespace()) { + const IdentifierInfo *Identifier = Callee->getIdentifier(); + CalledFromStd = Identifier && Identifier->isStr("start_lifetime"); + } + Info.FFDiag(CalledFromStd ? Info.CurrentCall->getCallRange().getBegin() + : E->getExprLoc(), + diag::err_invalid_start_lifetime) + << (CalledFromStd ? "std::start_lifetime" : "__builtin_start_lifetime") + << Diag; + return false; + }; + + if (Val.isNullPointer() || Val.getLValueBase().isNull()) + return Error(0); + + if (Val.getLValueDesignator().isOnePastTheEnd()) + return Error(1); + + QualType T = Val.getLValueBase().getType(); + + // Find the complete object. + CompleteObject CO = + findCompleteObject(Info, E, AccessKinds::AK_Construct, Val, T); + if (!CO) + return false; + + // Navigate to the target subobject. Use AK_Construct so that + // findSubobject will activate inactive union members along the path. + // The handler starts the lifetime without initializing subobjects. + BuiltinStartLifetimeHandler Handler{Info}; + return findSubobject(Info, E, CO, Val.getLValueDesignator(), Handler); +} diff --git a/clang/lib/CodeGen/CGBuiltin.cpp b/clang/lib/CodeGen/CGBuiltin.cpp index 6fb43d5cb0fbf..cade845898ba8 100644 --- a/clang/lib/CodeGen/CGBuiltin.cpp +++ b/clang/lib/CodeGen/CGBuiltin.cpp @@ -5642,6 +5642,11 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID, E->getCallee()->getType()->castAs<FunctionProtoType>(), E, true); return RValue::get(nullptr); + case Builtin::BI__builtin_start_lifetime: + // No-op at runtime. Lifetime of implicit-lifetime aggregates + // begins automatically with storage acquisition. + return RValue::get(nullptr); + case Builtin::BI__builtin_is_aligned: return EmitBuiltinIsAligned(E); case Builtin::BI__builtin_align_up: diff --git a/clang/lib/Frontend/InitPreprocessor.cpp b/clang/lib/Frontend/InitPreprocessor.cpp index a00b34625510b..e61bbac819685 100644 --- a/clang/lib/Frontend/InitPreprocessor.cpp +++ b/clang/lib/Frontend/InitPreprocessor.cpp @@ -748,7 +748,7 @@ static void InitializeCPlusPlusFeatureTestMacros(const LangOptions &LangOpts, // C++26 features. if (LangOpts.CPlusPlus26) - Builder.defineMacro("__cpp_trivial_union", "202502L"); + Builder.defineMacro("__cpp_trivial_union", "202602L"); if (LangOpts.Char8) Builder.defineMacro("__cpp_char8_t", "202207L"); diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp index 29add9d092e6b..ff63068279c31 100644 --- a/clang/lib/Sema/SemaChecking.cpp +++ b/clang/lib/Sema/SemaChecking.cpp @@ -2021,6 +2021,57 @@ static ExprResult BuiltinIsWithinLifetime(Sema &S, CallExpr *TheCall) { return TheCall; } +static ExprResult BuiltinStartLifetime(Sema &S, CallExpr *TheCall) { + if (S.checkArgCount(TheCall, 1)) + return ExprError(); + + ExprResult Arg = S.DefaultFunctionArrayLvalueConversion(TheCall->getArg(0)); + if (Arg.isInvalid()) + return ExprError(); + QualType ParamTy = Arg.get()->getType(); + TheCall->setArg(0, Arg.get()); + TheCall->setType(S.Context.VoidTy); + + const auto *PT = ParamTy->getAs<PointerType>(); + if (!PT) { + S.Diag(TheCall->getArg(0)->getExprLoc(), + diag::err_builtin_start_lifetime_invalid_arg) + << ParamTy; + return ExprError(); + } + + QualType PointeeTy = PT->getPointeeType(); + + // Mandates: T is a complete type + if (S.RequireCompleteType(TheCall->getArg(0)->getExprLoc(), PointeeTy, + diag::err_incomplete_type)) + return ExprError(); + + // Mandates: T is an implicit-lifetime aggregate type + // Check aggregate first + if (!PointeeTy->isAggregateType()) { + S.Diag(TheCall->getArg(0)->getExprLoc(), + diag::err_builtin_start_lifetime_invalid_arg) + << PointeeTy; + return ExprError(); + } + + // Check implicit-lifetime: for aggregates, destructor must not be + // user-provided + if (const auto *RD = PointeeTy->getAsCXXRecordDecl()) { + if (const auto *Dtor = RD->getDestructor()) { + if (Dtor->isUserProvided()) { + S.Diag(TheCall->getArg(0)->getExprLoc(), + diag::err_builtin_start_lifetime_invalid_arg) + << PointeeTy; + return ExprError(); + } + } + } + + return TheCall; +} + static ExprResult BuiltinTriviallyRelocate(Sema &S, CallExpr *TheCall) { if (S.checkArgCount(TheCall, 3)) return ExprError(); @@ -3071,6 +3122,8 @@ Sema::CheckBuiltinFunctionCall(FunctionDecl *FDecl, unsigned BuiltinID, return BuiltinLaunder(*this, TheCall); case Builtin::BI__builtin_is_within_lifetime: return BuiltinIsWithinLifetime(*this, TheCall); + case Builtin::BI__builtin_start_lifetime: + return BuiltinStartLifetime(*this, TheCall); case Builtin::BI__builtin_trivially_relocate: return BuiltinTriviallyRelocate(*this, TheCall); diff --git a/clang/test/SemaCXX/cxx26-start-lifetime.cpp b/clang/test/SemaCXX/cxx26-start-lifetime.cpp new file mode 100644 index 0000000000000..19f0e0d0d5196 --- /dev/null +++ b/clang/test/SemaCXX/cxx26-start-lifetime.cpp @@ -0,0 +1,115 @@ +// RUN: %clang_cc1 -std=c++26 -fsyntax-only -verify %s + +// P3726R1: __builtin_start_lifetime and constituent values tests + +namespace std { + using size_t = decltype(sizeof(0)); +} +void* operator new(std::size_t, void* p) noexcept { return p; } + +// ===== Type checking tests ===== + +struct Agg { int x; int y; }; +struct NonAgg { + NonAgg(int); + int x; +}; +struct AggWithUserDtor { + int x; + ~AggWithUserDtor(); +}; + +// type checking for __builtin_start_lifetime is done via consteval contexts. +consteval void check_agg() { + Agg a; + __builtin_start_lifetime(&a); // OK +} +consteval void check_array() { + int arr[4]; + __builtin_start_lifetime(&arr); // OK +} + +consteval void check_scalar() { + int x = 0; + __builtin_start_lifetime(&x); // expected-error {{pointer to a complete implicit-lifetime aggregate type}} +} + +consteval void check_non_agg() { + // NonAgg is not constructible without an argument, can't be a local here. + // Just test the pointer type check with an invalid construct. +} + +consteval void check_user_dtor() { + AggWithUserDtor awd; + __builtin_start_lifetime(&awd); // expected-error {{pointer to a complete implicit-lifetime aggregate type}} +} + +// ===== Constexpr evaluation tests ===== + +// Test: start_lifetime on array member of union, then placement new elements +consteval int test_start_lifetime_array() { + struct S { + union { int storage[4]; }; + int size = 0; + }; + S s; + __builtin_start_lifetime(&s.storage); + // Now storage is the active member, but no elements are within lifetime. + ::new (&s.storage[0]) int(10); + ::new (&s.storage[1]) int(20); + s.size = 2; + return s.storage[0] + s.storage[1]; // 30 +} +static_assert(test_start_lifetime_array() == 30); + +// Test: start_lifetime is no-op if already within lifetime +consteval int test_start_lifetime_noop() { + struct S { + union { int storage[2]; }; + }; + S s; + __builtin_start_lifetime(&s.storage); + ::new (&s.storage[0]) int(42); + // Call again - should be a no-op since storage is already active + __builtin_start_lifetime(&s.storage); + return s.storage[0]; // Still 42 +} +static_assert(test_start_lifetime_noop() == 42); + +// Test: start_lifetime on struct member of union +consteval int test_start_lifetime_struct() { + struct Inner { int a; int b; }; + union U { Inner inner; int x; }; + U u; + __builtin_start_lifetime(&u.inner); + // inner is now active but its members aren't initialized yet + ::new (&u.inner.a) int(1); + ::new (&u.inner.b) int(2); + return u.inner.a + u.inner.b; +} +static_assert(test_start_lifetime_struct() == 3); + +// ===== Constituent values: array with holes in union ===== +// Array elements not within their lifetime in a union are inactive union +// subobjects and should be skipped (see C++26 [expr.const]p2). + +struct CVResult { + union { int arr[4]; }; + int size; +}; + +consteval CVResult test_constituent_values() { + CVResult s; + s.size = 2; + __builtin_start_lifetime(&s.arr); + ::new (&s.arr[0]) int(100); + ::new (&s.arr[1]) int(200); + // arr[2] and arr[3] are not within their lifetime — that's OK per P3726R1. + return s; +} +// This should be a valid constexpr variable even though arr[2] and arr[3] +// are not initialized — they are inactive union subobjects per P3726R1. +constexpr auto cv_result = test_constituent_values(); +static_assert(cv_result.arr[0] == 100); +static_assert(cv_result.arr[1] == 200); +static_assert(cv_result.size == 2); diff --git a/clang/test/SemaCXX/cxx26-trivial-union.cpp b/clang/test/SemaCXX/cxx26-trivial-union.cpp index 8265c4f4d7670..9beff149cfb94 100644 --- a/clang/test/SemaCXX/cxx26-trivial-union.cpp +++ b/clang/test/SemaCXX/cxx26-trivial-union.cpp @@ -77,7 +77,7 @@ void test_u5_destroy(U5 *p) { p->~U5(); } // cxx26-error {{deleted}} precxx26-er // ===== Test 6: Feature test macro ===== #if __cplusplus > 202302L -static_assert(__cpp_trivial_union >= 202502L); +static_assert(__cpp_trivial_union >= 202602L); #else #ifdef __cpp_trivial_union #error "should not have __cpp_trivial_union in C++23" _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
