1 //===--- StaticAssertCheck.cpp - clang-tidy -------------------------------===//
2 //
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
6 //
7 //===----------------------------------------------------------------------===//
8
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"
18 #include <string>
19
20 using namespace clang::ast_matchers;
21
22 namespace clang {
23 namespace tidy {
24 namespace misc {
25
StaticAssertCheck(StringRef Name,ClangTidyContext * Context)26 StaticAssertCheck::StaticAssertCheck(StringRef Name, ClangTidyContext *Context)
27 : ClangTidyCheck(Name, Context) {}
28
registerMatchers(MatchFinder * Finder)29 void StaticAssertCheck::registerMatchers(MatchFinder *Finder) {
30 auto NegatedString = unaryOperator(
31 hasOperatorName("!"), hasUnaryOperand(ignoringImpCasts(stringLiteral())));
32 auto IsAlwaysFalse =
33 expr(anyOf(cxxBoolLiteral(equals(false)), integerLiteral(equals(0)),
34 cxxNullPtrLiteralExpr(), gnuNullExpr(), NegatedString))
35 .bind("isAlwaysFalse");
36 auto IsAlwaysFalseWithCast = ignoringParenImpCasts(anyOf(
37 IsAlwaysFalse, cStyleCastExpr(has(ignoringParenImpCasts(IsAlwaysFalse)))
38 .bind("castExpr")));
39 auto AssertExprRoot = anyOf(
40 binaryOperator(
41 hasAnyOperatorName("&&", "=="),
42 hasEitherOperand(ignoringImpCasts(stringLiteral().bind("assertMSG"))),
43 anyOf(binaryOperator(hasEitherOperand(IsAlwaysFalseWithCast)),
44 anything()))
45 .bind("assertExprRoot"),
46 IsAlwaysFalse);
47 auto NonConstexprFunctionCall =
48 callExpr(hasDeclaration(functionDecl(unless(isConstexpr()))));
49 auto AssertCondition =
50 expr(
51 anyOf(expr(ignoringParenCasts(anyOf(
52 AssertExprRoot, unaryOperator(hasUnaryOperand(
53 ignoringParenCasts(AssertExprRoot)))))),
54 anything()),
55 unless(findAll(NonConstexprFunctionCall)))
56 .bind("condition");
57 auto Condition =
58 anyOf(ignoringParenImpCasts(callExpr(
59 hasDeclaration(functionDecl(hasName("__builtin_expect"))),
60 hasArgument(0, AssertCondition))),
61 AssertCondition);
62
63 Finder->addMatcher(conditionalOperator(hasCondition(Condition),
64 unless(isInTemplateInstantiation()))
65 .bind("condStmt"),
66 this);
67
68 Finder->addMatcher(
69 ifStmt(hasCondition(Condition), unless(isInTemplateInstantiation()))
70 .bind("condStmt"),
71 this);
72 }
73
check(const MatchFinder::MatchResult & Result)74 void StaticAssertCheck::check(const MatchFinder::MatchResult &Result) {
75 const ASTContext *ASTCtx = Result.Context;
76 const LangOptions &Opts = ASTCtx->getLangOpts();
77 const SourceManager &SM = ASTCtx->getSourceManager();
78 const auto *CondStmt = Result.Nodes.getNodeAs<Stmt>("condStmt");
79 const auto *Condition = Result.Nodes.getNodeAs<Expr>("condition");
80 const auto *IsAlwaysFalse = Result.Nodes.getNodeAs<Expr>("isAlwaysFalse");
81 const auto *AssertMSG = Result.Nodes.getNodeAs<StringLiteral>("assertMSG");
82 const auto *AssertExprRoot =
83 Result.Nodes.getNodeAs<BinaryOperator>("assertExprRoot");
84 const auto *CastExpr = Result.Nodes.getNodeAs<CStyleCastExpr>("castExpr");
85 SourceLocation AssertExpansionLoc = CondStmt->getBeginLoc();
86
87 if (!AssertExpansionLoc.isValid() || !AssertExpansionLoc.isMacroID())
88 return;
89
90 StringRef MacroName =
91 Lexer::getImmediateMacroName(AssertExpansionLoc, SM, Opts);
92
93 if (MacroName != "assert" || Condition->isValueDependent() ||
94 Condition->isTypeDependent() || Condition->isInstantiationDependent() ||
95 !Condition->isEvaluatable(*ASTCtx))
96 return;
97
98 // False literal is not the result of macro expansion.
99 if (IsAlwaysFalse && (!CastExpr || CastExpr->getType()->isPointerType())) {
100 SourceLocation FalseLiteralLoc =
101 SM.getImmediateSpellingLoc(IsAlwaysFalse->getExprLoc());
102 if (!FalseLiteralLoc.isMacroID())
103 return;
104
105 StringRef FalseMacroName =
106 Lexer::getImmediateMacroName(FalseLiteralLoc, SM, Opts);
107 if (FalseMacroName.compare_lower("false") == 0 ||
108 FalseMacroName.compare_lower("null") == 0)
109 return;
110 }
111
112 SourceLocation AssertLoc = SM.getImmediateMacroCallerLoc(AssertExpansionLoc);
113
114 SmallVector<FixItHint, 4> FixItHints;
115 SourceLocation LastParenLoc;
116 if (AssertLoc.isValid() && !AssertLoc.isMacroID() &&
117 (LastParenLoc = getLastParenLoc(ASTCtx, AssertLoc)).isValid()) {
118 FixItHints.push_back(
119 FixItHint::CreateReplacement(SourceRange(AssertLoc), "static_assert"));
120
121 std::string StaticAssertMSG = ", \"\"";
122 if (AssertExprRoot) {
123 FixItHints.push_back(FixItHint::CreateRemoval(
124 SourceRange(AssertExprRoot->getOperatorLoc())));
125 FixItHints.push_back(FixItHint::CreateRemoval(
126 SourceRange(AssertMSG->getBeginLoc(), AssertMSG->getEndLoc())));
127 StaticAssertMSG = (Twine(", \"") + AssertMSG->getString() + "\"").str();
128 }
129
130 FixItHints.push_back(
131 FixItHint::CreateInsertion(LastParenLoc, StaticAssertMSG));
132 }
133
134 diag(AssertLoc, "found assert() that could be replaced by static_assert()")
135 << FixItHints;
136 }
137
getLastParenLoc(const ASTContext * ASTCtx,SourceLocation AssertLoc)138 SourceLocation StaticAssertCheck::getLastParenLoc(const ASTContext *ASTCtx,
139 SourceLocation AssertLoc) {
140 const LangOptions &Opts = ASTCtx->getLangOpts();
141 const SourceManager &SM = ASTCtx->getSourceManager();
142
143 llvm::Optional<llvm::MemoryBufferRef> Buffer =
144 SM.getBufferOrNone(SM.getFileID(AssertLoc));
145 if (!Buffer)
146 return SourceLocation();
147
148 const char *BufferPos = SM.getCharacterData(AssertLoc);
149
150 Token Token;
151 Lexer Lexer(SM.getLocForStartOfFile(SM.getFileID(AssertLoc)), Opts,
152 Buffer->getBufferStart(), BufferPos, Buffer->getBufferEnd());
153
154 // assert first left parenthesis
155 if (Lexer.LexFromRawLexer(Token) || Lexer.LexFromRawLexer(Token) ||
156 !Token.is(tok::l_paren))
157 return SourceLocation();
158
159 unsigned int ParenCount = 1;
160 while (ParenCount && !Lexer.LexFromRawLexer(Token)) {
161 if (Token.is(tok::l_paren))
162 ++ParenCount;
163 else if (Token.is(tok::r_paren))
164 --ParenCount;
165 }
166
167 return Token.getLocation();
168 }
169
170 } // namespace misc
171 } // namespace tidy
172 } // namespace clang
173