Skip to content

Commit

Permalink
[Clang] Implement P2741R3 - user-generated static_assert messages
Browse files Browse the repository at this point in the history
Reviewed By: #clang-language-wg, aaron.ballman

Differential Revision: https://reviews.llvm.org/D154290
  • Loading branch information
cor3ntin committed Jul 20, 2023
1 parent 1c154bd commit 47ccfd7
Show file tree
Hide file tree
Showing 18 changed files with 631 additions and 82 deletions.
1 change: 1 addition & 0 deletions clang/docs/ReleaseNotes.rst
Expand Up @@ -137,6 +137,7 @@ C++2c Feature Support
- Implemented `P2738R1: constexpr cast from void* <https://wg21.link/P2738R1>`_.
- Partially implemented `P2361R6: Unevaluated strings <https://wg21.link/P2361R6>`_.
The changes to attributes declarations are not part of this release.
- Implemented `P2741R3: user-generated static_assert messages <https://wg21.link/P2741R3>`_.

Resolutions to C++ Defect Reports
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Expand Down
12 changes: 6 additions & 6 deletions clang/include/clang/AST/DeclCXX.h
Expand Up @@ -4010,12 +4010,12 @@ class UnresolvedUsingIfExistsDecl final : public NamedDecl {
/// Represents a C++11 static_assert declaration.
class StaticAssertDecl : public Decl {
llvm::PointerIntPair<Expr *, 1, bool> 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) {}
Expand All @@ -4027,15 +4027,15 @@ class StaticAssertDecl : public Decl {

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(); }

Expand Down
5 changes: 5 additions & 0 deletions clang/include/clang/AST/Expr.h
Expand Up @@ -762,6 +762,11 @@ class Expr : public ValueStmt {
/// 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 {
Expand Down
22 changes: 20 additions & 2 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Expand Up @@ -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 "
Expand Down Expand Up @@ -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<DiagGroup<"invalid-static-assert-message">>;
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">,
Expand Down
20 changes: 15 additions & 5 deletions clang/include/clang/Sema/Sema.h
Expand Up @@ -3887,8 +3887,17 @@ class Sema final {
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,
Expand Down Expand Up @@ -7795,15 +7804,16 @@ class Sema final {
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,
Expand Down
3 changes: 1 addition & 2 deletions clang/lib/AST/DeclCXX.cpp
Expand Up @@ -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,
Expand Down
4 changes: 2 additions & 2 deletions clang/lib/AST/DeclPrinter.cpp
Expand Up @@ -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 << ")";
}
Expand Down
44 changes: 43 additions & 1 deletion clang/lib/AST/ExprConstant.cpp
Expand Up @@ -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"
Expand Down Expand Up @@ -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<const ValueDecl*>()) {
// ... the address of an object with static storage duration,
Expand Down Expand Up @@ -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.
}

Expand Down Expand Up @@ -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<char>(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);
Expand Down
41 changes: 22 additions & 19 deletions clang/lib/AST/ODRDiagsEmitter.cpp
Expand Up @@ -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;
}
Expand Down
6 changes: 4 additions & 2 deletions clang/lib/Frontend/InitPreprocessor.cpp
Expand Up @@ -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");
Expand Down
7 changes: 5 additions & 2 deletions clang/lib/Parse/ParseDeclCXX.cpp
Expand Up @@ -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;
Expand Down

0 comments on commit 47ccfd7

Please sign in to comment.