xref: /llvm-project/clang-tools-extra/clang-tidy/misc/StaticAssertCheck.cpp (revision 3af6ae0fbea40097e159c11893ee7ab57d00480c)
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 "../utils/Matchers.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 <optional>
20 #include <string>
21 
22 using namespace clang::ast_matchers;
23 
24 namespace clang::tidy::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 NonConstexprVariableReference =
50       declRefExpr(to(varDecl(unless(isConstexpr()))),
51                   unless(hasAncestor(expr(matchers::hasUnevaluatedContext()))),
52                   unless(hasAncestor(typeLoc())));
53 
54   auto NonConstexprCode =
55       expr(anyOf(NonConstexprFunctionCall, NonConstexprVariableReference));
56   auto AssertCondition =
57       expr(
58           anyOf(expr(ignoringParenCasts(anyOf(
59                     AssertExprRoot, unaryOperator(hasUnaryOperand(
60                                         ignoringParenCasts(AssertExprRoot)))))),
61                 anything()),
62           unless(NonConstexprCode), unless(hasDescendant(NonConstexprCode)))
63           .bind("condition");
64   auto Condition =
65       anyOf(ignoringParenImpCasts(callExpr(
66                 hasDeclaration(functionDecl(hasName("__builtin_expect"))),
67                 hasArgument(0, AssertCondition))),
68             AssertCondition);
69 
70   Finder->addMatcher(conditionalOperator(hasCondition(Condition),
71                                          unless(isInTemplateInstantiation()))
72                          .bind("condStmt"),
73                      this);
74 
75   Finder->addMatcher(
76       ifStmt(hasCondition(Condition), unless(isInTemplateInstantiation()))
77           .bind("condStmt"),
78       this);
79 }
80 
check(const MatchFinder::MatchResult & Result)81 void StaticAssertCheck::check(const MatchFinder::MatchResult &Result) {
82   const ASTContext *ASTCtx = Result.Context;
83   const LangOptions &Opts = ASTCtx->getLangOpts();
84   const SourceManager &SM = ASTCtx->getSourceManager();
85   const auto *CondStmt = Result.Nodes.getNodeAs<Stmt>("condStmt");
86   const auto *Condition = Result.Nodes.getNodeAs<Expr>("condition");
87   const auto *IsAlwaysFalse = Result.Nodes.getNodeAs<Expr>("isAlwaysFalse");
88   const auto *AssertMSG = Result.Nodes.getNodeAs<StringLiteral>("assertMSG");
89   const auto *AssertExprRoot =
90       Result.Nodes.getNodeAs<BinaryOperator>("assertExprRoot");
91   const auto *CastExpr = Result.Nodes.getNodeAs<CStyleCastExpr>("castExpr");
92   SourceLocation AssertExpansionLoc = CondStmt->getBeginLoc();
93 
94   if (!AssertExpansionLoc.isValid() || !AssertExpansionLoc.isMacroID())
95     return;
96 
97   StringRef MacroName =
98       Lexer::getImmediateMacroName(AssertExpansionLoc, SM, Opts);
99 
100   if (MacroName != "assert" || Condition->isValueDependent() ||
101       Condition->isTypeDependent() || Condition->isInstantiationDependent() ||
102       !Condition->isEvaluatable(*ASTCtx))
103     return;
104 
105   // False literal is not the result of macro expansion.
106   if (IsAlwaysFalse && (!CastExpr || CastExpr->getType()->isPointerType())) {
107     SourceLocation FalseLiteralLoc =
108         SM.getImmediateSpellingLoc(IsAlwaysFalse->getExprLoc());
109     if (!FalseLiteralLoc.isMacroID())
110       return;
111 
112     StringRef FalseMacroName =
113         Lexer::getImmediateMacroName(FalseLiteralLoc, SM, Opts);
114     if (FalseMacroName.compare_insensitive("false") == 0 ||
115         FalseMacroName.compare_insensitive("null") == 0)
116       return;
117   }
118 
119   SourceLocation AssertLoc = SM.getImmediateMacroCallerLoc(AssertExpansionLoc);
120 
121   SmallVector<FixItHint, 4> FixItHints;
122   SourceLocation LastParenLoc;
123   if (AssertLoc.isValid() && !AssertLoc.isMacroID() &&
124       (LastParenLoc = getLastParenLoc(ASTCtx, AssertLoc)).isValid()) {
125     FixItHints.push_back(
126         FixItHint::CreateReplacement(SourceRange(AssertLoc), "static_assert"));
127 
128     if (AssertExprRoot) {
129       FixItHints.push_back(FixItHint::CreateRemoval(
130           SourceRange(AssertExprRoot->getOperatorLoc())));
131       FixItHints.push_back(FixItHint::CreateRemoval(
132           SourceRange(AssertMSG->getBeginLoc(), AssertMSG->getEndLoc())));
133       FixItHints.push_back(FixItHint::CreateInsertion(
134           LastParenLoc, (Twine(", \"") + AssertMSG->getString() + "\"").str()));
135     } else if (!Opts.CPlusPlus17) {
136       FixItHints.push_back(FixItHint::CreateInsertion(LastParenLoc, ", \"\""));
137     }
138   }
139 
140   diag(AssertLoc, "found assert() that could be replaced by static_assert()")
141       << FixItHints;
142 }
143 
getLastParenLoc(const ASTContext * ASTCtx,SourceLocation AssertLoc)144 SourceLocation StaticAssertCheck::getLastParenLoc(const ASTContext *ASTCtx,
145                                                   SourceLocation AssertLoc) {
146   const LangOptions &Opts = ASTCtx->getLangOpts();
147   const SourceManager &SM = ASTCtx->getSourceManager();
148 
149   std::optional<llvm::MemoryBufferRef> Buffer =
150       SM.getBufferOrNone(SM.getFileID(AssertLoc));
151   if (!Buffer)
152     return {};
153 
154   const char *BufferPos = SM.getCharacterData(AssertLoc);
155 
156   Token Token;
157   Lexer Lexer(SM.getLocForStartOfFile(SM.getFileID(AssertLoc)), Opts,
158               Buffer->getBufferStart(), BufferPos, Buffer->getBufferEnd());
159 
160   //        assert                          first left parenthesis
161   if (Lexer.LexFromRawLexer(Token) || Lexer.LexFromRawLexer(Token) ||
162       !Token.is(tok::l_paren))
163     return {};
164 
165   unsigned int ParenCount = 1;
166   while (ParenCount && !Lexer.LexFromRawLexer(Token)) {
167     if (Token.is(tok::l_paren))
168       ++ParenCount;
169     else if (Token.is(tok::r_paren))
170       --ParenCount;
171   }
172 
173   return Token.getLocation();
174 }
175 
176 } // namespace clang::tidy::misc
177