1 //===--- StaticAssertCheck.cpp - clang-tidy -------------------------------===//
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7 //===----------------------------------------------------------------------===//
9 #include "StaticAssertCheck.h"
10 #include "clang/AST/ASTContext.h"
11 #include "clang/AST/Expr.h"
12 #include "clang/ASTMatchers/ASTMatchFinder.h"
13 #include "clang/Frontend/CompilerInstance.h"
14 #include "clang/Lex/Lexer.h"
15 #include "llvm/ADT/SmallVector.h"
16 #include "llvm/ADT/StringRef.h"
17 #include "llvm/Support/Casting.h"
21 using namespace clang::ast_matchers
;
23 namespace clang::tidy::misc
{
25 StaticAssertCheck::StaticAssertCheck(StringRef Name
, ClangTidyContext
*Context
)
26 : ClangTidyCheck(Name
, Context
) {}
28 void StaticAssertCheck::registerMatchers(MatchFinder
*Finder
) {
29 auto NegatedString
= unaryOperator(
30 hasOperatorName("!"), hasUnaryOperand(ignoringImpCasts(stringLiteral())));
32 expr(anyOf(cxxBoolLiteral(equals(false)), integerLiteral(equals(0)),
33 cxxNullPtrLiteralExpr(), gnuNullExpr(), NegatedString
))
34 .bind("isAlwaysFalse");
35 auto IsAlwaysFalseWithCast
= ignoringParenImpCasts(anyOf(
36 IsAlwaysFalse
, cStyleCastExpr(has(ignoringParenImpCasts(IsAlwaysFalse
)))
38 auto AssertExprRoot
= anyOf(
40 hasAnyOperatorName("&&", "=="),
41 hasEitherOperand(ignoringImpCasts(stringLiteral().bind("assertMSG"))),
42 anyOf(binaryOperator(hasEitherOperand(IsAlwaysFalseWithCast
)),
44 .bind("assertExprRoot"),
46 auto NonConstexprFunctionCall
=
47 callExpr(hasDeclaration(functionDecl(unless(isConstexpr()))));
48 auto AssertCondition
=
50 anyOf(expr(ignoringParenCasts(anyOf(
51 AssertExprRoot
, unaryOperator(hasUnaryOperand(
52 ignoringParenCasts(AssertExprRoot
)))))),
54 unless(findAll(NonConstexprFunctionCall
)))
57 anyOf(ignoringParenImpCasts(callExpr(
58 hasDeclaration(functionDecl(hasName("__builtin_expect"))),
59 hasArgument(0, AssertCondition
))),
62 Finder
->addMatcher(conditionalOperator(hasCondition(Condition
),
63 unless(isInTemplateInstantiation()))
68 ifStmt(hasCondition(Condition
), unless(isInTemplateInstantiation()))
73 void StaticAssertCheck::check(const MatchFinder::MatchResult
&Result
) {
74 const ASTContext
*ASTCtx
= Result
.Context
;
75 const LangOptions
&Opts
= ASTCtx
->getLangOpts();
76 const SourceManager
&SM
= ASTCtx
->getSourceManager();
77 const auto *CondStmt
= Result
.Nodes
.getNodeAs
<Stmt
>("condStmt");
78 const auto *Condition
= Result
.Nodes
.getNodeAs
<Expr
>("condition");
79 const auto *IsAlwaysFalse
= Result
.Nodes
.getNodeAs
<Expr
>("isAlwaysFalse");
80 const auto *AssertMSG
= Result
.Nodes
.getNodeAs
<StringLiteral
>("assertMSG");
81 const auto *AssertExprRoot
=
82 Result
.Nodes
.getNodeAs
<BinaryOperator
>("assertExprRoot");
83 const auto *CastExpr
= Result
.Nodes
.getNodeAs
<CStyleCastExpr
>("castExpr");
84 SourceLocation AssertExpansionLoc
= CondStmt
->getBeginLoc();
86 if (!AssertExpansionLoc
.isValid() || !AssertExpansionLoc
.isMacroID())
90 Lexer::getImmediateMacroName(AssertExpansionLoc
, SM
, Opts
);
92 if (MacroName
!= "assert" || Condition
->isValueDependent() ||
93 Condition
->isTypeDependent() || Condition
->isInstantiationDependent() ||
94 !Condition
->isEvaluatable(*ASTCtx
))
97 // False literal is not the result of macro expansion.
98 if (IsAlwaysFalse
&& (!CastExpr
|| CastExpr
->getType()->isPointerType())) {
99 SourceLocation FalseLiteralLoc
=
100 SM
.getImmediateSpellingLoc(IsAlwaysFalse
->getExprLoc());
101 if (!FalseLiteralLoc
.isMacroID())
104 StringRef FalseMacroName
=
105 Lexer::getImmediateMacroName(FalseLiteralLoc
, SM
, Opts
);
106 if (FalseMacroName
.compare_insensitive("false") == 0 ||
107 FalseMacroName
.compare_insensitive("null") == 0)
111 SourceLocation AssertLoc
= SM
.getImmediateMacroCallerLoc(AssertExpansionLoc
);
113 SmallVector
<FixItHint
, 4> FixItHints
;
114 SourceLocation LastParenLoc
;
115 if (AssertLoc
.isValid() && !AssertLoc
.isMacroID() &&
116 (LastParenLoc
= getLastParenLoc(ASTCtx
, AssertLoc
)).isValid()) {
117 FixItHints
.push_back(
118 FixItHint::CreateReplacement(SourceRange(AssertLoc
), "static_assert"));
120 if (AssertExprRoot
) {
121 FixItHints
.push_back(FixItHint::CreateRemoval(
122 SourceRange(AssertExprRoot
->getOperatorLoc())));
123 FixItHints
.push_back(FixItHint::CreateRemoval(
124 SourceRange(AssertMSG
->getBeginLoc(), AssertMSG
->getEndLoc())));
125 FixItHints
.push_back(FixItHint::CreateInsertion(
126 LastParenLoc
, (Twine(", \"") + AssertMSG
->getString() + "\"").str()));
127 } else if (!Opts
.CPlusPlus17
) {
128 FixItHints
.push_back(FixItHint::CreateInsertion(LastParenLoc
, ", \"\""));
132 diag(AssertLoc
, "found assert() that could be replaced by static_assert()")
136 SourceLocation
StaticAssertCheck::getLastParenLoc(const ASTContext
*ASTCtx
,
137 SourceLocation AssertLoc
) {
138 const LangOptions
&Opts
= ASTCtx
->getLangOpts();
139 const SourceManager
&SM
= ASTCtx
->getSourceManager();
141 std::optional
<llvm::MemoryBufferRef
> Buffer
=
142 SM
.getBufferOrNone(SM
.getFileID(AssertLoc
));
144 return SourceLocation();
146 const char *BufferPos
= SM
.getCharacterData(AssertLoc
);
149 Lexer
Lexer(SM
.getLocForStartOfFile(SM
.getFileID(AssertLoc
)), Opts
,
150 Buffer
->getBufferStart(), BufferPos
, Buffer
->getBufferEnd());
152 // assert first left parenthesis
153 if (Lexer
.LexFromRawLexer(Token
) || Lexer
.LexFromRawLexer(Token
) ||
154 !Token
.is(tok::l_paren
))
155 return SourceLocation();
157 unsigned int ParenCount
= 1;
158 while (ParenCount
&& !Lexer
.LexFromRawLexer(Token
)) {
159 if (Token
.is(tok::l_paren
))
161 else if (Token
.is(tok::r_paren
))
165 return Token
.getLocation();
168 } // namespace clang::tidy::misc