https://github.com/yronglin updated https://github.com/llvm/llvm-project/pull/197458
>From c8a0f26dd7a90bfff8520f5b9db10c87bd7a16cd Mon Sep 17 00:00:00 2001 From: yronglin <[email protected]> Date: Wed, 27 May 2026 09:39:03 -0700 Subject: [PATCH] [C++26] Implement P2752R3 'Static storage for braced initializers' Signed-off-by: yronglin <[email protected]> --- clang/docs/ReleaseNotes.rst | 2 + clang/include/clang/AST/ExprCXX.h | 17 +- clang/include/clang/AST/Stmt.h | 14 + clang/include/clang/AST/TypeBase.h | 9 + .../include/clang/Basic/DiagnosticASTKinds.td | 2 + clang/lib/AST/ASTImporter.cpp | 2 + clang/lib/AST/ByteCode/Interp.cpp | 112 ++++++ clang/lib/AST/ByteCode/Interp.h | 14 +- clang/lib/AST/ExprCXX.cpp | 1 + clang/lib/AST/ExprConstShared.h | 31 +- clang/lib/AST/ExprConstant.cpp | 331 ++++++++++++++++-- clang/lib/AST/Type.cpp | 81 +++++ clang/lib/Sema/CheckExprLifetime.cpp | 19 +- clang/lib/Sema/SemaDeclCXX.cpp | 101 +----- clang/lib/Sema/SemaInit.cpp | 1 + clang/lib/Serialization/ASTReaderStmt.cpp | 1 + clang/lib/Serialization/ASTWriterStmt.cpp | 1 + clang/test/AST/ByteCode/initializer_list.cpp | 300 ++++++++++++++++ .../dcl.decl/dcl.init/dcl.init.list/p5.cpp | 139 ++++++++ clang/test/CXX/drs/cwg27xx.cpp | 167 +++++++-- clang/test/CodeGenCXX/Inputs/jk.txt | 1 + .../CodeGenCXX/p2752r3-initializer-list.cpp | 93 +++++ clang/www/cxx_dr_status.html | 2 +- clang/www/cxx_status.html | 2 +- 24 files changed, 1278 insertions(+), 165 deletions(-) create mode 100644 clang/test/CXX/dcl.decl/dcl.init/dcl.init.list/p5.cpp create mode 100644 clang/test/CodeGenCXX/Inputs/jk.txt create mode 100644 clang/test/CodeGenCXX/p2752r3-initializer-list.cpp diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 603f427c40aa4..03eb3f0156b2e 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -152,6 +152,8 @@ C++ Language Changes C++2c Feature Support ^^^^^^^^^^^^^^^^^^^^^ +- Clang now supports `P2752R3 <https://wg21.link/p2752r3>`_ 'Static storage for braced initializers'. (#GH104487) + C++23 Feature Support ^^^^^^^^^^^^^^^^^^^^^ diff --git a/clang/include/clang/AST/ExprCXX.h b/clang/include/clang/AST/ExprCXX.h index 0287797370397..8284f0092145e 100644 --- a/clang/include/clang/AST/ExprCXX.h +++ b/clang/include/clang/AST/ExprCXX.h @@ -4930,7 +4930,22 @@ class MaterializeTemporaryExpr : public Expr { LifetimeExtendedTemporaryDecl *MTD = nullptr); MaterializeTemporaryExpr(EmptyShell Empty) - : Expr(MaterializeTemporaryExprClass, Empty) {} + : Expr(MaterializeTemporaryExprClass, Empty) { + MaterializeTemporaryExprBits.IsBackingArrayForInitializerList = false; + } + + /// Whether this materialized temporary is the backing array of a + /// std::initializer_list. + /// + /// This used by [intro.object]/9 "potentially non-unique object" handling. + bool isBackingArrayForInitializerList() const { + return MaterializeTemporaryExprBits.IsBackingArrayForInitializerList; + } + + /// This bitfield will set by SK_StdInitializerList step in Sema. + void setBackingArrayForInitializerList(bool V = true) { + MaterializeTemporaryExprBits.IsBackingArrayForInitializerList = V; + } /// Retrieve the temporary-generating subexpression whose value will /// be materialized into a glvalue. diff --git a/clang/include/clang/AST/Stmt.h b/clang/include/clang/AST/Stmt.h index f07ba9205661b..e4eebe5986bd5 100644 --- a/clang/include/clang/AST/Stmt.h +++ b/clang/include/clang/AST/Stmt.h @@ -831,6 +831,19 @@ class alignas(void *) Stmt { SourceLocation Loc; }; + class MaterializeTemporaryExprBitfields { + friend class ASTStmtReader; + friend class MaterializeTemporaryExpr; + + LLVM_PREFERRED_TYPE(ExprBitfields) + unsigned : NumExprBits; + + /// Whether the materialized temporary is the backing array of a + /// std::initializer_list. + LLVM_PREFERRED_TYPE(bool) + unsigned IsBackingArrayForInitializerList : 1; + }; + class CXXThisExprBitfields { friend class CXXThisExpr; @@ -1381,6 +1394,7 @@ class alignas(void *) Stmt { CXXRewrittenBinaryOperatorBitfields CXXRewrittenBinaryOperatorBits; CXXBoolLiteralExprBitfields CXXBoolLiteralExprBits; CXXNullPtrLiteralExprBitfields CXXNullPtrLiteralExprBits; + MaterializeTemporaryExprBitfields MaterializeTemporaryExprBits; CXXThisExprBitfields CXXThisExprBits; CXXThrowExprBitfields CXXThrowExprBits; CXXDefaultArgExprBitfields CXXDefaultArgExprBits; diff --git a/clang/include/clang/AST/TypeBase.h b/clang/include/clang/AST/TypeBase.h index c64eee11fd91e..388e09fea89cf 100644 --- a/clang/include/clang/AST/TypeBase.h +++ b/clang/include/clang/AST/TypeBase.h @@ -116,6 +116,7 @@ namespace clang { class ASTContext; template <typename> class CanQual; class CXXRecordDecl; +class Decl; class DeclContext; class EnumDecl; class Expr; @@ -2762,6 +2763,14 @@ class alignas(TypeAlignment) Type : public ExtQualsTypeCommonBase { bool isNothrowT() const; // C++ std::nothrow_t bool isAlignValT() const; // C++17 std::align_val_t bool isStdByteType() const; // C++17 std::byte + bool + isStdClassTemplateSpecialization(const ASTContext &Ctx, StringRef ClassName, + QualType *TypeArg = nullptr, + ClassTemplateDecl **CachedDecl = nullptr, + const Decl **MalformedDecl = nullptr) const; + bool isStdInitializerListType( + const ASTContext &Ctx, + QualType *Element = nullptr) const; // C++11 std::initializer_list<T> bool isAtomicType() const; // C11 _Atomic() bool isUndeducedAutoType() const; // C++11 auto or // C++14 decltype(auto) diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td index bde418695f647..f4f239a16a38f 100644 --- a/clang/include/clang/Basic/DiagnosticASTKinds.td +++ b/clang/include/clang/Basic/DiagnosticASTKinds.td @@ -99,6 +99,8 @@ def note_constexpr_pointer_constant_comparison : Note< "at runtime">; def note_constexpr_literal_comparison : Note< "comparison of addresses of potentially overlapping literals has unspecified value">; +def note_constexpr_non_unique_object_comparison : Note< + "comparison of addresses of potentially non-unique objects has unspecified value">; def note_constexpr_literal_arith : Note< "arithmetic on addresses of potentially overlapping literals has unspecified value">; def note_constexpr_repeated_literal_eval : Note< diff --git a/clang/lib/AST/ASTImporter.cpp b/clang/lib/AST/ASTImporter.cpp index 7bab2d7dcddfa..7e6a90e2af31a 100644 --- a/clang/lib/AST/ASTImporter.cpp +++ b/clang/lib/AST/ASTImporter.cpp @@ -8547,6 +8547,8 @@ ASTNodeImporter::VisitMaterializeTemporaryExpr(MaterializeTemporaryExpr *E) { auto *ToMTE = new (Importer.getToContext()) MaterializeTemporaryExpr( ToType, ToTemporaryExpr, E->isBoundToLvalueReference(), ToMaterializedDecl); + ToMTE->setBackingArrayForInitializerList( + E->isBackingArrayForInitializerList()); return ToMTE; } diff --git a/clang/lib/AST/ByteCode/Interp.cpp b/clang/lib/AST/ByteCode/Interp.cpp index ed182ea899f61..95f391e8ac655 100644 --- a/clang/lib/AST/ByteCode/Interp.cpp +++ b/clang/lib/AST/ByteCode/Interp.cpp @@ -2527,6 +2527,118 @@ bool arePotentiallyOverlappingStringLiterals(const Pointer &LHS, return Shorter == Longer.take_front(Shorter.size()); } +/// Whether Ptr designates an object that is the backing array of a +/// std::initializer_list. +static bool isInitializerListBackingArray(const Pointer &Ptr) { + if (Ptr.isZero() || !Ptr.isBlockPointer() || Ptr.block()->isDynamic()) + return false; + + const auto *MTE = + dyn_cast_or_null<MaterializeTemporaryExpr>(Ptr.getDeclDesc()->asExpr()); + return MTE && MTE->isBackingArrayForInitializerList(); +} + +namespace { +/// Pairs an enclosing-array Pointer with the element-relative location of +/// the original pointer within it. +struct ArraySubobjectInfo { + Pointer Array; + ArraySubobjectLocation Loc; +}; +} // namespace + +/// Returns the enclosing array and element-relative location if Ptr +/// designates an element of an initializer_list backing array, +/// one-past-the-end of it, or a subobject of an element. Returns +/// std::nullopt otherwise. +static std::optional<ArraySubobjectInfo> +getArraySubobjectLocation(const ASTContext &Ctx, const Pointer &Ptr) { + if (Ptr.isZero() || !Ptr.isBlockPointer()) + return std::nullopt; + + Pointer Array = Ptr.getDeclPtr(); + if (!isInitializerListBackingArray(Array)) + return std::nullopt; + + const auto *ArrayType = + Ctx.getAsConstantArrayType(Array.getFieldDesc()->getType()); + if (!ArrayType) + return std::nullopt; + + APValue PtrValue = Ptr.toAPValue(Ctx); + if (!PtrValue.isLValue() || !PtrValue.hasLValuePath()) + return std::nullopt; + + ArrayRef<APValue::LValuePathEntry> Path = PtrValue.getLValuePath(); + if (Path.empty()) + return std::nullopt; + + uint64_t Index = Path.front().getAsArrayIndex(); + bool IsValidOnePastEnd = Path.size() == 1; + std::optional<ArraySubobjectLocation> Loc = getArraySubobjectLocationImpl( + Ctx, ArrayType, Index, PtrValue.getLValueOffset(), IsValidOnePastEnd); + if (!Loc) + return std::nullopt; + return ArraySubobjectInfo{std::move(Array), *Loc}; +} + +/// Returns true if \p LHS and \p RHS both designate elements of +/// std::initializer_list backing arrays whose values agree on the overlapping +/// region. Such backing arrays may be merged by the implementation, so +/// comparing their addresses produces an unspecified result and is rejected +/// as a constant expression. +bool arePotentiallyOverlappingInitListBackingArrays(InterpState &S, + const Pointer &LHS, + const Pointer &RHS) { + const ASTContext &Ctx = S.getASTContext(); + std::optional<ArraySubobjectInfo> LHSInfo = + getArraySubobjectLocation(Ctx, LHS); + std::optional<ArraySubobjectInfo> RHSInfo = + getArraySubobjectLocation(Ctx, RHS); + if (!LHSInfo || !RHSInfo) + return false; + + if (LHSInfo->Loc.OffsetInElement != RHSInfo->Loc.OffsetInElement) + return false; + + const auto *LHSArrayType = + Ctx.getAsConstantArrayType(LHSInfo->Array.getFieldDesc()->getType()); + const auto *RHSArrayType = + Ctx.getAsConstantArrayType(RHSInfo->Array.getFieldDesc()->getType()); + if (!LHSArrayType || !RHSArrayType || + !Ctx.hasSameType(LHSArrayType->getElementType(), + RHSArrayType->getElementType())) + return false; + + QualType ElementType = LHSArrayType->getElementType(); + int64_t LHSSize = LHSInfo->Array.getNumElems(); + int64_t RHSSize = RHSInfo->Array.getNumElems(); + int64_t LHSOffset = LHSInfo->Loc.Index; + int64_t RHSOffset = RHSInfo->Loc.Index; + int64_t OverlapBegin = std::max(-LHSOffset, -RHSOffset); + int64_t OverlapEnd = std::min(LHSSize - LHSOffset, RHSSize - RHSOffset); + if (OverlapBegin >= OverlapEnd) + return false; + + for (int64_t I = OverlapBegin; I != OverlapEnd; ++I) { + Pointer LHSElt = LHSInfo->Array.atIndex(I + LHSOffset).narrow(); + Pointer RHSElt = RHSInfo->Array.atIndex(I + RHSOffset).narrow(); + std::optional<APValue> LHSEltValue = + LHSElt.toRValue(S.getContext(), ElementType); + std::optional<APValue> RHSEltValue = + RHSElt.toRValue(S.getContext(), ElementType); + // Missing element data: be conservative and assume the backing arrays + // may share storage. + if (!LHSEltValue || !RHSEltValue) + return true; + + if (!AreAPValuesPotentiallyMergeable(*LHSEltValue, *RHSEltValue, Ctx)) + return false; + } + + return true; +} + static void copyPrimitiveMemory(InterpState &S, const Pointer &Ptr, PrimType T) { if (T == PT_IntAPS) { diff --git a/clang/lib/AST/ByteCode/Interp.h b/clang/lib/AST/ByteCode/Interp.h index 235f1c471f471..94bc9c79e3851 100644 --- a/clang/lib/AST/ByteCode/Interp.h +++ b/clang/lib/AST/ByteCode/Interp.h @@ -1253,6 +1253,9 @@ static inline bool IsOpaqueConstantCall(const CallExpr *E) { bool arePotentiallyOverlappingStringLiterals(const Pointer &LHS, const Pointer &RHS); +bool arePotentiallyOverlappingInitListBackingArrays(InterpState &S, + const Pointer &LHS, + const Pointer &RHS); template <> inline bool CmpHelperEQ<Pointer>(InterpState &S, CodePtr OpPC, CompareFn Fn) { @@ -1297,7 +1300,10 @@ inline bool CmpHelperEQ<Pointer>(InterpState &S, CodePtr OpPC, CompareFn Fn) { return true; } - // FIXME: The source check here isn't entirely correct. + // C++ [intro.object]/9: + // An object is potentially non-unique if it is a string literal object, + // the backing array of an initializer list, or a subobject thereof. + // FIXME: The string literal source check here isn't entirely correct. if (LHS.pointsToStringLiteral() && RHS.pointsToStringLiteral() && LHS.getFieldDesc()->asExpr() != RHS.getFieldDesc()->asExpr()) { if (arePotentiallyOverlappingStringLiterals(LHS, RHS)) { @@ -1317,6 +1323,12 @@ inline bool CmpHelperEQ<Pointer>(InterpState &S, CodePtr OpPC, CompareFn Fn) { return true; } + if (arePotentiallyOverlappingInitListBackingArrays(S, LHS, RHS)) { + const SourceInfo &Loc = S.Current->getSource(OpPC); + S.FFDiag(Loc, diag::note_constexpr_non_unique_object_comparison); + return false; + } + // Otherwise we need to do a bunch of extra checks before returning Unordered. if (LHS.isOnePastEnd() && !RHS.isOnePastEnd() && RHS.isBlockPointer() && RHS.getOffset() == 0) { diff --git a/clang/lib/AST/ExprCXX.cpp b/clang/lib/AST/ExprCXX.cpp index 40e129d03dcea..b485808184d8c 100644 --- a/clang/lib/AST/ExprCXX.cpp +++ b/clang/lib/AST/ExprCXX.cpp @@ -1837,6 +1837,7 @@ MaterializeTemporaryExpr::MaterializeTemporaryExpr( LifetimeExtendedTemporaryDecl *MTD) : Expr(MaterializeTemporaryExprClass, T, BoundToLvalueReference ? VK_LValue : VK_XValue, OK_Ordinary) { + MaterializeTemporaryExprBits.IsBackingArrayForInitializerList = false; if (MTD) { State = MTD; MTD->ExprWithTemporary = Temporary; diff --git a/clang/lib/AST/ExprConstShared.h b/clang/lib/AST/ExprConstShared.h index 619c79a1408f3..73c4ab76ade19 100644 --- a/clang/lib/AST/ExprConstShared.h +++ b/clang/lib/AST/ExprConstShared.h @@ -14,6 +14,7 @@ #ifndef LLVM_CLANG_LIB_AST_EXPRCONSTSHARED_H #define LLVM_CLANG_LIB_AST_EXPRCONSTSHARED_H +#include "clang/AST/CharUnits.h" #include "clang/Basic/TypeTraits.h" #include <cstdint> #include <optional> @@ -27,7 +28,8 @@ namespace clang { class QualType; class LangOptions; class ASTContext; -class CharUnits; +class APValue; +class ConstantArrayType; class Expr; } // namespace clang using namespace clang; @@ -78,6 +80,12 @@ void HandleComplexComplexDiv(llvm::APFloat A, llvm::APFloat B, llvm::APFloat C, CharUnits GetAlignOfExpr(const ASTContext &Ctx, const Expr *E, UnaryExprOrTypeTrait ExprKind); +/// Whether two APValues could be merged into a single storage location by +/// the implementation (the relation [intro.object]/9 cares about for +/// initializer_list backing arrays and string literals). +bool AreAPValuesPotentiallyMergeable(const APValue &LHS, const APValue &RHS, + const ASTContext &Ctx); + uint8_t GFNIMultiplicativeInverse(uint8_t Byte); uint8_t GFNIMul(uint8_t AByte, uint8_t BByte); uint8_t GFNIAffine(uint8_t XByte, const llvm::APInt &AQword, @@ -89,4 +97,23 @@ std::optional<llvm::APFloat> EvalScalarMinMaxFp(const llvm::APFloat &A, const llvm::APFloat &B, std::optional<llvm::APSInt> RoundingMode, bool IsMin); -#endif +/// Where an lvalue into an array element lives: the element index within the +/// array (or the array length for a one-past-the-end pointer), and the byte +/// offset from the start of that element. +struct ArraySubobjectLocation { + uint64_t Index; + CharUnits OffsetInElement; +}; + +/// Computes the array-element location designated by an lvalue whose first +/// path entry indexes into ArrayType with the given Index and whose +/// byte offset from the array base is LValueOffset. IsValidOnePastEnd +/// must be true iff the lvalue is a valid one-past-the-end position of the +/// array (which the caller determines from its own lvalue representation). +/// Returns std::nullopt if the lvalue does not designate an element, +/// one-past-the-end position, or subobject of an element. +std::optional<ArraySubobjectLocation> getArraySubobjectLocationImpl( + const ASTContext &Ctx, const ConstantArrayType *ArrayType, uint64_t Index, + CharUnits LValueOffset, bool IsValidOnePastEnd); + +#endif // LLVM_CLANG_LIB_AST_EXPRCONSTSHARED_H diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 38aa5798cfeb9..8016e7ec0eae4 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -56,6 +56,7 @@ #include "clang/Basic/TargetBuiltins.h" #include "clang/Basic/TargetInfo.h" #include "llvm/ADT/APFixedPoint.h" +#include "llvm/ADT/FoldingSet.h" #include "llvm/ADT/Sequence.h" #include "llvm/ADT/SmallBitVector.h" #include "llvm/ADT/StringExtras.h" @@ -2024,17 +2025,18 @@ struct LValueBaseString { int CharWidth; }; -// Gets the lvalue base of LVal as a string. -static bool GetLValueBaseAsString(const EvalInfo &Info, const LValue &LVal, +// Gets the lvalue base as a string. +static bool GetLValueBaseAsString(const ASTContext &Ctx, + APValue::LValueBase Base, LValueBaseString &AsString) { - const auto *BaseExpr = LVal.Base.dyn_cast<const Expr *>(); + const auto *BaseExpr = Base.dyn_cast<const Expr *>(); if (!BaseExpr) return false; // For ObjCEncodeExpr, we need to compute and store the string. if (const auto *EE = dyn_cast<ObjCEncodeExpr>(BaseExpr)) { - Info.Ctx.getObjCEncodingForType(EE->getEncodedType(), - AsString.ObjCEncodeStorage); + Ctx.getObjCEncodingForType(EE->getEncodedType(), + AsString.ObjCEncodeStorage); AsString.Bytes = AsString.ObjCEncodeStorage; AsString.CharWidth = 1; return true; @@ -2053,6 +2055,11 @@ static bool GetLValueBaseAsString(const EvalInfo &Info, const LValue &LVal, return true; } +static bool GetLValueBaseAsString(const EvalInfo &Info, const LValue &LVal, + LValueBaseString &AsString) { + return GetLValueBaseAsString(Info.Ctx, LVal.Base, AsString); +} + // Determine whether two string literals potentially overlap. This will be the // case if they agree on the values of all the bytes on the overlapping region // between them. @@ -2067,31 +2074,31 @@ static bool GetLValueBaseAsString(const EvalInfo &Info, const LValue &LVal, // addresses onwards. // // See open core issue CWG2765 which is discussing the desired rule here. -static bool ArePotentiallyOverlappingStringLiterals(const EvalInfo &Info, - const LValue &LHS, - const LValue &RHS) { - LValueBaseString LHSString, RHSString; - if (!GetLValueBaseAsString(Info, LHS, LHSString) || - !GetLValueBaseAsString(Info, RHS, RHSString)) - return false; +static bool ArePotentiallyOverlappingStringLiterals( + const LValueBaseString &LHSString, CharUnits LHSOffset, + const LValueBaseString &RHSString, CharUnits RHSOffset) { + assert(LHSString.Bytes.data() && RHSString.Bytes.data() && + "passing a string with no underlying storage"); // This is the byte offset to the location of the first character of LHS // within RHS. We don't need to look at the characters of one string that // would appear before the start of the other string if they were merged. - CharUnits Offset = RHS.Offset - LHS.Offset; + StringRef LHSBytes = LHSString.Bytes; + StringRef RHSBytes = RHSString.Bytes; + CharUnits Offset = RHSOffset - LHSOffset; if (Offset.isNegative()) { - if (LHSString.Bytes.size() < (size_t)-Offset.getQuantity()) + if (LHSBytes.size() < (size_t)-Offset.getQuantity()) return false; - LHSString.Bytes = LHSString.Bytes.drop_front(-Offset.getQuantity()); + LHSBytes = LHSBytes.drop_front(-Offset.getQuantity()); } else { - if (RHSString.Bytes.size() < (size_t)Offset.getQuantity()) + if (RHSBytes.size() < (size_t)Offset.getQuantity()) return false; - RHSString.Bytes = RHSString.Bytes.drop_front(Offset.getQuantity()); + RHSBytes = RHSBytes.drop_front(Offset.getQuantity()); } - bool LHSIsLonger = LHSString.Bytes.size() > RHSString.Bytes.size(); - StringRef Longer = LHSIsLonger ? LHSString.Bytes : RHSString.Bytes; - StringRef Shorter = LHSIsLonger ? RHSString.Bytes : LHSString.Bytes; + bool LHSIsLonger = LHSBytes.size() > RHSBytes.size(); + StringRef Longer = LHSIsLonger ? LHSBytes : RHSBytes; + StringRef Shorter = LHSIsLonger ? RHSBytes : LHSBytes; int ShorterCharWidth = (LHSIsLonger ? RHSString : LHSString).CharWidth; // The null terminator isn't included in the string data, so check for it @@ -2109,6 +2116,32 @@ static bool ArePotentiallyOverlappingStringLiterals(const EvalInfo &Info, return Shorter == Longer.take_front(Shorter.size()); } +static bool ArePotentiallyOverlappingStringLiterals(const EvalInfo &Info, + const LValue &LHS, + const LValue &RHS) { + LValueBaseString LHSString, RHSString; + if (!GetLValueBaseAsString(Info, LHS, LHSString) || + !GetLValueBaseAsString(Info, RHS, RHSString)) + return false; + + return ArePotentiallyOverlappingStringLiterals(LHSString, LHS.Offset, + RHSString, RHS.Offset); +} + +/// APValue overload of the string-literal overlap predicate. Returns nullopt +/// if either operand is not a string-literal (or ObjC encoding) LValue. +static std::optional<bool> ArePotentiallyOverlappingStringLiterals( + const ASTContext &Ctx, const APValue &LHS, const APValue &RHS) { + if (!LHS.isLValue() || !RHS.isLValue()) + return std::nullopt; + LValueBaseString LHSString, RHSString; + if (!GetLValueBaseAsString(Ctx, LHS.getLValueBase(), LHSString) || + !GetLValueBaseAsString(Ctx, RHS.getLValueBase(), RHSString)) + return std::nullopt; + return ArePotentiallyOverlappingStringLiterals( + LHSString, LHS.getLValueOffset(), RHSString, RHS.getLValueOffset()); +} + static bool IsWeakLValue(const LValue &Value) { const ValueDecl *Decl = GetLValueBaseDecl(Value); return Decl && Decl->isWeak(); @@ -4860,6 +4893,259 @@ static CompleteObject findCompleteObject(EvalInfo &Info, const Expr *E, return CompleteObject(LVal.getLValueBase(), BaseVal, BaseType); } +static const APValue *GetArrayInitializedElt(const APValue &Array, + uint64_t Index) { + if (!Array.isArray() || Index >= Array.getArraySize()) + return nullptr; + if (Index < Array.getArrayInitializedElts()) + return &Array.getArrayInitializedElt(Index); + return Array.hasArrayFiller() ? &Array.getArrayFiller() : nullptr; +} + +/// True if the LValue base names a weak entity (whose link-time identity +/// may be merged with another). +static bool isWeakLValueBase(const APValue &V) { + if (!V.isLValue()) + return false; + if (const auto *VD = V.getLValueBase().dyn_cast<const ValueDecl *>()) + return VD->isWeak(); + return false; +} + +/// True if the member-pointer target decl is weak. +static bool isWeakMemberPointer(const APValue &V) { + if (!V.isMemberPointer()) + return false; + const ValueDecl *D = V.getMemberPointerDecl(); + return D && D->isWeak(); +} + +/// AreAPValuesPotentiallyMergeableSlow - The slow path for +/// AreAPValuesPotentiallyMergeable. Will be invoked when the two values' +/// APValue::Profile already differs. Returns true if the disagreement is one +/// the runtime can collapse (overlapping string literals, weak decls that might +/// resolve to the same target), recursing into aggregate kinds so that +/// aggregates containing such leaves are still reported as mergeable. +static bool AreAPValuesPotentiallyMergeableSlow(const APValue &LHS, + const APValue &RHS, + const ASTContext &Ctx) { + if (!LHS.hasValue() || !RHS.hasValue()) + return true; + if (LHS.getKind() != RHS.getKind()) + return false; + + switch (LHS.getKind()) { + case APValue::LValue: { + // Distinct string-literal LValues whose content could be merged. + if (auto Overlap = ArePotentiallyOverlappingStringLiterals(Ctx, LHS, RHS)) + return *Overlap; + // Either side names a weak entity -> link-time may resolve to one. + return isWeakLValueBase(LHS) || isWeakLValueBase(RHS); + } + + case APValue::MemberPointer: + return isWeakMemberPointer(LHS) || isWeakMemberPointer(RHS); + + case APValue::Struct: { + if (LHS.getStructNumBases() != RHS.getStructNumBases() || + LHS.getStructNumFields() != RHS.getStructNumFields()) + return false; + for (unsigned I = 0, N = LHS.getStructNumBases(); I != N; ++I) + if (!AreAPValuesPotentiallyMergeable(LHS.getStructBase(I), + RHS.getStructBase(I), Ctx)) + return false; + for (unsigned I = 0, N = LHS.getStructNumFields(); I != N; ++I) + if (!AreAPValuesPotentiallyMergeable(LHS.getStructField(I), + RHS.getStructField(I), Ctx)) + return false; + return true; + } + + case APValue::Union: { + if (LHS.getUnionField() != RHS.getUnionField()) + return false; + if (!LHS.getUnionField()) + return true; + return AreAPValuesPotentiallyMergeable(LHS.getUnionValue(), + RHS.getUnionValue(), Ctx); + } + + case APValue::Array: { + if (LHS.getArraySize() != RHS.getArraySize()) + return false; + for (unsigned I = 0, N = LHS.getArraySize(); I != N; ++I) { + const APValue *LE = GetArrayInitializedElt(LHS, I); + const APValue *RE = GetArrayInitializedElt(RHS, I); + // Missing element data: be conservative. + if (!LE || !RE) + return true; + if (!AreAPValuesPotentiallyMergeable(*LE, *RE, Ctx)) + return false; + } + return true; + } + + case APValue::Vector: { + if (LHS.getVectorLength() != RHS.getVectorLength()) + return false; + for (unsigned I = 0, N = LHS.getVectorLength(); I != N; ++I) + if (!AreAPValuesPotentiallyMergeable(LHS.getVectorElt(I), + RHS.getVectorElt(I), Ctx)) + return false; + return true; + } + + case APValue::Matrix: { + if (LHS.getMatrixNumRows() != RHS.getMatrixNumRows() || + LHS.getMatrixNumColumns() != RHS.getMatrixNumColumns()) + return false; + for (unsigned I = 0, N = LHS.getMatrixNumElements(); I != N; ++I) + if (!AreAPValuesPotentiallyMergeable(LHS.getMatrixElt(I), + RHS.getMatrixElt(I), Ctx)) + return false; + return true; + } + + // For every scalar kind (Int, Float, FixedPoint, ComplexInt, ComplexFloat, + // AddrLabelDiff, None, Indeterminate), Profile already captures the full + // observable content. If Profile disagreed, the values are observably + // distinct. + default: + return false; + } +} + +bool AreAPValuesPotentiallyMergeable(const APValue &LHS, const APValue &RHS, + const ASTContext &Ctx) { + // Fast path: bitwise-identical APValues -> definitely mergeable. + llvm::FoldingSetNodeID LHSID, RHSID; + LHS.Profile(LHSID); + RHS.Profile(RHSID); + if (LHSID == RHSID) + return true; + + return AreAPValuesPotentiallyMergeableSlow(LHS, RHS, Ctx); +} + +std::optional<ArraySubobjectLocation> getArraySubobjectLocationImpl( + const ASTContext &Ctx, const ConstantArrayType *ArrayType, uint64_t Index, + CharUnits LValueOffset, bool IsValidOnePastEnd) { + uint64_t ArraySize = ArrayType->getZExtSize(); + if (Index > ArraySize) + return std::nullopt; + + if (Index == ArraySize) { + if (!IsValidOnePastEnd) + return std::nullopt; + return ArraySubobjectLocation{Index, CharUnits::Zero()}; + } + + if (LValueOffset.isNegative()) + return std::nullopt; + + uint64_t ElementSize = + Ctx.getTypeSizeInChars(ArrayType->getElementType()).getQuantity(); + if (ElementSize && Index > std::numeric_limits<uint64_t>::max() / ElementSize) + return std::nullopt; + + uint64_t ElementOffset = ElementSize * Index; + uint64_t LValueOffsetQ = LValueOffset.getQuantity(); + if (LValueOffsetQ < ElementOffset) + return std::nullopt; + + uint64_t OffsetInElement = LValueOffsetQ - ElementOffset; + if (OffsetInElement > ElementSize) + return std::nullopt; + + return ArraySubobjectLocation{Index, + CharUnits::fromQuantity(OffsetInElement)}; +} + +/// Returns the array element and element-relative location that LV +/// designates, or std::nullopt if LV is not an element, one-past-the-end +/// position, or subobject of an element of its base array. +static std::optional<ArraySubobjectLocation> +getArraySubobjectLocation(const ASTContext &Ctx, const LValue &LV) { + if (LV.Designator.Invalid || LV.Designator.Entries.empty()) + return std::nullopt; + + const auto *ArrayType = Ctx.getAsConstantArrayType(getType(LV.Base)); + if (!ArrayType) + return std::nullopt; + + uint64_t Index = LV.Designator.Entries.front().getAsArrayIndex(); + bool IsValidOnePastEnd = + LV.Designator.Entries.size() == 1 && LV.Designator.isOnePastTheEnd(); + return getArraySubobjectLocationImpl(Ctx, ArrayType, Index, LV.Offset, + IsValidOnePastEnd); +} + +static const APValue *GetCompleteObjectValue(EvalInfo &Info, const Expr *E, + const LValue &LV) { + CompleteObject Obj = + findCompleteObject(Info, E, AK_Read, LV, getType(LV.Base)); + return Obj ? Obj.Value : nullptr; +} + +static bool isInitializerListBackingArray(const LValue &LV) { + const auto *BaseExpr = LV.Base.dyn_cast<const Expr *>(); + const auto *MTE = dyn_cast_or_null<MaterializeTemporaryExpr>(BaseExpr); + return MTE && MTE->isBackingArrayForInitializerList(); +} + +static bool ArePotentiallyOverlappingInitListBackingArrays(EvalInfo &Info, + const Expr *E, + const LValue &LHS, + const LValue &RHS) { + if (!isInitializerListBackingArray(LHS) || + !isInitializerListBackingArray(RHS)) + return false; + + std::optional<ArraySubobjectLocation> LHSLoc = + getArraySubobjectLocation(Info.Ctx, LHS); + std::optional<ArraySubobjectLocation> RHSLoc = + getArraySubobjectLocation(Info.Ctx, RHS); + if (!LHSLoc || !RHSLoc) + return false; + + if (LHSLoc->OffsetInElement != RHSLoc->OffsetInElement) + return false; + + const auto *LHSArrayType = Info.Ctx.getAsConstantArrayType(getType(LHS.Base)); + const auto *RHSArrayType = Info.Ctx.getAsConstantArrayType(getType(RHS.Base)); + if (!LHSArrayType || !RHSArrayType || + !Info.Ctx.hasSameType(LHSArrayType->getElementType(), + RHSArrayType->getElementType())) + return false; + + const APValue *LHSArray = GetCompleteObjectValue(Info, E, LHS); + const APValue *RHSArray = GetCompleteObjectValue(Info, E, RHS); + if (!LHSArray || !RHSArray || !LHSArray->isArray() || !RHSArray->isArray()) + return false; + + int64_t LHSSize = LHSArray->getArraySize(); + int64_t RHSSize = RHSArray->getArraySize(); + int64_t LHSOffset = LHSLoc->Index; + int64_t RHSOffset = RHSLoc->Index; + int64_t OverlapBegin = std::max(-LHSOffset, -RHSOffset); + int64_t OverlapEnd = std::min(LHSSize - LHSOffset, RHSSize - RHSOffset); + if (OverlapBegin >= OverlapEnd) + return false; + + for (int64_t I = OverlapBegin; I != OverlapEnd; ++I) { + const APValue *LHSElt = GetArrayInitializedElt(*LHSArray, I + LHSOffset); + const APValue *RHSElt = GetArrayInitializedElt(*RHSArray, I + RHSOffset); + // Missing element data: be conservative and assume the arrays may share + // storage. + if (!LHSElt || !RHSElt) + return true; + if (!AreAPValuesPotentiallyMergeable(*LHSElt, *RHSElt, Info.Ctx)) + return false; + } + + return true; +} + /// Perform an lvalue-to-rvalue conversion on the given glvalue. This /// can also be used for 'lvalue-to-lvalue' conversions for looking up the /// glvalue referred to by an entity of reference type. @@ -18695,9 +18981,12 @@ EvaluateComparisonBinaryOperator(EvalInfo &Info, const BinaryOperator *E, // This makes the comparison result unspecified, so it's not a constant // expression. // - // TODO: Do we need to handle the initializer list case here? if (ArePotentiallyOverlappingStringLiterals(Info, LHSValue, RHSValue)) return DiagComparison(diag::note_constexpr_literal_comparison); + if (ArePotentiallyOverlappingInitListBackingArrays(Info, E, LHSValue, + RHSValue)) + return DiagComparison( + diag::note_constexpr_non_unique_object_comparison); if (IsOpaqueConstantCall(LHSValue) || IsOpaqueConstantCall(RHSValue)) return DiagComparison(diag::note_constexpr_opaque_call_comparison, !IsOpaqueConstantCall(LHSValue)); diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp index 96a398aa21dad..0df78035e5325 100644 --- a/clang/lib/AST/Type.cpp +++ b/clang/lib/AST/Type.cpp @@ -3319,6 +3319,87 @@ bool Type::isStdByteType() const { return false; } +bool Type::isStdClassTemplateSpecialization(const ASTContext &Ctx, + StringRef ClassName, + QualType *TypeArg, + ClassTemplateDecl **CachedDecl, + const Decl **MalformedDecl) const { + QualType SugaredType(this, 0); + auto ReportMatchingNameAsMalformed = [&](NamedDecl *D) { + if (!MalformedDecl) + return; + if (!D) + D = SugaredType->getAsTagDecl(); + if (!D || !D->isInStdNamespace()) + return; + IdentifierInfo *II = D->getDeclName().getAsIdentifierInfo(); + if (II && II->isStr(ClassName)) + *MalformedDecl = D; + }; + + ClassTemplateDecl *Template = nullptr; + ArrayRef<TemplateArgument> Arguments; + if (const TemplateSpecializationType *TST = + SugaredType->getAsNonAliasTemplateSpecializationType()) { + Template = dyn_cast_or_null<ClassTemplateDecl>( + TST->getTemplateName().getAsTemplateDecl()); + Arguments = TST->template_arguments(); + } else if (const auto *TT = SugaredType->getAs<TagType>()) { + Template = TT->getTemplateDecl(); + Arguments = TT->getTemplateArgs(Ctx); + } + + if (!Template) { + ReportMatchingNameAsMalformed(SugaredType->getAsTagDecl()); + return false; + } + + ClassTemplateDecl *Cached = CachedDecl ? *CachedDecl : nullptr; + if (!Cached) { + CXXRecordDecl *TemplateClass = Template->getTemplatedDecl(); + IdentifierInfo *II = TemplateClass->getIdentifier(); + if (!II || !II->isStr(ClassName) || !TemplateClass->isInStdNamespace()) + return false; + + TemplateParameterList *Params = Template->getTemplateParameters(); + if (Params->getMinRequiredArguments() != 1 || + !isa<TemplateTypeParmDecl>(Params->getParam(0)) || + Params->getParam(0)->isTemplateParameterPack()) { + if (MalformedDecl) + *MalformedDecl = TemplateClass; + return false; + } + + Cached = Template; + if (CachedDecl) + *CachedDecl = Template; + } + + if (Template->getCanonicalDecl() != Cached->getCanonicalDecl()) + return false; + + if (TypeArg) { + if (Arguments.empty() || Arguments[0].getKind() != TemplateArgument::Type) + return false; + + QualType ArgType = Arguments[0].getAsType(); + if (Ctx.getLangOpts().ObjCAutoRefCount && ArgType->isObjCLifetimeType() && + !ArgType.getObjCLifetime()) { + Qualifiers Qs; + Qs.setObjCLifetime(Qualifiers::OCL_Strong); + ArgType = Ctx.getQualifiedType(ArgType, Qs); + } + *TypeArg = ArgType; + } + + return true; +} + +bool Type::isStdInitializerListType(const ASTContext &Ctx, + QualType *Element) const { + return isStdClassTemplateSpecialization(Ctx, "initializer_list", Element); +} + bool Type::isSpecifierType() const { // Note that this intentionally does not use the canonical type. switch (getTypeClass()) { diff --git a/clang/lib/Sema/CheckExprLifetime.cpp b/clang/lib/Sema/CheckExprLifetime.cpp index 4e050e9bf6045..b85337a743e08 100644 --- a/clang/lib/Sema/CheckExprLifetime.cpp +++ b/clang/lib/Sema/CheckExprLifetime.cpp @@ -285,17 +285,11 @@ static bool isContainerOfOwner(const RecordDecl *Container) { isGslOwnerType(TAs[0].getAsType()); } -// Returns true if the given Record is `std::initializer_list<pointer>`. -static bool isStdInitializerListOfPointer(const RecordDecl *RD) { - if (const auto *CTSD = - dyn_cast_if_present<ClassTemplateSpecializationDecl>(RD)) { - const auto &TAs = CTSD->getTemplateArgs(); - return lifetimes::isInStlNamespace(RD) && RD->getIdentifier() && - RD->getName() == "initializer_list" && TAs.size() > 0 && - TAs[0].getKind() == TemplateArgument::Type && - lifetimes::isPointerLikeType(TAs[0].getAsType()); - } - return false; +// Returns true if the given type is `std::initializer_list<pointer>`. +static bool isStdInitializerListOfPointer(QualType T, const ASTContext &Ctx) { + QualType ElementType; + return T.getNonReferenceType()->isStdInitializerListType(Ctx, &ElementType) && + lifetimes::isPointerLikeType(ElementType); } // Returns true if the given constructor is a copy-like constructor, such as @@ -351,7 +345,8 @@ shouldTrackFirstArgumentForConstructor(const CXXConstructExpr *Ctor) { // array. We perform analysis on it to determine if there are any dangling // temporaries in the backing array. // E.g. std::vector<string_view> abc = {string()}; - if (isStdInitializerListOfPointer(RHSRD)) + if (isStdInitializerListOfPointer(RHSArgType, + Ctor->getConstructor()->getASTContext())) return true; // RHS must be an owner. diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp index 7c126186831eb..ee79bad64ae44 100644 --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -12321,96 +12321,6 @@ NamespaceDecl *Sema::getOrCreateStdNamespace() { return getStdNamespace(); } -static bool isStdClassTemplate(Sema &S, QualType SugaredType, QualType *TypeArg, - const char *ClassName, - ClassTemplateDecl **CachedDecl, - const Decl **MalformedDecl) { - // We're looking for implicit instantiations of - // template <typename U> class std::{ClassName}. - - if (!S.StdNamespace) // If we haven't seen namespace std yet, this can't be - // it. - return false; - - auto ReportMatchingNameAsMalformed = [&](NamedDecl *D) { - if (!MalformedDecl) - return; - if (!D) - D = SugaredType->getAsTagDecl(); - if (!D || !D->isInStdNamespace()) - return; - IdentifierInfo *II = D->getDeclName().getAsIdentifierInfo(); - if (II && II == &S.PP.getIdentifierTable().get(ClassName)) - *MalformedDecl = D; - }; - - ClassTemplateDecl *Template = nullptr; - ArrayRef<TemplateArgument> Arguments; - if (const TemplateSpecializationType *TST = - SugaredType->getAsNonAliasTemplateSpecializationType()) { - Template = dyn_cast_or_null<ClassTemplateDecl>( - TST->getTemplateName().getAsTemplateDecl()); - Arguments = TST->template_arguments(); - } else if (const auto *TT = SugaredType->getAs<TagType>()) { - Template = TT->getTemplateDecl(); - Arguments = TT->getTemplateArgs(S.Context); - } - - if (!Template) { - ReportMatchingNameAsMalformed(SugaredType->getAsTagDecl()); - return false; - } - - if (!*CachedDecl) { - // Haven't recognized std::{ClassName} yet, maybe this is it. - // FIXME: It seems we should just reuse LookupStdClassTemplate but the - // semantics of this are slightly different, most notably the existing - // "lookup" semantics explicitly diagnose an invalid definition as an - // error. - CXXRecordDecl *TemplateClass = Template->getTemplatedDecl(); - if (TemplateClass->getIdentifier() != - &S.PP.getIdentifierTable().get(ClassName) || - !S.getStdNamespace()->InEnclosingNamespaceSetOf( - TemplateClass->getNonTransparentDeclContext())) - return false; - // This is a template called std::{ClassName}, but is it the right - // template? - TemplateParameterList *Params = Template->getTemplateParameters(); - if (Params->getMinRequiredArguments() != 1 || - !isa<TemplateTypeParmDecl>(Params->getParam(0)) || - Params->getParam(0)->isTemplateParameterPack()) { - if (MalformedDecl) - *MalformedDecl = TemplateClass; - return false; - } - - // It's the right template. - *CachedDecl = Template; - } - - if (Template->getCanonicalDecl() != (*CachedDecl)->getCanonicalDecl()) - return false; - - // This is an instance of std::{ClassName}. Find the argument type. - if (TypeArg) { - QualType ArgType = Arguments[0].getAsType(); - // FIXME: Since TST only has as-written arguments, we have to perform the - // only kind of conversion applicable to type arguments; in Objective-C ARC: - // - If an explicitly-specified template argument type is a lifetime type - // with no lifetime qualifier, the __strong lifetime qualifier is - // inferred. - if (S.getLangOpts().ObjCAutoRefCount && ArgType->isObjCLifetimeType() && - !ArgType.getObjCLifetime()) { - Qualifiers Qs; - Qs.setObjCLifetime(Qualifiers::OCL_Strong); - ArgType = S.Context.getQualifiedType(ArgType, Qs); - } - *TypeArg = ArgType; - } - - return true; -} - bool Sema::isStdInitializerList(QualType Ty, QualType *Element) { assert(getLangOpts().CPlusPlus && "Looking for std::initializer_list outside of C++."); @@ -12418,8 +12328,10 @@ bool Sema::isStdInitializerList(QualType Ty, QualType *Element) { // We're looking for implicit instantiations of // template <typename E> class std::initializer_list. - return isStdClassTemplate(*this, Ty, Element, "initializer_list", - &StdInitializerList, /*MalformedDecl=*/nullptr); + return !Ty.isNull() && + Ty->isStdClassTemplateSpecialization(Context, "initializer_list", + Element, &StdInitializerList, + /*MalformedDecl=*/nullptr); } bool Sema::isStdTypeIdentity(QualType Ty, QualType *Element, @@ -12430,8 +12342,9 @@ bool Sema::isStdTypeIdentity(QualType Ty, QualType *Element, // We're looking for implicit instantiations of // template <typename T> struct std::type_identity. - return isStdClassTemplate(*this, Ty, Element, "type_identity", - &StdTypeIdentity, MalformedDecl); + return !Ty.isNull() && + Ty->isStdClassTemplateSpecialization(Context, "type_identity", Element, + &StdTypeIdentity, MalformedDecl); } static ClassTemplateDecl *LookupStdClassTemplate(Sema &S, SourceLocation Loc, diff --git a/clang/lib/Sema/SemaInit.cpp b/clang/lib/Sema/SemaInit.cpp index efc816c0d8b75..640ad8abecbc0 100644 --- a/clang/lib/Sema/SemaInit.cpp +++ b/clang/lib/Sema/SemaInit.cpp @@ -8677,6 +8677,7 @@ ExprResult InitializationSequence::Perform(Sema &S, MaterializeTemporaryExpr *MTE = S.CreateMaterializeTemporaryExpr( CurInit.get()->getType(), CurInit.get(), /*BoundToLvalueReference=*/false); + MTE->setBackingArrayForInitializerList(); // Wrap it in a construction of a std::initializer_list<T>. CurInit = new (S.Context) CXXStdInitializerListExpr(Step->Type, MTE); diff --git a/clang/lib/Serialization/ASTReaderStmt.cpp b/clang/lib/Serialization/ASTReaderStmt.cpp index 7e51ce8c0aca2..436179101f3b1 100644 --- a/clang/lib/Serialization/ASTReaderStmt.cpp +++ b/clang/lib/Serialization/ASTReaderStmt.cpp @@ -2322,6 +2322,7 @@ void ASTStmtReader::VisitFunctionParmPackExpr(FunctionParmPackExpr *E) { void ASTStmtReader::VisitMaterializeTemporaryExpr(MaterializeTemporaryExpr *E) { VisitExpr(E); + E->setBackingArrayForInitializerList(Record.readInt()); bool HasMaterialzedDecl = Record.readInt(); if (HasMaterialzedDecl) E->State = cast<LifetimeExtendedTemporaryDecl>(Record.readDecl()); diff --git a/clang/lib/Serialization/ASTWriterStmt.cpp b/clang/lib/Serialization/ASTWriterStmt.cpp index a7e815a1ef438..c425b8b47b923 100644 --- a/clang/lib/Serialization/ASTWriterStmt.cpp +++ b/clang/lib/Serialization/ASTWriterStmt.cpp @@ -2332,6 +2332,7 @@ void ASTStmtWriter::VisitFunctionParmPackExpr(FunctionParmPackExpr *E) { void ASTStmtWriter::VisitMaterializeTemporaryExpr(MaterializeTemporaryExpr *E) { VisitExpr(E); + Record.push_back(E->isBackingArrayForInitializerList()); Record.push_back(static_cast<bool>(E->getLifetimeExtendedTemporaryDecl())); if (E->getLifetimeExtendedTemporaryDecl()) Record.AddDeclRef(E->getLifetimeExtendedTemporaryDecl()); diff --git a/clang/test/AST/ByteCode/initializer_list.cpp b/clang/test/AST/ByteCode/initializer_list.cpp index f882e4ff1b124..a6751453a7f3a 100644 --- a/clang/test/AST/ByteCode/initializer_list.cpp +++ b/clang/test/AST/ByteCode/initializer_list.cpp @@ -68,4 +68,304 @@ namespace rdar13395022 { } } +namespace cwg2765 { + constexpr bool same(std::initializer_list<int> a, + std::initializer_list<int> b) { + return a.begin() != b.begin(); // #cwg2765-init-list-compare + } + static_assert(same({1}, {1}), ""); + // both-error@-1 {{static assertion expression is not an integral constant expression}} + // both-note@#cwg2765-init-list-compare {{comparison of addresses of potentially non-unique objects has unspecified value}} + // both-note@-3 {{in call to}} + + template <class T> + constexpr bool begins_equal(T a, T b) { + return a.begin() == b.begin(); // #cwg2765-template-compare + } + constexpr bool same_three = + begins_equal<std::initializer_list<int>>({1, 2, 3}, {1, 2, 3}); + // both-error@-2 {{constexpr variable 'same_three' must be initialized by a constant expression}} + // both-note@#cwg2765-template-compare {{comparison of addresses of potentially non-unique objects has unspecified value}} + // both-note@-3 {{in call to}} + + constexpr bool different_three = + begins_equal<std::initializer_list<int>>({1, 2, 3}, {4, 5, 6}); + static_assert(!different_three, ""); + + constexpr bool same_object() { + std::initializer_list<int> il = {1, 1, 1}; + return il.begin() == il.begin() && il.begin() != il.begin() + 1; + } + static_assert(same_object(), ""); + + constexpr bool same_pointer_value() { + std::initializer_list<int> il = {1, 1, 1}; + const int *p = il.begin(); + return p + 0 == p && p != p + 1; + } + static_assert(same_pointer_value(), ""); + + constexpr bool local_list(const int *p) { + std::initializer_list<int> il = {1, 2, 3}; + return p ? (p == il.begin()) : local_list(il.begin()); // #cwg2765-local-compare + } + constexpr bool same_local = local_list(nullptr); // #cwg2765-local-call + // both-error@-1 {{constexpr variable 'same_local' must be initialized by a constant expression}} + // both-note@#cwg2765-local-compare {{comparison of addresses of potentially non-unique objects has unspecified value}} + // both-note@#cwg2765-local-compare {{in call to}} + // both-note@#cwg2765-local-call {{in call to}} + + constexpr bool shifted(std::initializer_list<int> a, + std::initializer_list<int> b) { + return a.begin() != b.begin() + 1; // #cwg2765-shifted-compare + } + constexpr bool annex_c = shifted({2, 3}, {1, 2, 3}); + // both-error@-1 {{constexpr variable 'annex_c' must be initialized by a constant expression}} + // both-note@#cwg2765-shifted-compare {{comparison of addresses of potentially non-unique objects has unspecified value}} + // both-note@-3 {{in call to}} + + constexpr bool end_shifted(std::initializer_list<int> a, + std::initializer_list<int> b) { + return a.end() == b.begin() + 1; // #cwg2765-end-shifted-compare + } + constexpr bool same_end_shifted = end_shifted({1}, {1, 2}); + // both-error@-1 {{constexpr variable 'same_end_shifted' must be initialized by a constant expression}} + // both-note@#cwg2765-end-shifted-compare {{comparison of addresses of potentially non-unique objects has unspecified value}} + // both-note@-3 {{in call to}} + + constexpr bool different_end_shifted = end_shifted({1}, {2, 1}); + static_assert(!different_end_shifted, ""); + + struct Box { + int n; + }; + constexpr bool class_same(std::initializer_list<Box> a, + std::initializer_list<Box> b) { + return a.begin() == b.begin(); // #cwg2765-class-compare + } + constexpr bool same_box = class_same({{1}}, {{1}}); + // both-error@-1 {{constexpr variable 'same_box' must be initialized by a constant expression}} + // both-note@#cwg2765-class-compare {{comparison of addresses of potentially non-unique objects has unspecified value}} + // both-note@-3 {{in call to}} + + constexpr bool different_box = class_same({{1}}, {{2}}); + static_assert(!different_box, ""); + + constexpr bool class_field_same(std::initializer_list<Box> a, + std::initializer_list<Box> b) { + return &a.begin()->n == &b.begin()->n; // #cwg2765-field-compare + } + constexpr bool same_box_field = class_field_same({{1}}, {{1}}); + // both-error@-1 {{constexpr variable 'same_box_field' must be initialized by a constant expression}} + // both-note@#cwg2765-field-compare {{comparison of addresses of potentially non-unique objects has unspecified value}} + // both-note@-3 {{in call to}} + + constexpr bool different_box_field = class_field_same({{1}}, {{2}}); + static_assert(!different_box_field, ""); + + struct WithPointer { + const int *p; + int n; + }; + constexpr int pointer_anchor = 0; + constexpr int pointer_anchor_2 = 0; + constexpr bool class_with_pointer_same(std::initializer_list<WithPointer> a, + std::initializer_list<WithPointer> b) { + return a.begin() == b.begin(); // #cwg2765-with-pointer-compare + } + // Both elements compare equal in the scalar field; the pointer field + // points to the same global, so observable equality is Unknown overall + // and the address comparison is unspecified. + constexpr bool same_after_pointer_field = + class_with_pointer_same({{&pointer_anchor, 1}}, + {{&pointer_anchor, 1}}); + // both-error@-3 {{constexpr variable 'same_after_pointer_field' must be initialized by a constant expression}} + // both-note@#cwg2765-with-pointer-compare {{comparison of addresses of potentially non-unique objects has unspecified value}} + // both-note@-4 {{in call to}} + + // The non-pointer field already disagrees, so we can conclude the + // backing arrays are distinct even though a pointer field is present. + constexpr bool different_after_pointer_field = + class_with_pointer_same({{&pointer_anchor, 1}}, + {{&pointer_anchor, 2}}); + static_assert(!different_after_pointer_field, ""); + + // Distinct strong-symbol globals have distinct addresses, so the + // pointer field alone is enough to conclude the backing arrays differ. + constexpr bool different_pointer_field = + class_with_pointer_same({{&pointer_anchor, 1}}, + {{&pointer_anchor_2, 1}}); + static_assert(!different_pointer_field, ""); + + // A null pointer is distinct from a pointer-to-object. + constexpr bool one_null_pointer = + class_with_pointer_same({{nullptr, 1}}, {{&pointer_anchor, 1}}); + static_assert(!one_null_pointer, ""); + // Both null and scalar fields agree -> potentially overlapping. + constexpr bool both_null_pointer = + class_with_pointer_same({{nullptr, 1}}, {{nullptr, 1}}); // #cwg2765-both-null-call + // both-error@-2 {{constexpr variable 'both_null_pointer' must be initialized by a constant expression}} + // both-note@#cwg2765-with-pointer-compare {{comparison of addresses of potentially non-unique objects has unspecified value}} + // both-note@#cwg2765-both-null-call {{in call to}} + + struct WithStringPointer { const char *p; }; + constexpr bool class_string_pointer_same( + std::initializer_list<WithStringPointer> a, + std::initializer_list<WithStringPointer> b) { + return a.begin() == b.begin(); // #cwg2765-string-ptr-compare + } + constexpr bool different_string_pointer = + class_string_pointer_same({{"abc"}}, {{"def"}}); + static_assert(!different_string_pointer, ""); + + constexpr bool same_string_pointer = + class_string_pointer_same({{"abc"}}, {{"abc"}}); // #cwg2765-same-string-ptr-call + // both-error@-2 {{constexpr variable 'same_string_pointer' must be initialized by a constant expression}} + // both-note@#cwg2765-string-ptr-compare {{comparison of addresses of potentially non-unique objects has unspecified value}} + // both-note@#cwg2765-same-string-ptr-call {{in call to}} + + // Both pointers are at offset 0 and the literals have different sizes, so + // "abc" cannot be merged into the start of "abcd": that would require a + // null terminator at offset 3 of "abcd", which holds 'd'. The string-overlap + // predicate resolves this to NotEqual. + constexpr bool prefix_string_pointer = + class_string_pointer_same({{"abc"}}, {{"abcd"}}); + static_assert(!prefix_string_pointer, ""); + + // A null const char * is distinct from any non-null string literal. + constexpr bool null_vs_string = + class_string_pointer_same({{nullptr}}, {{"abc"}}); + static_assert(!null_vs_string, ""); + + // Floating-point uses bitwise equality: -0.0 is distinguishable from +0.0. + struct WithFloat { double d; }; + constexpr bool class_float_same(std::initializer_list<WithFloat> a, + std::initializer_list<WithFloat> b) { + return a.begin() == b.begin(); + } + constexpr bool different_signed_zero = class_float_same({{-0.0}}, {{0.0}}); + static_assert(!different_signed_zero, ""); + + // Anonymous union field: the active member differs between LHS and RHS, so + // the backing arrays must have distinct addresses. + struct WithAnonUnion { + union { int i; float f; }; + int tag; + }; + constexpr bool class_anon_union_same(std::initializer_list<WithAnonUnion> a, + std::initializer_list<WithAnonUnion> b) { + return a.begin() == b.begin(); + } + constexpr bool different_union_member = + class_anon_union_same({{.i = 0, .tag = 0}}, {{.f = 0.0f, .tag = 0}}); + static_assert(!different_union_member, ""); + + // Member pointers compare structurally: distinct target fields imply + // distinct member-pointer values, so the backing arrays differ. + struct Holder { int x; int y; }; + struct WithMemberPtr { int Holder::*p; }; + constexpr bool class_member_ptr_same( + std::initializer_list<WithMemberPtr> a, + std::initializer_list<WithMemberPtr> b) { + return a.begin() == b.begin(); // #cwg2765-member-ptr-compare + } + constexpr bool different_member_ptr = + class_member_ptr_same({{&Holder::x}}, {{&Holder::y}}); + static_assert(!different_member_ptr, ""); + + // Same member pointer in both arrays + only this field -> potentially + // overlapping, so address comparison is unspecified. + constexpr bool same_member_ptr = + class_member_ptr_same({{&Holder::x}}, {{&Holder::x}}); // #cwg2765-same-mptr-call + // both-error@-2 {{constexpr variable 'same_member_ptr' must be initialized by a constant expression}} + // both-note@#cwg2765-member-ptr-compare {{comparison of addresses of potentially non-unique objects has unspecified value}} + // both-note@#cwg2765-same-mptr-call {{in call to}} + + // Weak member functions: pointers to the same weak decl still resolve to + // a single merged target at link time, so two arrays holding pointers to + // the same weak member are still potentially-overlapping (not provably + // distinct, but not provably equal either — the link-time merge is fine). + struct HolderWithWeak { + __attribute__((weak)) void f(); + __attribute__((weak)) void g(); + }; + struct WithWeakMemberPtr { void (HolderWithWeak::*p)(); }; + constexpr bool class_weak_mptr_same( + std::initializer_list<WithWeakMemberPtr> a, + std::initializer_list<WithWeakMemberPtr> b) { + return a.begin() == b.begin(); // #cwg2765-weak-mptr-compare + } + // Distinct weak decls may merge at link time -> Unknown. + constexpr bool different_weak_member_ptr = + class_weak_mptr_same({{&HolderWithWeak::f}}, // #cwg2765-diff-weak-mptr-call + {{&HolderWithWeak::g}}); + // both-error@-3 {{constexpr variable 'different_weak_member_ptr' must be initialized by a constant expression}} + // both-note@#cwg2765-weak-mptr-compare {{comparison of addresses of potentially non-unique objects has unspecified value}} + // both-note@#cwg2765-diff-weak-mptr-call {{in call to}} + + // nullptr_t fields: the only value of nullptr_t is null, so this field + // never contributes inequality. The other field decides. + struct WithNullptrT { decltype(nullptr) np; int n; }; + constexpr bool class_nullptr_t_same(std::initializer_list<WithNullptrT> a, + std::initializer_list<WithNullptrT> b) { + return a.begin() == b.begin(); + } + constexpr bool different_after_nullptr_t = + class_nullptr_t_same({{nullptr, 1}}, {{nullptr, 2}}); + static_assert(!different_after_nullptr_t, ""); + + // Aggregate carrying a std::initializer_list member: the backing array's + // extending declaration is the enclosing aggregate, not the + // initializer_list. The dedicated marker on MaterializeTemporaryExpr + // still recognises the backing array. + struct WithIL { std::initializer_list<int> il; }; + constexpr WithIL agg_a{{1}}, agg_b{{1}}; + constexpr bool agg_same = agg_a.il.begin() == agg_b.il.begin(); // #cwg2765-agg-compare + // both-error@-1 {{constexpr variable 'agg_same' must be initialized by a constant expression}} + // both-note@#cwg2765-agg-compare {{comparison of addresses of potentially non-unique objects has unspecified value}} + + constexpr WithIL agg_c{{1}}, agg_d{{2}}; + constexpr bool agg_different = agg_c.il.begin() == agg_d.il.begin(); + static_assert(!agg_different, ""); + + // Rvalue reference to array bound to a braced-init-list: the materialized + // array is NOT a std::initializer_list backing array. + constexpr bool rvref_arr_same(const int (&&a)[3], const int (&&b)[3]) { + return &a[0] == &b[0]; + } + constexpr bool rvref_ok = rvref_arr_same({1, 2, 3}, {1, 2, 3}); + static_assert(!rvref_ok, ""); + + // Vector elements: APValue::Profile recurses element-wise, so the + // mergeability judgment is correctly element-sensitive (NOT "hard-code + // these rare kinds to NotEqual"). Confirmed by runtime observation: + // identical-content backing arrays are mergeable, differing ones are not. + typedef int v4 __attribute__((ext_vector_type(4))); + constexpr v4 va = {1, 2, 3, 4}; + constexpr v4 vb = {1, 2, 3, 5}; + struct WithVec { v4 v; }; + constexpr bool class_vec_same(std::initializer_list<WithVec> a, + std::initializer_list<WithVec> b) { + return a.begin() == b.begin(); // #cwg2765-vec-compare + } + constexpr bool same_vec = class_vec_same({{va}}, {{va}}); // #cwg2765-same-vec-call + // both-error@-1 {{constexpr variable 'same_vec' must be initialized by a constant expression}} + // both-note@#cwg2765-vec-compare {{comparison of addresses of potentially non-unique objects has unspecified value}} + // both-note@#cwg2765-same-vec-call {{in call to}} + + constexpr bool different_vec = class_vec_same({{va}}, {{vb}}); + static_assert(!different_vec, ""); + + // Complex elements: Profile bitwise-compares real / imag, so two + // complex values differing in only the imaginary part are correctly + // recognised as distinct. + struct WithComplex { _Complex double c; }; + constexpr bool class_complex_same(std::initializer_list<WithComplex> a, + std::initializer_list<WithComplex> b) { + return a.begin() == b.begin(); + } + constexpr bool different_complex = class_complex_same( + {{__builtin_complex(1.0, 2.0)}}, {{__builtin_complex(1.0, 3.0)}}); + static_assert(!different_complex, ""); +} diff --git a/clang/test/CXX/dcl.decl/dcl.init/dcl.init.list/p5.cpp b/clang/test/CXX/dcl.decl/dcl.init/dcl.init.list/p5.cpp new file mode 100644 index 0000000000000..a64ce068913b5 --- /dev/null +++ b/clang/test/CXX/dcl.decl/dcl.init/dcl.init.list/p5.cpp @@ -0,0 +1,139 @@ +// RUN: %clang_cc1 %std_cxx11- -fexceptions -fcxx-exceptions -fsyntax-only --embed-dir=%S/../../../../Preprocessor/Inputs -Wno-c23-extensions -verify %s + +namespace std { +using size_t = decltype(sizeof(int)); + +template <class E> class initializer_list { + const E *begin_; + size_t size_; + +public: + constexpr initializer_list() : begin_(nullptr), size_(0) {} + constexpr initializer_list(const E *begin, size_t size) + : begin_(begin), size_(size) {} + constexpr const E *begin() const { return begin_; } + constexpr const E *end() const { return begin_ + size_; } + constexpr size_t size() const { return size_; } +}; + +template <class T> struct complex { + constexpr complex(double) {} +}; + +template <class T> struct vector { + vector(initializer_list<T>); +}; +} // namespace std + +namespace example12 { +void f(std::initializer_list<double> il); +void g(float x) { + f({1, x, 3}); +} +void h() { + f({1, 2, 3}); +} + +struct A { + mutable int i; +}; +void q(std::initializer_list<A>); +void r() { + q({A{1}, A{2}, A{3}}); +} +} // namespace example12 + +namespace example13 { +typedef std::complex<double> cmplx; +std::vector<cmplx> v1 = {1, 2, 3}; +void f() { + std::vector<cmplx> v2{1, 2, 3}; + std::initializer_list<int> i3 = {1, 2, 3}; +} + +struct A { + std::initializer_list<int> i4; // expected-note {{'std::initializer_list' member declared here}} + A() : i4{1, 2, 3} {} + // expected-error@-1 {{backing array for 'std::initializer_list' member 'i4' is a temporary object whose lifetime would be shorter than the lifetime of the constructed object}} +}; +} // namespace example13 + +namespace embed_example { +void bytes(std::initializer_list<unsigned char>); +void f() { + bytes({ +#embed <jk.txt> + }); +} + +constexpr std::initializer_list<unsigned char> jk = { +#embed <jk.txt> +}; +static_assert(jk.size() == 2, ""); +static_assert(jk.begin()[0] == 'j', ""); +static_assert(jk.begin()[1] == 'k', ""); +} // namespace embed_example + +namespace shared_backing_arrays { +void f2(std::initializer_list<int> ia, std::initializer_list<int> ib) { + (void)(ia.begin() == ib.begin()); +} +void test() { + f2({1, 2, 3}, {1, 2, 3}); +} + +void f3() { + std::initializer_list<int> i1 = {1, 2, 3, 4, 5}; + std::initializer_list<int> i2 = {2, 3, 4}; + (void)(i1.begin() == i2.begin() + 1); +} +} // namespace shared_backing_arrays + +namespace lifetime_is_unchanged { +const int *f4(std::initializer_list<int> i4) { + return i4.begin(); +} +void test() { + const int *p = f4({1, 2, 3}); + (void)*p; +} +} // namespace lifetime_is_unchanged + +namespace destructor_side_effects { +extern "C" int printf(const char *, ...); + +struct C6 { + constexpr C6(int) {} + ~C6() { printf(" X"); } +}; + +void f6(std::initializer_list<C6>) {} +void test() { + f6({1, 2, 3}); + f6({1, 2, 3}); +} +} // namespace destructor_side_effects + +namespace mutable_members { +struct S { + constexpr S(int i) : i(i) {} + mutable int i; +}; + +void f(std::initializer_list<S> il) { + if (il.begin()->i != 1) + throw; + il.begin()->i = 4; +} +void test() { + for (int i = 0; i < 2; ++i) + f({1, 2, 3}); +} +} // namespace mutable_members + +namespace annex_c { +bool ne(std::initializer_list<int> a, std::initializer_list<int> b) { + return a.begin() != b.begin() + 1; +} +bool b = ne({2, 3}, {1, 2, 3}); +} // namespace annex_c diff --git a/clang/test/CXX/drs/cwg27xx.cpp b/clang/test/CXX/drs/cwg27xx.cpp index 16fd56e0c4a63..ea28892a633e7 100644 --- a/clang/test/CXX/drs/cwg27xx.cpp +++ b/clang/test/CXX/drs/cwg27xx.cpp @@ -1,10 +1,10 @@ -// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++98 -fexceptions -fcxx-exceptions -pedantic-errors %s -verify-directives -verify=expected,cxx98 -// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++11 -fexceptions -fcxx-exceptions -pedantic-errors %s -verify-directives -verify=expected,since-cxx11 -// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++14 -fexceptions -fcxx-exceptions -pedantic-errors %s -verify-directives -verify=expected,since-cxx11,since-cxx14 -// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++17 -fexceptions -fcxx-exceptions -pedantic-errors %s -verify-directives -verify=expected,since-cxx11,since-cxx14 -// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++20 -fexceptions -fcxx-exceptions -pedantic-errors %s -verify-directives -verify=expected,since-cxx11,since-cxx14,since-cxx20 -// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++23 -fexceptions -fcxx-exceptions -pedantic-errors %s -verify-directives -verify=expected,since-cxx11,since-cxx14,since-cxx20,since-cxx23 -// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++2c -fexceptions -fcxx-exceptions -pedantic-errors %s -verify-directives -verify=expected,since-cxx11,since-cxx14,since-cxx20,since-cxx23,since-cxx26 +// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++98 -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,cxx98,cxx98-20 %s +// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++11 -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,since-cxx11,cxx98-20 %s +// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++14 -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,since-cxx11,since-cxx14,cxx98-20 %s +// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++17 -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,since-cxx11,since-cxx14,cxx98-20 %s +// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++20 -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,since-cxx11,since-cxx14,since-cxx20,cxx98-20 %s +// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++23 -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,since-cxx11,since-cxx14,since-cxx20,since-cxx23 %s +// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++2c -fexceptions -fcxx-exceptions -pedantic-errors -verify-directives -verify=expected,since-cxx11,since-cxx14,since-cxx20,since-cxx23,since-cxx26 %s #if __cplusplus == 199711L #define static_assert(...) __extension__ _Static_assert(__VA_ARGS__) @@ -19,22 +19,37 @@ namespace std { #if __cplusplus >= 201103L -using size_t = decltype(sizeof(0)); -template <typename T> -struct initializer_list { - const T *p; - size_t n; - - #if __cplusplus >= 201402L - constexpr - #endif - initializer_list(const T *p, size_t n); - - #if __cplusplus >= 201402L - constexpr - #endif - const T* begin() const { return p; }; -}; + using size_t = decltype(sizeof(int)); + + template <class E> class initializer_list { + const E *begin_; + size_t size_; + + public: +#if __cplusplus >= 201402L + constexpr +#endif + initializer_list() : begin_(nullptr), size_(0) {} +#if __cplusplus >= 201402L + constexpr +#endif + initializer_list(const E *begin, size_t size) + : begin_(begin), size_(size) {} +#if __cplusplus >= 201402L + constexpr +#endif + const E *begin() const { return begin_; } +#if __cplusplus >= 201402L + constexpr +#endif + size_t size() const { return size_; } + }; + + struct string_view { + const char *begin_; + constexpr string_view(const char *begin) : begin_(begin) {} + constexpr const char *begin() const { return begin_; } + }; #endif #if __cplusplus >= 202002L @@ -193,7 +208,7 @@ static_assert(!__is_layout_compatible(StructWithAnonUnion, StructWithAnonUnion3) #endif } // namespace cwg2759 -namespace cwg2765 { // cwg2765: partial +namespace cwg2765 { // cwg2765: 23 static_assert(+"foo" == "foo", ""); // expected-error@-1 {{static assertion expression is not an integral constant expression}} // expected-note@-2 {{comparison of addresses of potentially overlapping literals has unspecified value}} @@ -211,19 +226,107 @@ static_assert((const char*)"foo" != "oo", ""); #if __cplusplus >= 201103L constexpr const char *f() { return "foo"; } -constexpr bool b2 = f() == f(); + +constexpr bool b2 = f() == f(); // since-cxx11-error@-1 {{constexpr variable 'b2' must be initialized by a constant expression}} // since-cxx11-note@-2 {{comparison of addresses of potentially overlapping literals has unspecified value}} constexpr const char *p = f(); -constexpr bool b3 = p == p; -#endif +constexpr bool b3 = p == p; +static_assert(b3, ""); + +constexpr bool b4 = &"xfoo"[1] == &"foo\0y"[0]; +// since-cxx11-error@-1 {{constexpr variable 'b4' must be initialized by a constant expression}} +// since-cxx11-note@-2 {{comparison of addresses of potentially overlapping literals has unspecified value}} +static_assert("foo" != &"bar"[0], ""); +static_assert((const char *)"foo" != "oo", ""); + +template <class T> +constexpr bool f10(T s, T t) { + return s.begin() == t.begin(); // #cwg2765-f10-compare +} +constexpr bool b10a = f10<std::string_view>("abc", "abc"); +// since-cxx11-error@-1 {{constexpr variable 'b10a' must be initialized by a constant expression}} +// since-cxx11-note@#cwg2765-f10-compare {{comparison of addresses of potentially overlapping literals has unspecified value}} +// since-cxx11-note@-3 {{in call to 'f10<std::string_view>({&"abc"[0]}, {&"abc"[0]})'}} +constexpr bool b10b = f10<std::string_view>("abc", "def"); +static_assert(!b10b, ""); + +constexpr const char *a11 = "abc"; +constexpr const char *b11 = "abc"; +constexpr bool f11() { return a11 == b11; } // #cwg2765-f11-compare +// cxx98-20-error@-1 {{constexpr function never produces a constant expression}} +// cxx98-20-note@#cwg2765-f11-compare {{comparison of addresses of potentially overlapping literals has unspecified value}} +static_assert(f11() || !f11(), ""); +// since-cxx11-error@-1 {{static assertion expression is not an integral constant expression}} +// since-cxx11-note@#cwg2765-f11-compare {{comparison of addresses of potentially overlapping literals has unspecified value}} +// since-cxx11-note@-3 {{in call to 'f11()'}} #if __cplusplus >= 201402L -constexpr std::initializer_list<int *> il1 = { (int *)nullptr }; -constexpr std::initializer_list<unsigned long> il2 = { 0 }; -constexpr bool b7 = il1.begin() == (void *)il2.begin(); -// FIXME-error@-1 {{constexpr variable 'b7' must be initialized by a constant expression}} -// FIXME-note@-2 {{address of a constexpr-unknown object cannot be used for comparison}} +constexpr bool f(std::initializer_list<int> a, std::initializer_list<int> b) { + return a.begin() != b.begin(); // #cwg2765-init-list-compare +} +static_assert(f({1}, {1}), ""); +// since-cxx14-error@-1 {{static assertion expression is not an integral constant expression}} +// since-cxx14-note@#cwg2765-init-list-compare {{comparison of addresses of potentially non-unique objects has unspecified value}} +// since-cxx14-note@-3 {{in call to 'f({&{1}[0], 1}, {&{1}[0], 1})'}} + +constexpr bool f9(const int *p) { + std::initializer_list<int> il = {1, 2, 3}; + return p ? (p == il.begin()) : f9(il.begin()); // #cwg2765-f9-compare +} +constexpr bool b9 = f9(nullptr); +// since-cxx14-error@-1 {{constexpr variable 'b9' must be initialized by a constant expression}} +// since-cxx14-note@#cwg2765-f9-compare {{comparison of addresses of potentially non-unique objects has unspecified value}} +// since-cxx14-note@#cwg2765-f9-compare {{in call to 'f9(&{1, 2, 3}[0])'}} +// since-cxx14-note@-4 {{in call to 'f9(nullptr)'}} + +constexpr bool b10c = f10<std::initializer_list<int>>({1, 2, 3}, {1, 2, 3}); +// since-cxx14-error@-1 {{constexpr variable 'b10c' must be initialized by a constant expression}} +// since-cxx14-note@#cwg2765-f10-compare {{comparison of addresses of potentially non-unique objects has unspecified value}} +// since-cxx14-note@-3 {{in call to 'f10<std::initializer_list<int>>({&{1, 2, 3}[0], 3}, {&{1, 2, 3}[0], 3})'}} +constexpr bool b10d = f10<std::initializer_list<int>>({1, 2, 3}, {4, 5, 6}); +static_assert(!b10d, ""); + +constexpr bool ne(std::initializer_list<int> a, + std::initializer_list<int> b) { + return a.begin() != b.begin() + 1; // #cwg2765-annex-c-compare +} +constexpr bool annex_c = ne({2, 3}, {1, 2, 3}); +// since-cxx14-error@-1 {{constexpr variable 'annex_c' must be initialized by a constant expression}} +// since-cxx14-note@#cwg2765-annex-c-compare {{comparison of addresses of potentially non-unique objects has unspecified value}} +// since-cxx14-note@-3 {{in call to 'ne({&{2, 3}[0], 2}, {&{1, 2, 3}[0], 3})'}} + +int a_order[10]; +constexpr int inc(int &i) { return (i += 1); } +constexpr int twox(int &i) { return (i *= 2); } +constexpr int f_order(int i) { return inc(i) + twox(i); } +constexpr bool g_order() { return &a_order[f_order(1)] == &a_order[6]; } +constexpr bool b_order = g_order(); +static_assert(b_order, ""); + +// Aggregate carrying a std::initializer_list member: the backing array's +// extending declaration is the enclosing aggregate, not the +// initializer_list. The precise marker on MaterializeTemporaryExpr must +// still recognise the backing array. +struct WithIL { std::initializer_list<int> il; }; +constexpr WithIL agg_a{{1}}, agg_b{{1}}; +constexpr bool agg_same = agg_a.il.begin() == agg_b.il.begin(); +// since-cxx14-error@-1 {{constexpr variable 'agg_same' must be initialized by a constant expression}} +// since-cxx14-note@-2 {{comparison of addresses of potentially non-unique objects has unspecified value}} + +constexpr WithIL agg_c{{1}}, agg_d{{2}}; +constexpr bool agg_different = agg_c.il.begin() == agg_d.il.begin(); +static_assert(!agg_different, ""); + +// Rvalue reference to array bound to a braced-init-list: the materialized +// array is not a std::initializer_list backing array, so address +// comparisons across two such temporaries are well-defined. +constexpr bool rvref_arr_same(const int (&&a)[3], const int (&&b)[3]) { + return &a[0] == &b[0]; +} +constexpr bool rvref_ok = rvref_arr_same({1, 2, 3}, {1, 2, 3}); +static_assert(!rvref_ok, ""); +#endif #endif } // namespace cwg2765 diff --git a/clang/test/CodeGenCXX/Inputs/jk.txt b/clang/test/CodeGenCXX/Inputs/jk.txt new file mode 100644 index 0000000000000..93d177a48c83a --- /dev/null +++ b/clang/test/CodeGenCXX/Inputs/jk.txt @@ -0,0 +1 @@ +jk \ No newline at end of file diff --git a/clang/test/CodeGenCXX/p2752r3-initializer-list.cpp b/clang/test/CodeGenCXX/p2752r3-initializer-list.cpp new file mode 100644 index 0000000000000..3f5ecac648066 --- /dev/null +++ b/clang/test/CodeGenCXX/p2752r3-initializer-list.cpp @@ -0,0 +1,93 @@ +// RUN: %clang_cc1 %std_cxx11- -triple x86_64-unknown-linux-gnu -fexceptions -fcxx-exceptions -emit-llvm -o - --embed-dir=%S/Inputs -Wno-c23-extensions %s | FileCheck %s + +namespace std { +using size_t = decltype(sizeof(int)); + +template <class E> class initializer_list { + const E *begin_; + size_t size_; + +public: + constexpr initializer_list() : begin_(nullptr), size_(0) {} + constexpr initializer_list(const E *begin, size_t size) + : begin_(begin), size_(size) {} + constexpr const E *begin() const { return begin_; } + constexpr size_t size() const { return size_; } +}; +} // namespace std + +namespace example12 { +void f(std::initializer_list<double> il); + +void g(float x) { + // CHECK-LABEL: define{{.*}} void @_ZN9example121gEf( + // CHECK: alloca [3 x double], + // CHECK: fpext float + // CHECK: call void @_ZN9example121fESt16initializer_listIdE( + f({1, x, 3}); +} + +void h() { + // CHECK-LABEL: define{{.*}} void @_ZN9example121hEv( + // CHECK: alloca [3 x double], + // CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 8 {{.*}}, ptr align 8 @constinit, i64 24, + // CHECK: call void @_ZN9example121fESt16initializer_listIdE( + f({1, 2, 3}); +} +} // namespace example12 + +namespace embed_example { +void bytes(std::initializer_list<unsigned char>); + +void f() { + // CHECK-LABEL: define{{.*}} void @_ZN13embed_example1fEv( + // CHECK: alloca [2 x i8], + // CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 1 {{.*}}, ptr align 1 @.str, i64 2, + // CHECK: call void @_ZN13embed_example5bytesESt16initializer_listIhE( + bytes({ +#embed <jk.txt> + }); +} +} // namespace embed_example + +namespace destructor_side_effects { +extern "C" int printf(const char *, ...); + +struct C6 { + constexpr C6(int) {} + ~C6() { printf(" X"); } +}; + +void f6(std::initializer_list<C6>) {} + +void test() { + // CHECK-LABEL: define{{.*}} void @_ZN23destructor_side_effects4testEv( + // CHECK: call void @_ZN23destructor_side_effects2f6ESt16initializer_listINS_2C6EE( + // CHECK: call void @_ZN23destructor_side_effects2C6D1Ev( + // CHECK: call void @_ZN23destructor_side_effects2f6ESt16initializer_listINS_2C6EE( + // CHECK: call void @_ZN23destructor_side_effects2C6D1Ev( + f6({1, 2, 3}); + f6({1, 2, 3}); +} +} // namespace destructor_side_effects + +namespace mutable_members { +struct S { + constexpr S(int i) : i(i) {} + mutable int i; +}; + +void f(std::initializer_list<S> il) { + if (il.begin()->i != 1) + throw; + il.begin()->i = 4; +} + +void test() { + // CHECK-LABEL: define{{.*}} void @_ZN15mutable_members4testEv( + // CHECK: alloca [3 x %"struct.mutable_members::S"], + // CHECK: call void @_ZN15mutable_members1fESt16initializer_listINS_1SEE( + for (int i = 0; i < 2; ++i) + f({1, 2, 3}); +} +} // namespace mutable_members diff --git a/clang/www/cxx_dr_status.html b/clang/www/cxx_dr_status.html index e090e4afcaeca..66efea4d0999f 100755 --- a/clang/www/cxx_dr_status.html +++ b/clang/www/cxx_dr_status.html @@ -19160,7 +19160,7 @@ <h2 id="cxxdr">C++ defect report implementation status</h2> <td>[<a href="https://wg21.link/intro.object">intro.object</a>]</td> <td>DR</td> <td>Address comparisons between potentially non-unique objects during constant evaluation</td> - <td class="partial" align="center">Partial</td> + <td class="full" align="center">Clang 23</td> </tr> <tr class="open" id="2766"> <td><a href="https://cplusplus.github.io/CWG/issues/2766.html">2766</a></td> diff --git a/clang/www/cxx_status.html b/clang/www/cxx_status.html index 315fa54531a02..860c4b6e66cf7 100755 --- a/clang/www/cxx_status.html +++ b/clang/www/cxx_status.html @@ -135,7 +135,7 @@ <h2 id="cxx26">C++2c implementation status</h2> <tr> <td>Static storage for braced initializers</td> <td><a href="https://wg21.link/P2752R3">P2752R3</a> (<a href="#dr">DR</a>)</td> - <td class="none" align="center">No</td> + <td class="unreleased" align="center">Clang 23</td> </tr> <tr> <td>User-generated <tt>static_assert</tt> messages</td> _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
