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