From 47ccfd7a89e2a9a747a7114db18db1376324799c Mon Sep 17 00:00:00 2001 From: Corentin Jabot Date: Tue, 1 Nov 2022 13:37:12 +0100 Subject: [PATCH] [Clang] Implement P2741R3 - user-generated static_assert messages Reviewed By: #clang-language-wg, aaron.ballman Differential Revision: https://reviews.llvm.org/D154290 --- clang/docs/ReleaseNotes.rst | 1 + clang/include/clang/AST/DeclCXX.h | 12 +- clang/include/clang/AST/Expr.h | 5 + clang/include/clang/Basic/DiagnosticSemaKinds.td | 22 +- clang/include/clang/Sema/Sema.h | 20 +- clang/lib/AST/DeclCXX.cpp | 3 +- clang/lib/AST/DeclPrinter.cpp | 4 +- clang/lib/AST/ExprConstant.cpp | 44 +++- clang/lib/AST/ODRDiagsEmitter.cpp | 41 ++-- clang/lib/Frontend/InitPreprocessor.cpp | 6 +- clang/lib/Parse/ParseDeclCXX.cpp | 7 +- clang/lib/Sema/SemaDeclCXX.cpp | 168 ++++++++++++- clang/lib/Sema/SemaOverload.cpp | 66 +++-- clang/lib/Sema/SemaTemplateInstantiateDecl.cpp | 13 +- clang/test/Lexer/cxx-features.cpp | 6 +- clang/test/SemaCXX/static-assert-cxx26.cpp | 291 +++++++++++++++++++++++ clang/tools/libclang/CIndex.cpp | 2 +- clang/www/cxx_status.html | 2 +- 18 files changed, 631 insertions(+), 82 deletions(-) create mode 100644 clang/test/SemaCXX/static-assert-cxx26.cpp diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 55fa4cb..ef1cc89 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -137,6 +137,7 @@ C++2c Feature Support - Implemented `P2738R1: constexpr cast from void* `_. - Partially implemented `P2361R6: Unevaluated strings `_. The changes to attributes declarations are not part of this release. +- Implemented `P2741R3: user-generated static_assert messages `_. Resolutions to C++ Defect Reports ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/clang/include/clang/AST/DeclCXX.h b/clang/include/clang/AST/DeclCXX.h index f4322e1..afec815 100644 --- a/clang/include/clang/AST/DeclCXX.h +++ b/clang/include/clang/AST/DeclCXX.h @@ -4010,12 +4010,12 @@ public: /// Represents a C++11 static_assert declaration. class StaticAssertDecl : public Decl { llvm::PointerIntPair AssertExprAndFailed; - StringLiteral *Message; + Expr *Message; SourceLocation RParenLoc; StaticAssertDecl(DeclContext *DC, SourceLocation StaticAssertLoc, - Expr *AssertExpr, StringLiteral *Message, - SourceLocation RParenLoc, bool Failed) + Expr *AssertExpr, Expr *Message, SourceLocation RParenLoc, + bool Failed) : Decl(StaticAssert, DC, StaticAssertLoc), AssertExprAndFailed(AssertExpr, Failed), Message(Message), RParenLoc(RParenLoc) {} @@ -4027,15 +4027,15 @@ public: static StaticAssertDecl *Create(ASTContext &C, DeclContext *DC, SourceLocation StaticAssertLoc, - Expr *AssertExpr, StringLiteral *Message, + Expr *AssertExpr, Expr *Message, SourceLocation RParenLoc, bool Failed); static StaticAssertDecl *CreateDeserialized(ASTContext &C, unsigned ID); Expr *getAssertExpr() { return AssertExprAndFailed.getPointer(); } const Expr *getAssertExpr() const { return AssertExprAndFailed.getPointer(); } - StringLiteral *getMessage() { return Message; } - const StringLiteral *getMessage() const { return Message; } + Expr *getMessage() { return Message; } + const Expr *getMessage() const { return Message; } bool isFailed() const { return AssertExprAndFailed.getInt(); } diff --git a/clang/include/clang/AST/Expr.h b/clang/include/clang/AST/Expr.h index 7a886f5..f9795b6 100644 --- a/clang/include/clang/AST/Expr.h +++ b/clang/include/clang/AST/Expr.h @@ -762,6 +762,11 @@ public: /// strlen, false otherwise. bool tryEvaluateStrLen(uint64_t &Result, ASTContext &Ctx) const; + bool EvaluateCharRangeAsString(std::string &Result, + const Expr *SizeExpression, + const Expr *PtrExpression, ASTContext &Ctx, + EvalResult &Status) const; + /// Enumeration used to describe the kind of Null pointer constant /// returned from \c isNullPointerConstant(). enum NullPointerConstantKind { diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 52cca5a..7faefab0a 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -83,8 +83,8 @@ def err_typecheck_converted_constant_expression_indirect : Error< "bind reference to a temporary">; def err_expr_not_cce : Error< "%select{case value|enumerator value|non-type template argument|" - "array size|explicit specifier argument|noexcept specifier argument}0 " - "is not a constant expression">; + "array size|explicit specifier argument|noexcept specifier argument|" + "call to 'size()'|call to 'data()'}0 is not a constant expression">; def ext_cce_narrowing : ExtWarn< "%select{case value|enumerator value|non-type template argument|" "array size|explicit specifier argument|noexcept specifier argument}0 " @@ -1545,6 +1545,24 @@ def err_static_assert_requirement_failed : Error< "static assertion failed due to requirement '%0'%select{: %2|}1">; def note_expr_evaluates_to : Note< "expression evaluates to '%0 %1 %2'">; +def err_static_assert_invalid_message : Error< + "the message in a static assertion must be a string literal or an " + "object with 'data()' and 'size()' member functions">; +def err_static_assert_missing_member_function : Error< + "the message object in this static assertion is missing %select{" + "a 'size()' member function|" + "a 'data()' member function|" + "'data()' and 'size()' member functions}0">; +def err_static_assert_invalid_mem_fn_ret_ty : Error< + "the message in a static assertion must have a '%select{size|data}0()' member " + "function returning an object convertible to '%select{std::size_t|const char *}0'">; +def warn_static_assert_message_constexpr : Warning< + "the message in this static assertion is not a " + "constant expression">, + DefaultError, InGroup>; +def err_static_assert_message_constexpr : Error< + "the message in a static assertion must be produced by a " + "constant expression">; def warn_consteval_if_always_true : Warning< "consteval if is always true in an %select{unevaluated|immediate}0 context">, diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index 400b6f7..082fa25 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -3887,8 +3887,17 @@ public: CCEK_TemplateArg, ///< Value of a non-type template parameter. CCEK_ArrayBound, ///< Array bound in array declarator or new-expression. CCEK_ExplicitBool, ///< Condition in an explicit(bool) specifier. - CCEK_Noexcept ///< Condition in a noexcept(bool) specifier. + CCEK_Noexcept, ///< Condition in a noexcept(bool) specifier. + CCEK_StaticAssertMessageSize, ///< Call to size() in a static assert + ///< message. + CCEK_StaticAssertMessageData, ///< Call to data() in a static assert + ///< message. }; + + ExprResult BuildConvertedConstantExpression(Expr *From, QualType T, + CCEKind CCE, + NamedDecl *Dest = nullptr); + ExprResult CheckConvertedConstantExpression(Expr *From, QualType T, llvm::APSInt &Value, CCEKind CCE); ExprResult CheckConvertedConstantExpression(Expr *From, QualType T, @@ -7795,15 +7804,16 @@ public: void UnmarkAsLateParsedTemplate(FunctionDecl *FD); bool IsInsideALocalClassWithinATemplateFunction(); + bool EvaluateStaticAssertMessageAsString(Expr *Message, std::string &Result, + ASTContext &Ctx, + bool ErrorOnInvalidMessage); Decl *ActOnStaticAssertDeclaration(SourceLocation StaticAssertLoc, Expr *AssertExpr, Expr *AssertMessageExpr, SourceLocation RParenLoc); Decl *BuildStaticAssertDeclaration(SourceLocation StaticAssertLoc, - Expr *AssertExpr, - StringLiteral *AssertMessageExpr, - SourceLocation RParenLoc, - bool Failed); + Expr *AssertExpr, Expr *AssertMessageExpr, + SourceLocation RParenLoc, bool Failed); void DiagnoseStaticAssertDetails(const Expr *E); FriendDecl *CheckFriendTypeDecl(SourceLocation LocStart, diff --git a/clang/lib/AST/DeclCXX.cpp b/clang/lib/AST/DeclCXX.cpp index bbd7901..e4572aa 100644 --- a/clang/lib/AST/DeclCXX.cpp +++ b/clang/lib/AST/DeclCXX.cpp @@ -3237,8 +3237,7 @@ void StaticAssertDecl::anchor() {} StaticAssertDecl *StaticAssertDecl::Create(ASTContext &C, DeclContext *DC, SourceLocation StaticAssertLoc, - Expr *AssertExpr, - StringLiteral *Message, + Expr *AssertExpr, Expr *Message, SourceLocation RParenLoc, bool Failed) { return new (C, DC) StaticAssertDecl(DC, StaticAssertLoc, AssertExpr, Message, diff --git a/clang/lib/AST/DeclPrinter.cpp b/clang/lib/AST/DeclPrinter.cpp index 4a285f3..a6a9911 100644 --- a/clang/lib/AST/DeclPrinter.cpp +++ b/clang/lib/AST/DeclPrinter.cpp @@ -949,9 +949,9 @@ void DeclPrinter::VisitStaticAssertDecl(StaticAssertDecl *D) { Out << "static_assert("; D->getAssertExpr()->printPretty(Out, nullptr, Policy, Indentation, "\n", &Context); - if (StringLiteral *SL = D->getMessage()) { + if (Expr *E = D->getMessage()) { Out << ", "; - SL->printPretty(Out, nullptr, Policy, Indentation, "\n", &Context); + E->printPretty(Out, nullptr, Policy, Indentation, "\n", &Context); } Out << ")"; } diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index f0185d0..b5308ed 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -50,6 +50,7 @@ #include "clang/AST/StmtVisitor.h" #include "clang/AST/TypeLoc.h" #include "clang/Basic/Builtins.h" +#include "clang/Basic/DiagnosticSema.h" #include "clang/Basic/TargetInfo.h" #include "llvm/ADT/APFixedPoint.h" #include "llvm/ADT/SmallBitVector.h" @@ -1995,7 +1996,8 @@ static bool IsGlobalLValue(APValue::LValueBase B) { // ... a null pointer value, or a prvalue core constant expression of type // std::nullptr_t. - if (!B) return true; + if (!B) + return true; if (const ValueDecl *D = B.dyn_cast()) { // ... the address of an object with static storage duration, @@ -2126,6 +2128,7 @@ static void NoteLValueLocation(EvalInfo &Info, APValue::LValueBase Base) { Info.Note((*Alloc)->AllocExpr->getExprLoc(), diag::note_constexpr_dynamic_alloc_here); } + // We have no information to show for a typeid(T) object. } @@ -16379,6 +16382,45 @@ static bool EvaluateBuiltinStrLen(const Expr *E, uint64_t &Result, } } +bool Expr::EvaluateCharRangeAsString(std::string &Result, + const Expr *SizeExpression, + const Expr *PtrExpression, ASTContext &Ctx, + EvalResult &Status) const { + LValue String; + EvalInfo Info(Ctx, Status, EvalInfo::EM_ConstantExpression); + Info.InConstantContext = true; + + FullExpressionRAII Scope(Info); + APSInt SizeValue; + if (!::EvaluateInteger(SizeExpression, SizeValue, Info)) + return false; + + int64_t Size = SizeValue.getExtValue(); + + if (!::EvaluatePointer(PtrExpression, String, Info)) + return false; + + QualType CharTy = PtrExpression->getType()->getPointeeType(); + for (int64_t I = 0; I < Size; ++I) { + APValue Char; + if (!handleLValueToRValueConversion(Info, PtrExpression, CharTy, String, + Char)) + return false; + + APSInt C = Char.getInt(); + Result.push_back(static_cast(C.getExtValue())); + if (!HandleLValueArrayAdjustment(Info, PtrExpression, String, CharTy, 1)) + return false; + } + if (!Scope.destroy()) + return false; + + if (!CheckMemoryLeaks(Info)) + return false; + + return true; +} + bool Expr::tryEvaluateStrLen(uint64_t &Result, ASTContext &Ctx) const { Expr::EvalStatus Status; EvalInfo Info(Ctx, Status, EvalInfo::EM_ConstantFold); diff --git a/clang/lib/AST/ODRDiagsEmitter.cpp b/clang/lib/AST/ODRDiagsEmitter.cpp index 1994f08..0189a5d 100644 --- a/clang/lib/AST/ODRDiagsEmitter.cpp +++ b/clang/lib/AST/ODRDiagsEmitter.cpp @@ -994,40 +994,43 @@ bool ODRDiagsEmitter::diagnoseMismatch( return true; } - const StringLiteral *FirstStr = FirstSA->getMessage(); - const StringLiteral *SecondStr = SecondSA->getMessage(); - assert((FirstStr || SecondStr) && "Both messages cannot be empty"); - if ((FirstStr && !SecondStr) || (!FirstStr && SecondStr)) { + const Expr *FirstMessage = FirstSA->getMessage(); + const Expr *SecondMessage = SecondSA->getMessage(); + assert((FirstMessage || SecondMessage) && "Both messages cannot be empty"); + if ((FirstMessage && !SecondMessage) || (!FirstMessage && SecondMessage)) { SourceLocation FirstLoc, SecondLoc; SourceRange FirstRange, SecondRange; - if (FirstStr) { - FirstLoc = FirstStr->getBeginLoc(); - FirstRange = FirstStr->getSourceRange(); + if (FirstMessage) { + FirstLoc = FirstMessage->getBeginLoc(); + FirstRange = FirstMessage->getSourceRange(); } else { FirstLoc = FirstSA->getBeginLoc(); FirstRange = FirstSA->getSourceRange(); } - if (SecondStr) { - SecondLoc = SecondStr->getBeginLoc(); - SecondRange = SecondStr->getSourceRange(); + if (SecondMessage) { + SecondLoc = SecondMessage->getBeginLoc(); + SecondRange = SecondMessage->getSourceRange(); } else { SecondLoc = SecondSA->getBeginLoc(); SecondRange = SecondSA->getSourceRange(); } DiagError(FirstLoc, FirstRange, StaticAssertOnlyMessage) - << (FirstStr == nullptr); + << (FirstMessage == nullptr); DiagNote(SecondLoc, SecondRange, StaticAssertOnlyMessage) - << (SecondStr == nullptr); + << (SecondMessage == nullptr); return true; } - if (FirstStr && SecondStr && - FirstStr->getString() != SecondStr->getString()) { - DiagError(FirstStr->getBeginLoc(), FirstStr->getSourceRange(), - StaticAssertMessage); - DiagNote(SecondStr->getBeginLoc(), SecondStr->getSourceRange(), - StaticAssertMessage); - return true; + if (FirstMessage && SecondMessage) { + unsigned FirstMessageODRHash = computeODRHash(FirstMessage); + unsigned SecondMessageODRHash = computeODRHash(SecondMessage); + if (FirstMessageODRHash != SecondMessageODRHash) { + DiagError(FirstMessage->getBeginLoc(), FirstMessage->getSourceRange(), + StaticAssertMessage); + DiagNote(SecondMessage->getBeginLoc(), SecondMessage->getSourceRange(), + StaticAssertMessage); + return true; + } } break; } diff --git a/clang/lib/Frontend/InitPreprocessor.cpp b/clang/lib/Frontend/InitPreprocessor.cpp index 2ab9e65..f8fae82 100644 --- a/clang/lib/Frontend/InitPreprocessor.cpp +++ b/clang/lib/Frontend/InitPreprocessor.cpp @@ -629,8 +629,10 @@ static void InitializeCPlusPlusFeatureTestMacros(const LangOptions &LangOpts, Builder.defineMacro("__cpp_constexpr_in_decltype", "201711L"); Builder.defineMacro("__cpp_range_based_for", LangOpts.CPlusPlus17 ? "201603L" : "200907"); - Builder.defineMacro("__cpp_static_assert", - LangOpts.CPlusPlus17 ? "201411L" : "200410"); + Builder.defineMacro("__cpp_static_assert", LangOpts.CPlusPlus26 ? "202306L" + : LangOpts.CPlusPlus17 + ? "201411L" + : "200410"); Builder.defineMacro("__cpp_decltype", "200707L"); Builder.defineMacro("__cpp_attributes", "200809L"); Builder.defineMacro("__cpp_rvalue_references", "200610L"); diff --git a/clang/lib/Parse/ParseDeclCXX.cpp b/clang/lib/Parse/ParseDeclCXX.cpp index 126843d..742f3ac 100644 --- a/clang/lib/Parse/ParseDeclCXX.cpp +++ b/clang/lib/Parse/ParseDeclCXX.cpp @@ -1016,14 +1016,17 @@ Decl *Parser::ParseStaticAssertDeclaration(SourceLocation &DeclEnd) { return nullptr; } - if (!isTokenStringLiteral()) { + if (isTokenStringLiteral()) + AssertMessage = ParseUnevaluatedStringLiteralExpression(); + else if (getLangOpts().CPlusPlus26) + AssertMessage = ParseConstantExpressionInExprEvalContext(); + else { Diag(Tok, diag::err_expected_string_literal) << /*Source='static_assert'*/ 1; SkipMalformedDecl(); return nullptr; } - AssertMessage = ParseUnevaluatedStringLiteralExpression(); if (AssertMessage.isInvalid()) { SkipMalformedDecl(); return nullptr; diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp index 4848af9..9e28453 100644 --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -16745,14 +16745,11 @@ Decl *Sema::ActOnStaticAssertDeclaration(SourceLocation StaticAssertLoc, Expr *AssertExpr, Expr *AssertMessageExpr, SourceLocation RParenLoc) { - StringLiteral *AssertMessage = - AssertMessageExpr ? cast(AssertMessageExpr) : nullptr; - if (DiagnoseUnexpandedParameterPack(AssertExpr, UPPC_StaticAssertExpression)) return nullptr; return BuildStaticAssertDeclaration(StaticAssertLoc, AssertExpr, - AssertMessage, RParenLoc, false); + AssertMessageExpr, RParenLoc, false); } /// Convert \V to a string we can present to the user in a diagnostic @@ -16887,13 +16884,147 @@ void Sema::DiagnoseStaticAssertDetails(const Expr *E) { } } +bool Sema::EvaluateStaticAssertMessageAsString(Expr *Message, + std::string &Result, + ASTContext &Ctx, + bool ErrorOnInvalidMessage) { + assert(Message); + assert(!Message->isTypeDependent() && !Message->isValueDependent() && + "can't evaluate a dependant static assert message"); + + if (const auto *SL = dyn_cast(Message)) { + assert(SL->isUnevaluated() && "expected an unevaluated string"); + Result.assign(SL->getString().begin(), SL->getString().end()); + return true; + } + + SourceLocation Loc = Message->getBeginLoc(); + QualType T = Message->getType().getNonReferenceType(); + auto *RD = T->getAsCXXRecordDecl(); + if (!RD) { + Diag(Loc, diag::err_static_assert_invalid_message); + return false; + } + + auto FindMember = [&](StringRef Member, bool &Empty, + bool Diag = false) -> std::optional { + QualType ObjectType = Message->getType(); + Expr::Classification ObjectClassification = + Message->Classify(getASTContext()); + + DeclarationName DN = PP.getIdentifierInfo(Member); + LookupResult MemberLookup(*this, DN, Loc, Sema::LookupMemberName); + LookupQualifiedName(MemberLookup, RD); + Empty = MemberLookup.empty(); + OverloadCandidateSet Candidates(MemberLookup.getNameLoc(), + OverloadCandidateSet::CSK_Normal); + for (NamedDecl *D : MemberLookup) { + AddMethodCandidate(DeclAccessPair::make(D, D->getAccess()), ObjectType, + ObjectClassification, /*Args=*/{}, Candidates); + } + OverloadCandidateSet::iterator Best; + switch (Candidates.BestViableFunction(*this, Loc, Best)) { + case OR_Success: + return MemberLookup; + default: + if (Diag) + Candidates.NoteCandidates( + PartialDiagnosticAt( + Loc, PDiag(diag::err_static_assert_invalid_mem_fn_ret_ty) + << (Member == "data")), + *this, OCD_AllCandidates, /*Args=*/{}); + } + return std::nullopt; + }; + + bool SizeNotFound, DataNotFound; + std::optional SizeMember = FindMember("size", SizeNotFound); + std::optional DataMember = FindMember("data", DataNotFound); + if (SizeNotFound || DataNotFound) { + Diag(Loc, diag::err_static_assert_missing_member_function) + << ((SizeNotFound && DataNotFound) ? 2 + : SizeNotFound ? 0 + : 1); + return false; + } + + if (!SizeMember || !DataMember) { + if (!SizeMember) + FindMember("size", SizeNotFound, /*Diag=*/true); + if (!DataMember) + FindMember("data", DataNotFound, /*Diag=*/true); + return false; + } + + auto BuildExpr = [&](LookupResult &LR) { + ExprResult Res = BuildMemberReferenceExpr( + Message, Message->getType(), Message->getBeginLoc(), false, + CXXScopeSpec(), SourceLocation(), nullptr, LR, nullptr, nullptr); + if (Res.isInvalid()) + return ExprError(); + Res = BuildCallExpr(nullptr, Res.get(), Loc, std::nullopt, Loc, nullptr, + false, true); + if (Res.isInvalid()) + return ExprError(); + if (Res.get()->isTypeDependent() || Res.get()->isValueDependent()) + return ExprError(); + return TemporaryMaterializationConversion(Res.get()); + }; + + ExprResult SizeE = BuildExpr(*SizeMember); + ExprResult DataE = BuildExpr(*DataMember); + + QualType SizeT = Context.getSizeType(); + QualType ConstCharPtr = + Context.getPointerType(Context.getConstType(Context.CharTy)); + + ExprResult EvaluatedSize = + SizeE.isInvalid() ? ExprError() + : BuildConvertedConstantExpression( + SizeE.get(), SizeT, CCEK_StaticAssertMessageSize); + if (EvaluatedSize.isInvalid()) { + Diag(Loc, diag::err_static_assert_invalid_mem_fn_ret_ty) << /*size*/ 0; + return false; + } + + ExprResult EvaluatedData = + DataE.isInvalid() + ? ExprError() + : BuildConvertedConstantExpression(DataE.get(), ConstCharPtr, + CCEK_StaticAssertMessageData); + if (EvaluatedData.isInvalid()) { + Diag(Loc, diag::err_static_assert_invalid_mem_fn_ret_ty) << /*data*/ 1; + return false; + } + + if (!ErrorOnInvalidMessage && + Diags.isIgnored(diag::warn_static_assert_message_constexpr, Loc)) + return true; + + Expr::EvalResult Status; + SmallVector Notes; + Status.Diag = &Notes; + if (!Message->EvaluateCharRangeAsString(Result, EvaluatedSize.get(), + EvaluatedData.get(), Ctx, Status) || + !Notes.empty()) { + Diag(Message->getBeginLoc(), + ErrorOnInvalidMessage ? diag::err_static_assert_message_constexpr + : diag::warn_static_assert_message_constexpr); + for (const auto &Note : Notes) + Diag(Note.first, Note.second); + return !ErrorOnInvalidMessage; + } + return true; +} + Decl *Sema::BuildStaticAssertDeclaration(SourceLocation StaticAssertLoc, - Expr *AssertExpr, - StringLiteral *AssertMessage, + Expr *AssertExpr, Expr *AssertMessage, SourceLocation RParenLoc, bool Failed) { assert(AssertExpr != nullptr && "Expected non-null condition"); if (!AssertExpr->isTypeDependent() && !AssertExpr->isValueDependent() && + (!AssertMessage || (!AssertMessage->isTypeDependent() && + !AssertMessage->isValueDependent())) && !Failed) { // In a static_assert-declaration, the constant-expression shall be a // constant expression that can be contextually converted to bool. @@ -16927,6 +17058,14 @@ Decl *Sema::BuildStaticAssertDeclaration(SourceLocation StaticAssertLoc, FoldKind).isInvalid()) Failed = true; + // If the static_assert passes, only verify that + // the message is grammatically valid without evaluating it. + if (!Failed && AssertMessage && Cond.getBoolValue()) { + std::string Str; + EvaluateStaticAssertMessageAsString(AssertMessage, Str, Context, + /*ErrorOnInvalidMessage=*/false); + } + // CWG2518 // [dcl.pre]/p10 If [...] the expression is evaluated in the context of a // template definition, the declaration has no effect. @@ -16934,14 +17073,17 @@ Decl *Sema::BuildStaticAssertDeclaration(SourceLocation StaticAssertLoc, getLangOpts().CPlusPlus && CurContext->isDependentContext(); if (!Failed && !Cond && !InTemplateDefinition) { - SmallString<256> MsgBuffer; llvm::raw_svector_ostream Msg(MsgBuffer); + bool HasMessage = AssertMessage; if (AssertMessage) { - const auto *MsgStr = cast(AssertMessage); - Msg << MsgStr->getString(); + std::string Str; + HasMessage = + EvaluateStaticAssertMessageAsString( + AssertMessage, Str, Context, /*ErrorOnInvalidMessage=*/true) || + !Str.empty(); + Msg << Str; } - Expr *InnerCond = nullptr; std::string InnerCondDescription; std::tie(InnerCond, InnerCondDescription) = @@ -16950,7 +17092,7 @@ Decl *Sema::BuildStaticAssertDeclaration(SourceLocation StaticAssertLoc, // Drill down into concept specialization expressions to see why they // weren't satisfied. Diag(AssertExpr->getBeginLoc(), diag::err_static_assert_failed) - << !AssertMessage << Msg.str() << AssertExpr->getSourceRange(); + << !HasMessage << Msg.str() << AssertExpr->getSourceRange(); ConstraintSatisfaction Satisfaction; if (!CheckConstraintSatisfaction(InnerCond, Satisfaction)) DiagnoseUnsatisfiedConstraint(Satisfaction); @@ -16958,12 +17100,12 @@ Decl *Sema::BuildStaticAssertDeclaration(SourceLocation StaticAssertLoc, && !isa(InnerCond)) { Diag(InnerCond->getBeginLoc(), diag::err_static_assert_requirement_failed) - << InnerCondDescription << !AssertMessage << Msg.str() + << InnerCondDescription << !HasMessage << Msg.str() << InnerCond->getSourceRange(); DiagnoseStaticAssertDetails(InnerCond); } else { Diag(AssertExpr->getBeginLoc(), diag::err_static_assert_failed) - << !AssertMessage << Msg.str() << AssertExpr->getSourceRange(); + << !HasMessage << Msg.str() << AssertExpr->getSourceRange(); PrintContextStack(); } Failed = true; diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp index 45a9e5d..c70d37d 100644 --- a/clang/lib/Sema/SemaOverload.cpp +++ b/clang/lib/Sema/SemaOverload.cpp @@ -5817,14 +5817,14 @@ static bool CheckConvertedConstantConversions(Sema &S, llvm_unreachable("unknown conversion kind"); } -/// CheckConvertedConstantExpression - Check that the expression From is a -/// converted constant expression of type T, perform the conversion and produce -/// the converted expression, per C++11 [expr.const]p3. -static ExprResult CheckConvertedConstantExpression(Sema &S, Expr *From, - QualType T, APValue &Value, +/// BuildConvertedConstantExpression - Check that the expression From is a +/// converted constant expression of type T, perform the conversion but +/// does not evaluate the expression +static ExprResult BuildConvertedConstantExpression(Sema &S, Expr *From, + QualType T, Sema::CCEKind CCE, - bool RequireInt, - NamedDecl *Dest) { + NamedDecl *Dest, + APValue &PreNarrowingValue) { assert(S.getLangOpts().CPlusPlus11 && "converted constant expression outside C++11"); @@ -5908,7 +5908,6 @@ static ExprResult CheckConvertedConstantExpression(Sema &S, Expr *From, // Check for a narrowing implicit conversion. bool ReturnPreNarrowingValue = false; - APValue PreNarrowingValue; QualType PreNarrowingType; switch (SCS->getNarrowingKind(S.Context, Result.get(), PreNarrowingValue, PreNarrowingType)) { @@ -5942,12 +5941,19 @@ static ExprResult CheckConvertedConstantExpression(Sema &S, Expr *From, << CCE << /*Constant*/ 0 << From->getType() << T; break; } + if (!ReturnPreNarrowingValue) + PreNarrowingValue = {}; - if (Result.get()->isValueDependent()) { - Value = APValue(); - return Result; - } + return Result; +} +/// EvaluateConvertedConstantExpression - Evaluate an Expression +/// That is a converted constant expression +/// (which was built with BuildConvertedConstantExpression) +static ExprResult EvaluateConvertedConstantExpression( + Sema &S, Expr *E, QualType T, APValue &Value, Sema::CCEKind CCE, + bool RequireInt, const APValue &PreNarrowingValue) { + ExprResult Result = E; // Check the expression is a constant expression. SmallVector Notes; Expr::EvalResult Eval; @@ -5961,7 +5967,7 @@ static ExprResult CheckConvertedConstantExpression(Sema &S, Expr *From, else Kind = ConstantExprKind::Normal; - if (!Result.get()->EvaluateAsConstantExpr(Eval, S.Context, Kind) || + if (!E->EvaluateAsConstantExpr(Eval, S.Context, Kind) || (RequireInt && !Eval.Val.isInt())) { // The expression can't be folded, so we can't keep it at this position in // the AST. @@ -5972,7 +5978,7 @@ static ExprResult CheckConvertedConstantExpression(Sema &S, Expr *From, if (Notes.empty()) { // It's a constant expression. Expr *E = ConstantExpr::Create(S.Context, Result.get(), Value); - if (ReturnPreNarrowingValue) + if (!PreNarrowingValue.isAbsent()) Value = std::move(PreNarrowingValue); return E; } @@ -5988,14 +5994,42 @@ static ExprResult CheckConvertedConstantExpression(Sema &S, Expr *From, for (unsigned I = 0; I < Notes.size(); ++I) S.Diag(Notes[I].first, Notes[I].second); } else { - S.Diag(From->getBeginLoc(), diag::err_expr_not_cce) - << CCE << From->getSourceRange(); + S.Diag(E->getBeginLoc(), diag::err_expr_not_cce) + << CCE << E->getSourceRange(); for (unsigned I = 0; I < Notes.size(); ++I) S.Diag(Notes[I].first, Notes[I].second); } return ExprError(); } +/// CheckConvertedConstantExpression - Check that the expression From is a +/// converted constant expression of type T, perform the conversion and produce +/// the converted expression, per C++11 [expr.const]p3. +static ExprResult CheckConvertedConstantExpression(Sema &S, Expr *From, + QualType T, APValue &Value, + Sema::CCEKind CCE, + bool RequireInt, + NamedDecl *Dest) { + + APValue PreNarrowingValue; + ExprResult Result = BuildConvertedConstantExpression(S, From, T, CCE, Dest, + PreNarrowingValue); + if (Result.isInvalid() || Result.get()->isValueDependent()) { + Value = APValue(); + return Result; + } + return EvaluateConvertedConstantExpression(S, Result.get(), T, Value, CCE, + RequireInt, PreNarrowingValue); +} + +ExprResult Sema::BuildConvertedConstantExpression(Expr *From, QualType T, + CCEKind CCE, + NamedDecl *Dest) { + APValue PreNarrowingValue; + return ::BuildConvertedConstantExpression(*this, From, T, CCE, Dest, + PreNarrowingValue); +} + ExprResult Sema::CheckConvertedConstantExpression(Expr *From, QualType T, APValue &Value, CCEKind CCE, NamedDecl *Dest) { diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp index 9e5f85b..f78d46f 100644 --- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp +++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp @@ -1429,11 +1429,14 @@ Decl *TemplateDeclInstantiator::VisitStaticAssertDecl(StaticAssertDecl *D) { if (InstantiatedAssertExpr.isInvalid()) return nullptr; - return SemaRef.BuildStaticAssertDeclaration(D->getLocation(), - InstantiatedAssertExpr.get(), - D->getMessage(), - D->getRParenLoc(), - D->isFailed()); + ExprResult InstantiatedMessageExpr = + SemaRef.SubstExpr(D->getMessage(), TemplateArgs); + if (InstantiatedMessageExpr.isInvalid()) + return nullptr; + + return SemaRef.BuildStaticAssertDeclaration( + D->getLocation(), InstantiatedAssertExpr.get(), + InstantiatedMessageExpr.get(), D->getRParenLoc(), D->isFailed()); } Decl *TemplateDeclInstantiator::VisitEnumDecl(EnumDecl *D) { diff --git a/clang/test/Lexer/cxx-features.cpp b/clang/test/Lexer/cxx-features.cpp index 5c5408c2..94b2677 100644 --- a/clang/test/Lexer/cxx-features.cpp +++ b/clang/test/Lexer/cxx-features.cpp @@ -169,10 +169,6 @@ #error "wrong value for __cpp_if_constexpr" #endif -// range_based_for checked below - -// static_assert checked below - #if check(deduction_guides, 0, 0, 0, 201703, 201703, 201703, 201703) // FIXME: 201907 in C++20 #error "wrong value for __cpp_deduction_guides" @@ -308,7 +304,7 @@ #error "wrong value for __cpp_range_based_for" #endif -#if check(static_assert, 0, 200410, 200410, 201411, 201411, 201411, 201411) +#if check(static_assert, 0, 200410, 200410, 201411, 201411, 201411, 202306) #error "wrong value for __cpp_static_assert" #endif diff --git a/clang/test/SemaCXX/static-assert-cxx26.cpp b/clang/test/SemaCXX/static-assert-cxx26.cpp new file mode 100644 index 0000000..9eddc48 --- /dev/null +++ b/clang/test/SemaCXX/static-assert-cxx26.cpp @@ -0,0 +1,291 @@ +// RUN: %clang_cc1 -std=c++2c -fsyntax-only %s -verify + +static_assert(true, ""); +static_assert(true, 0); // expected-error {{the message in a static assertion must be a string literal or an object with 'data()' and 'size()' member functions}} +struct Empty{}; +static_assert(true, Empty{}); // expected-error {{the message object in this static assertion is missing 'data()' and 'size()' member functions}} +struct NoData { + unsigned long size() const; +}; +struct NoSize { + const char* data() const; +}; +static_assert(true, NoData{}); // expected-error {{the message object in this static assertion is missing a 'data()' member function}} +static_assert(true, NoSize{}); // expected-error {{the message object in this static assertion is missing a 'size()' member function}} + +struct InvalidSize { + const char* size() const; + const char* data() const; +}; +static_assert(true, InvalidSize{}); // expected-error {{the message in a static assertion must have a 'size()' member function returning an object convertible to 'std::size_t'}} \ + // expected-error {{value of type 'const char *' is not implicitly convertible to 'unsigned long'}} +struct InvalidData { + unsigned long size() const; + unsigned long data() const; +}; +static_assert(true, InvalidData{}); // expected-error {{the message in a static assertion must have a 'data()' member function returning an object convertible to 'const char *'}} \ + // expected-error {{value of type 'unsigned long' is not implicitly convertible to 'const char *'}} + +struct NonConstexprSize { + unsigned long size() const; // expected-note 2{{declared here}} + constexpr const char* data() const; +}; + +static_assert(true, NonConstexprSize{}); // expected-error {{the message in this static assertion is not a constant expression}} \ + // expected-note {{non-constexpr function 'size' cannot be used in a constant expression}} + +static_assert(false, NonConstexprSize{}); // expected-error {{the message in a static assertion must be produced by a constant expression}} \ + // expected-error {{static assertion failed}} \ + // expected-note {{non-constexpr function 'size' cannot be used in a constant expression}} + +struct NonConstexprData { + constexpr unsigned long size() const { + return 32; + } + const char* data() const; // expected-note 2{{declared here}} +}; + +static_assert(true, NonConstexprData{}); // expected-error {{the message in this static assertion is not a constant expression}} \ + // expected-note {{non-constexpr function 'data' cannot be used in a constant expression}} + +static_assert(false, NonConstexprData{}); // expected-error {{the message in a static assertion must be produced by a constant expression}} \ + // expected-error {{static assertion failed}} \ + // expected-note {{non-constexpr function 'data' cannot be used in a constant expression}} + +struct string_view { + int S; + const char* D; + constexpr string_view(const char* Str) : S(__builtin_strlen(Str)), D(Str) {} + constexpr string_view(int Size, const char* Str) : S(Size), D(Str) {} + constexpr int size() const { + return S; + } + constexpr const char* data() const { + return D; + } +}; + +constexpr const char g_[] = "long string"; + +template +struct array { + constexpr unsigned long size() const { + return S; + } + constexpr const char* data() const { + return d_; + } + const char d_[S]; +}; + +static_assert(false, string_view("test")); // expected-error {{static assertion failed: test}} +static_assert(false, string_view("😀")); // expected-error {{static assertion failed: 😀}} +static_assert(false, string_view(0, nullptr)); // expected-error {{static assertion failed:}} +static_assert(false, string_view(1, "ABC")); // expected-error {{static assertion failed: A}} +static_assert(false, string_view(42, "ABC")); // expected-error {{static assertion failed: ABC}} \ + // expected-error {{the message in a static assertion must be produced by a constant expression}} \ + // expected-note {{read of dereferenced one-past-the-end pointer is not allowed in a constant expression}} +static_assert(false, array{'a', 'b'}); // expected-error {{static assertion failed: ab}} + + + +struct ConvertibleToInt { + constexpr operator int() { + return 4; + } +}; +struct ConvertibleToCharPtr { + constexpr operator const char*() { + return "test"; + } +}; +struct MessageFromConvertible { + constexpr ConvertibleToInt size() const { + return {}; + } + constexpr ConvertibleToCharPtr data() const { + return {}; + } +}; + +static_assert(true, MessageFromConvertible{}); +static_assert(false, MessageFromConvertible{}); // expected-error{{static assertion failed: test}} + + + +struct Leaks { + constexpr unsigned long size() const { + return 2; + } + constexpr const char* data() const { + return new char[2]{'u', 'b'}; // expected-note {{allocation performed here was not deallocated}} + } +}; + +static_assert(false, Leaks{}); //expected-error {{the message in a static assertion must be produced by a constant expression}} \ + // expected-error {{static assertion failed: ub}} + +struct RAII { + const char* d = new char[2]{'o', 'k'}; + constexpr unsigned long size() const { + return 2; + } + + constexpr const char* data() const { + return d; + } + + constexpr ~RAII() { + delete[] d; + } +}; +static_assert(false, RAII{}); // expected-error {{static assertion failed: ok}} + +namespace MoreTemporary { + +struct Data{ +constexpr operator const char*() const { + return d; +} +char d[6] = { "Hello" }; +}; + +struct Size { + constexpr operator int() const { + return 5; + } +}; + +struct Message { + constexpr auto size() const { + return Size{}; + } + constexpr auto data() const { + return Data{}; + } +}; + +static_assert(false, Message{}); // expected-error {{static assertion failed: Hello}} + +} + +struct MessageInvalidSize { + constexpr auto size(int) const; // expected-note {{candidate function not viable: requires 1 argument, but 0 were provided}} + constexpr auto data() const; +}; +struct MessageInvalidData { + constexpr auto size() const; + constexpr auto data(int) const; // expected-note {{candidate function not viable: requires 1 argument, but 0 were provided}} +}; + +static_assert(false, MessageInvalidSize{}); // expected-error {{static assertion failed}} \ + // expected-error {{the message in a static assertion must have a 'size()' member function returning an object convertible to 'std::size_t'}} +static_assert(false, MessageInvalidData{}); // expected-error {{static assertion failed}} \ + // expected-error {{the message in a static assertion must have a 'data()' member function returning an object convertible to 'const char *'}} + +struct NonConstMembers { + constexpr int size() { + return 1; + } + constexpr const char* data() { + return "A"; + } +}; + +static_assert(false, NonConstMembers{}); // expected-error {{static assertion failed: A}} + +struct DefaultArgs { + constexpr int size(int i = 0) { + return 2; + } + constexpr const char* data(int i =0, int j = 42) { + return "OK"; + } +}; + +static_assert(false, DefaultArgs{}); // expected-error {{static assertion failed: OK}} + +struct Variadic { + constexpr int size(auto...) { + return 2; + } + constexpr const char* data(auto...) { + return "OK"; + } +}; + +static_assert(false, Variadic{}); // expected-error {{static assertion failed: OK}} + +template +struct DeleteAndRequires { + constexpr int size() = delete; // expected-note {{candidate function has been explicitly deleted}} + constexpr const char* data() requires false; // expected-note {{candidate function not viable: constraints not satisfied}} \ + // expected-note {{because 'false' evaluated to false}} +}; +static_assert(false, DeleteAndRequires{}); +// expected-error@-1 {{static assertion failed}} \ +// expected-error@-1 {{the message in a static assertion must have a 'size()' member function returning an object convertible to 'std::size_t'}}\ +// expected-error@-1 {{the message in a static assertion must have a 'data()' member function returning an object convertible to 'const char *'}} + +class Private { + constexpr int size(int i = 0) { // expected-note {{implicitly declared private here}} + return 2; + } + constexpr const char* data(int i =0, int j = 42) { // expected-note {{implicitly declared private here}} + return "OK"; + } +}; + +static_assert(false, Private{}); // expected-error {{'data' is a private member of 'Private'}}\ + // expected-error {{'size' is a private member of 'Private'}}\ + // expected-error {{static assertion failed: OK}} + +struct MessageOverload { + constexpr int size() { + return 1; + } + constexpr int size() const; + + constexpr const char* data() { + return "A"; + } + constexpr const char* data() const; +}; + +static_assert(false, MessageOverload{}); // expected-error {{static assertion failed: A}} + +struct InvalidPtr { + consteval auto size() { + return 42; + } + consteval const char *data() { + const char *ptr; // Garbage + return ptr; // expected-note {{read of uninitialized object is not allowed in a constant expression}} + } +}; + +static_assert(false, InvalidPtr{}); // expected-error{{the message in a static assertion must be produced by a constant expression}} \ + //expected-error {{static assertion failed}} \ + // expected-note {{in call to 'InvalidPtr{}.data()'}} + +namespace DependentMessage { +template +struct Good { + static_assert(false, Ty{}); // expected-error {{static assertion failed: hello}} +}; + +template +struct Bad { + static_assert(false, Ty{}); // expected-error {{the message in a static assertion must be a string literal or an object with 'data()' and 'size()' member functions}} \ + // expected-error {{static assertion failed}} +}; + +struct Frobble { + constexpr int size() const { return 5; } + constexpr const char *data() const { return "hello"; } +}; + +Good a; // expected-note {{in instantiation}} +Bad b; // expected-note {{in instantiation}} + +} diff --git a/clang/tools/libclang/CIndex.cpp b/clang/tools/libclang/CIndex.cpp index ec309fa..0c629fe 100644 --- a/clang/tools/libclang/CIndex.cpp +++ b/clang/tools/libclang/CIndex.cpp @@ -1294,7 +1294,7 @@ bool CursorVisitor::VisitUnresolvedUsingTypenameDecl( bool CursorVisitor::VisitStaticAssertDecl(StaticAssertDecl *D) { if (Visit(MakeCXCursor(D->getAssertExpr(), StmtParent, TU, RegionOfInterest))) return true; - if (StringLiteral *Message = D->getMessage()) + if (auto *Message = dyn_cast(D->getMessage())) if (Visit(MakeCXCursor(Message, StmtParent, TU, RegionOfInterest))) return true; return false; diff --git a/clang/www/cxx_status.html b/clang/www/cxx_status.html index e27c0c7..23dc53b 100755 --- a/clang/www/cxx_status.html +++ b/clang/www/cxx_status.html @@ -145,7 +145,7 @@ C++23, informally referred to as C++26.

User-generated static_assert messages P2741R3 - No + Clang 17 Placeholder variables with no name -- 2.7.4