clang-tools  10.0.0git
ElseAfterReturnCheck.cpp
Go to the documentation of this file.
1 //===--- ElseAfterReturnCheck.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 "ElseAfterReturnCheck.h"
10 #include "clang/AST/ASTContext.h"
11 #include "clang/ASTMatchers/ASTMatchFinder.h"
12 #include "clang/Tooling/FixIt.h"
13 #include "llvm/ADT/SmallVector.h"
14 
15 using namespace clang::ast_matchers;
16 
17 namespace clang {
18 namespace tidy {
19 namespace readability {
20 
21 namespace {
22 static const char ReturnStr[] = "return";
23 static const char ContinueStr[] = "continue";
24 static const char BreakStr[] = "break";
25 static const char ThrowStr[] = "throw";
26 static const char WarningMessage[] = "do not use 'else' after '%0'";
27 static const char WarnOnUnfixableStr[] = "WarnOnUnfixable";
28 
29 const DeclRefExpr *findUsage(const Stmt *Node, int64_t DeclIdentifier) {
30  if (const auto *DeclRef = dyn_cast<DeclRefExpr>(Node)) {
31  if (DeclRef->getDecl()->getID() == DeclIdentifier) {
32  return DeclRef;
33  }
34  } else {
35  for (const Stmt *ChildNode : Node->children()) {
36  if (const DeclRefExpr *Result = findUsage(ChildNode, DeclIdentifier)) {
37  return Result;
38  }
39  }
40  }
41  return nullptr;
42 }
43 
44 const DeclRefExpr *
45 findUsageRange(const Stmt *Node,
46  const llvm::iterator_range<int64_t *> &DeclIdentifiers) {
47  if (const auto *DeclRef = dyn_cast<DeclRefExpr>(Node)) {
48  if (llvm::is_contained(DeclIdentifiers, DeclRef->getDecl()->getID())) {
49  return DeclRef;
50  }
51  } else {
52  for (const Stmt *ChildNode : Node->children()) {
53  if (const DeclRefExpr *Result =
54  findUsageRange(ChildNode, DeclIdentifiers)) {
55  return Result;
56  }
57  }
58  }
59  return nullptr;
60 }
61 
62 const DeclRefExpr *checkInitDeclUsageInElse(const IfStmt *If) {
63  const auto *InitDeclStmt = dyn_cast_or_null<DeclStmt>(If->getInit());
64  if (!InitDeclStmt)
65  return nullptr;
66  if (InitDeclStmt->isSingleDecl()) {
67  const Decl *InitDecl = InitDeclStmt->getSingleDecl();
68  assert(isa<VarDecl>(InitDecl) && "SingleDecl must be a VarDecl");
69  return findUsage(If->getElse(), InitDecl->getID());
70  }
71  llvm::SmallVector<int64_t, 4> DeclIdentifiers;
72  for (const Decl *ChildDecl : InitDeclStmt->decls()) {
73  assert(isa<VarDecl>(ChildDecl) && "Init Decls must be a VarDecl");
74  DeclIdentifiers.push_back(ChildDecl->getID());
75  }
76  return findUsageRange(If->getElse(), DeclIdentifiers);
77 }
78 
79 const DeclRefExpr *checkConditionVarUsageInElse(const IfStmt *If) {
80  const VarDecl *CondVar = If->getConditionVariable();
81  return CondVar != nullptr ? findUsage(If->getElse(), CondVar->getID())
82  : nullptr;
83 }
84 
85 bool containsDeclInScope(const Stmt *Node) {
86  if (isa<DeclStmt>(Node)) {
87  return true;
88  }
89  if (const auto *Compound = dyn_cast<CompoundStmt>(Node)) {
90  return llvm::any_of(Compound->body(), [](const Stmt *SubNode) {
91  return isa<DeclStmt>(SubNode);
92  });
93  }
94  return false;
95 }
96 
97 void removeElseAndBrackets(DiagnosticBuilder &Diag, ASTContext &Context,
98  const Stmt *Else, SourceLocation ElseLoc) {
99  auto Remap = [&](SourceLocation Loc) {
100  return Context.getSourceManager().getExpansionLoc(Loc);
101  };
102  auto TokLen = [&](SourceLocation Loc) {
103  return Lexer::MeasureTokenLength(Loc, Context.getSourceManager(),
104  Context.getLangOpts());
105  };
106 
107  if (const auto *CS = dyn_cast<CompoundStmt>(Else)) {
108  Diag << tooling::fixit::createRemoval(ElseLoc);
109  SourceLocation LBrace = CS->getLBracLoc();
110  SourceLocation RBrace = CS->getRBracLoc();
111  SourceLocation RangeStart =
112  Remap(LBrace).getLocWithOffset(TokLen(LBrace) + 1);
113  SourceLocation RangeEnd = Remap(RBrace).getLocWithOffset(-1);
114 
115  llvm::StringRef Repl = Lexer::getSourceText(
116  CharSourceRange::getTokenRange(RangeStart, RangeEnd),
117  Context.getSourceManager(), Context.getLangOpts());
118  Diag << tooling::fixit::createReplacement(CS->getSourceRange(), Repl);
119  } else {
120  SourceLocation ElseExpandedLoc = Remap(ElseLoc);
121  SourceLocation EndLoc = Remap(Else->getEndLoc());
122 
123  llvm::StringRef Repl = Lexer::getSourceText(
125  ElseExpandedLoc.getLocWithOffset(TokLen(ElseLoc) + 1), EndLoc),
126  Context.getSourceManager(), Context.getLangOpts());
127  Diag << tooling::fixit::createReplacement(
128  SourceRange(ElseExpandedLoc, EndLoc), Repl);
129  }
130 }
131 } // namespace
132 
133 ElseAfterReturnCheck::ElseAfterReturnCheck(StringRef Name,
134  ClangTidyContext *Context)
135  : ClangTidyCheck(Name, Context),
136  WarnOnUnfixable(Options.get(WarnOnUnfixableStr, true)) {}
137 
139  Options.store(Opts, WarnOnUnfixableStr, WarnOnUnfixable);
140 }
141 
142 void ElseAfterReturnCheck::registerMatchers(MatchFinder *Finder) {
143  const auto InterruptsControlFlow =
144  stmt(anyOf(returnStmt().bind(ReturnStr), continueStmt().bind(ContinueStr),
145  breakStmt().bind(BreakStr),
146  expr(ignoringImplicit(cxxThrowExpr().bind(ThrowStr)))));
147  Finder->addMatcher(
148  compoundStmt(
149  forEach(ifStmt(unless(isConstexpr()),
150  hasThen(stmt(
151  anyOf(InterruptsControlFlow,
152  compoundStmt(has(InterruptsControlFlow))))),
153  hasElse(stmt().bind("else")))
154  .bind("if")))
155  .bind("cs"),
156  this);
157 }
158 
159 void ElseAfterReturnCheck::check(const MatchFinder::MatchResult &Result) {
160  const auto *If = Result.Nodes.getNodeAs<IfStmt>("if");
161  const auto *Else = Result.Nodes.getNodeAs<Stmt>("else");
162  const auto *OuterScope = Result.Nodes.getNodeAs<CompoundStmt>("cs");
163 
164  bool IsLastInScope = OuterScope->body_back() == If;
165  SourceLocation ElseLoc = If->getElseLoc();
166 
167  auto ControlFlowInterruptor = [&]() -> llvm::StringRef {
168  for (llvm::StringRef BindingName :
169  {ReturnStr, ContinueStr, BreakStr, ThrowStr})
170  if (Result.Nodes.getNodeAs<Stmt>(BindingName))
171  return BindingName;
172  return {};
173  }();
174 
175  if (!IsLastInScope && containsDeclInScope(Else)) {
176  if (WarnOnUnfixable) {
177  // Warn, but don't attempt an autofix.
178  diag(ElseLoc, WarningMessage) << ControlFlowInterruptor;
179  }
180  return;
181  }
182 
183  if (checkConditionVarUsageInElse(If) != nullptr) {
184  if (IsLastInScope) {
185  // If the if statement is the last statement its enclosing statements
186  // scope, we can pull the decl out of the if statement.
187  DiagnosticBuilder Diag =
188  diag(ElseLoc, WarningMessage, clang::DiagnosticIDs::Level::Remark)
189  << ControlFlowInterruptor;
190  if (checkInitDeclUsageInElse(If) != nullptr) {
191  Diag << tooling::fixit::createReplacement(
192  SourceRange(If->getIfLoc()),
193  (tooling::fixit::getText(*If->getInit(), *Result.Context) +
194  llvm::StringRef("\n"))
195  .str())
196  << tooling::fixit::createRemoval(If->getInit()->getSourceRange());
197  }
198  const DeclStmt *VDeclStmt = If->getConditionVariableDeclStmt();
199  const VarDecl *VDecl = If->getConditionVariable();
200  std::string Repl =
201  (tooling::fixit::getText(*VDeclStmt, *Result.Context) +
202  llvm::StringRef(";\n") +
203  tooling::fixit::getText(If->getIfLoc(), *Result.Context))
204  .str();
205  Diag << tooling::fixit::createReplacement(SourceRange(If->getIfLoc()),
206  Repl)
207  << tooling::fixit::createReplacement(VDeclStmt->getSourceRange(),
208  VDecl->getName());
209  removeElseAndBrackets(Diag, *Result.Context, Else, ElseLoc);
210  } else if (WarnOnUnfixable) {
211  // Warn, but don't attempt an autofix.
212  diag(ElseLoc, WarningMessage) << ControlFlowInterruptor;
213  }
214  return;
215  }
216 
217  if (checkInitDeclUsageInElse(If) != nullptr) {
218  if (IsLastInScope) {
219  // If the if statement is the last statement its enclosing statements
220  // scope, we can pull the decl out of the if statement.
221  DiagnosticBuilder Diag = diag(ElseLoc, WarningMessage)
222  << ControlFlowInterruptor;
223  Diag << tooling::fixit::createReplacement(
224  SourceRange(If->getIfLoc()),
225  (tooling::fixit::getText(*If->getInit(), *Result.Context) +
226  "\n" +
227  tooling::fixit::getText(If->getIfLoc(), *Result.Context))
228  .str())
229  << tooling::fixit::createRemoval(If->getInit()->getSourceRange());
230  removeElseAndBrackets(Diag, *Result.Context, Else, ElseLoc);
231  } else if (WarnOnUnfixable) {
232  // Warn, but don't attempt an autofix.
233  diag(ElseLoc, WarningMessage) << ControlFlowInterruptor;
234  }
235  return;
236  }
237 
238  DiagnosticBuilder Diag = diag(ElseLoc, WarningMessage)
239  << ControlFlowInterruptor;
240  removeElseAndBrackets(Diag, *Result.Context, Else, ElseLoc);
241 }
242 
243 } // namespace readability
244 } // namespace tidy
245 } // namespace clang
SourceLocation Loc
&#39;#&#39; location in the include directive
const FunctionDecl * Decl
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
ClangTidyChecks that register ASTMatchers should do the actual work in here.
Base class for all clang-tidy checks.
void registerMatchers(ast_matchers::MatchFinder *Finder) override
Override this to register AST matchers with Finder.
void store(ClangTidyOptions::OptionMap &Options, StringRef LocalName, StringRef Value) const
Stores an option with the check-local name LocalName with string value Value to Options.
static constexpr llvm::StringLiteral Name
std::map< std::string, std::string > OptionMap
llvm::Optional< Range > getTokenRange(const SourceManager &SM, const LangOptions &LangOpts, SourceLocation TokLoc)
Returns the taken range at TokLoc.
Definition: SourceCode.cpp:227
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
Should store all options supported by this check with their current values or default values for opti...
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
const DeclRefExpr * DeclRef
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check&#39;s name.