clang-tools  14.0.0git
StaticAssertCheck.cpp
Go to the documentation of this file.
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 
26 StaticAssertCheck::StaticAssertCheck(StringRef Name, ClangTidyContext *Context)
27  : ClangTidyCheck(Name, Context) {}
28 
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 
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_insensitive("false") == 0 ||
108  FalseMacroName.compare_insensitive("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  if (AssertExprRoot) {
122  FixItHints.push_back(FixItHint::CreateRemoval(
123  SourceRange(AssertExprRoot->getOperatorLoc())));
124  FixItHints.push_back(FixItHint::CreateRemoval(
125  SourceRange(AssertMSG->getBeginLoc(), AssertMSG->getEndLoc())));
126  FixItHints.push_back(FixItHint::CreateInsertion(
127  LastParenLoc, (Twine(", \"") + AssertMSG->getString() + "\"").str()));
128  } else if (!Opts.CPlusPlus17) {
129  FixItHints.push_back(FixItHint::CreateInsertion(LastParenLoc, ", \"\""));
130  }
131  }
132 
133  diag(AssertLoc, "found assert() that could be replaced by static_assert()")
134  << FixItHints;
135 }
136 
137 SourceLocation StaticAssertCheck::getLastParenLoc(const ASTContext *ASTCtx,
138  SourceLocation AssertLoc) {
139  const LangOptions &Opts = ASTCtx->getLangOpts();
140  const SourceManager &SM = ASTCtx->getSourceManager();
141 
142  llvm::Optional<llvm::MemoryBufferRef> Buffer =
143  SM.getBufferOrNone(SM.getFileID(AssertLoc));
144  if (!Buffer)
145  return SourceLocation();
146 
147  const char *BufferPos = SM.getCharacterData(AssertLoc);
148 
149  Token Token;
150  Lexer Lexer(SM.getLocForStartOfFile(SM.getFileID(AssertLoc)), Opts,
151  Buffer->getBufferStart(), BufferPos, Buffer->getBufferEnd());
152 
153  // assert first left parenthesis
154  if (Lexer.LexFromRawLexer(Token) || Lexer.LexFromRawLexer(Token) ||
155  !Token.is(tok::l_paren))
156  return SourceLocation();
157 
158  unsigned int ParenCount = 1;
159  while (ParenCount && !Lexer.LexFromRawLexer(Token)) {
160  if (Token.is(tok::l_paren))
161  ++ParenCount;
162  else if (Token.is(tok::r_paren))
163  --ParenCount;
164  }
165 
166  return Token.getLocation();
167 }
168 
169 } // namespace misc
170 } // namespace tidy
171 } // namespace clang
StaticAssertCheck.h
clang::tidy::misc::StaticAssertCheck::registerMatchers
void registerMatchers(ast_matchers::MatchFinder *Finder) override
Override this to register AST matchers with Finder.
Definition: StaticAssertCheck.cpp:29
clang::tidy::ClangTidyCheck
Base class for all clang-tidy checks.
Definition: ClangTidyCheck.h:54
clang::tidy::misc::StaticAssertCheck::check
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
ClangTidyChecks that register ASTMatchers should do the actual work in here.
Definition: StaticAssertCheck.cpp:74
clang::ast_matchers
Definition: AbseilMatcher.h:14
Condition
std::string Condition
Condition used after the preprocessor directive.
Definition: RedundantPreprocessorCheck.cpp:24
clang::tidy::ClangTidyContext
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
Definition: ClangTidyDiagnosticConsumer.h:72
Name
static constexpr llvm::StringLiteral Name
Definition: UppercaseLiteralSuffixCheck.cpp:28
clang::tidy::ClangTidyCheck::diag
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check's name.
Definition: ClangTidyCheck.cpp:25
clang
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
Definition: ApplyReplacements.h:27