diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -137,6 +137,7 @@ - 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 --- a/clang/include/clang/AST/DeclCXX.h +++ b/clang/include/clang/AST/DeclCXX.h @@ -4010,12 +4010,12 @@ /// 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 @@ 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 --- a/clang/include/clang/AST/Expr.h +++ b/clang/include/clang/AST/Expr.h @@ -762,6 +762,11 @@ /// 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 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -83,7 +83,7 @@ "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 " + "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|" @@ -1542,6 +1542,23 @@ "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_size : Error< + "the message in a static assertion must have a 'size()' member " + "function returning an object convertible to 'std::size_t'">; +def err_static_assert_invalid_data : Error< + "the message in a static assertion must have a 'data()' member " + "function returning an object convertible to 'const char*'">; +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 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -3877,8 +3877,17 @@ 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, @@ -7785,15 +7794,15 @@ void UnmarkAsLateParsedTemplate(FunctionDecl *FD); bool IsInsideALocalClassWithinATemplateFunction(); + bool EvaluateStaticAssertMessageAsString(Expr *Message, std::string &Result, + ASTContext &Ctx, bool CheckOnly); 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 --- a/clang/lib/AST/DeclCXX.cpp +++ b/clang/lib/AST/DeclCXX.cpp @@ -3237,8 +3237,7 @@ 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 --- a/clang/lib/AST/DeclPrinter.cpp +++ b/clang/lib/AST/DeclPrinter.cpp @@ -949,7 +949,8 @@ Out << "static_assert("; D->getAssertExpr()->printPretty(Out, nullptr, Policy, Indentation, "\n", &Context); - if (StringLiteral *SL = D->getMessage()) { + if (StringLiteral *SL = + llvm::dyn_cast_if_present(D->getMessage())) { Out << ", "; SL->printPretty(Out, nullptr, Policy, Indentation, "\n", &Context); } diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp --- 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 @@ // ... 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 @@ 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,46 @@ } } +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) || + !Char.isInt()) + 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 --- a/clang/lib/AST/ODRDiagsEmitter.cpp +++ b/clang/lib/AST/ODRDiagsEmitter.cpp @@ -994,40 +994,43 @@ 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 --- a/clang/lib/Frontend/InitPreprocessor.cpp +++ b/clang/lib/Frontend/InitPreprocessor.cpp @@ -620,8 +620,10 @@ 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 --- a/clang/lib/Parse/ParseDeclCXX.cpp +++ b/clang/lib/Parse/ParseDeclCXX.cpp @@ -1016,14 +1016,17 @@ 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 --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -16742,14 +16742,11 @@ 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 @@ -16884,13 +16881,146 @@ } } +bool Sema::EvaluateStaticAssertMessageAsString(Expr *Message, + std::string &Result, + ASTContext &Ctx, + bool CheckOnly) { + SourceLocation Loc = Message->getBeginLoc(); + 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; + } + + QualType T = Message->getType().getNonReferenceType(); + auto *RD = T->getAsCXXRecordDecl(); + if (!RD) { + Diag(Message->getBeginLoc(), 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(Member == "size" ? diag::err_static_assert_invalid_size + : diag::err_static_assert_invalid_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(Message->getBeginLoc(), + 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(Message->getBeginLoc(), diag::err_static_assert_invalid_size); + return false; + } + + ExprResult EvaluatedData = + DataE.isInvalid() + ? ExprError() + : BuildConvertedConstantExpression(DataE.get(), ConstCharPtr, + CCEK_StaticAssertMessageData); + if (EvaluatedData.isInvalid()) { + Diag(Message->getBeginLoc(), diag::err_static_assert_invalid_data); + return false; + } + + if (CheckOnly) + 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(), diag::err_static_assert_message_constexpr); + for (unsigned I = 0; I < Notes.size(); ++I) + Diag(Notes[I].first, Notes[I].second); + return false; + } + 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. @@ -16924,6 +17054,14 @@ FoldKind).isInvalid()) Failed = true; + // If the static_assert passes, only verify that + // the message is grammatically valid without evaluating it. + if (!Failed && AssertMessage && !!Cond) { + std::string Str; + EvaluateStaticAssertMessageAsString(AssertMessage, Str, Context, + /*CheckOnly=*/true); + } + // CWG2518 // [dcl.pre]/p10 If [...] the expression is evaluated in the context of a // template definition, the declaration has no effect. @@ -16931,14 +17069,16 @@ 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, /*CheckOnly=*/false) || + !Str.empty(); + Msg << Str; } - Expr *InnerCond = nullptr; std::string InnerCondDescription; std::tie(InnerCond, InnerCondDescription) = @@ -16947,7 +17087,7 @@ // 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); @@ -16955,12 +17095,12 @@ && !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 --- a/clang/lib/Sema/SemaOverload.cpp +++ b/clang/lib/Sema/SemaOverload.cpp @@ -5817,14 +5817,14 @@ 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 @@ // 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 @@ << 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 @@ 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 @@ 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 @@ 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 --- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp +++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp @@ -1429,11 +1429,14 @@ 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 --- 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 --- /dev/null +++ b/clang/test/SemaCXX/static-assert-cxx26.cpp @@ -0,0 +1,240 @@ +// 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 {{declared here}} + constexpr const char* data() const; +}; + +static_assert(true, NonConstexprSize{}); +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 {{declared here}} +}; + +static_assert(true, NonConstexprData{}); +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 { + operator int(); +}; +struct ConvertibleToCharPtr { + operator const char*(); +}; +struct MessageFromConvertible { + ConvertibleToInt size() const; + ConvertibleToCharPtr data() const; +}; +static_assert(true, MessageFromConvertible{}); + + +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}} diff --git a/clang/tools/libclang/CIndex.cpp b/clang/tools/libclang/CIndex.cpp --- a/clang/tools/libclang/CIndex.cpp +++ b/clang/tools/libclang/CIndex.cpp @@ -1294,7 +1294,7 @@ 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 --- a/clang/www/cxx_status.html +++ b/clang/www/cxx_status.html @@ -145,7 +145,7 @@ User-generated static_assert messages P2741R3 - No + Clang 17 Placeholder variables with no name