• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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