clang-tools  10.0.0
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 (!Node)
31  return nullptr;
32  if (const auto *DeclRef = dyn_cast<DeclRefExpr>(Node)) {
33  if (DeclRef->getDecl()->getID() == DeclIdentifier) {
34  return DeclRef;
35  }
36  } else {
37  for (const Stmt *ChildNode : Node->children()) {
38  if (const DeclRefExpr *Result = findUsage(ChildNode, DeclIdentifier)) {
39  return Result;
40  }
41  }
42  }
43  return nullptr;
44 }
45 
46 const DeclRefExpr *
47 findUsageRange(const Stmt *Node,
48  const llvm::iterator_range<int64_t *> &DeclIdentifiers) {
49  if (!Node)
50  return nullptr;
51  if (const auto *DeclRef = dyn_cast<DeclRefExpr>(Node)) {
52  if (llvm::is_contained(DeclIdentifiers, DeclRef->getDecl()->getID())) {
53  return DeclRef;
54  }
55  } else {
56  for (const Stmt *ChildNode : Node->children()) {
57  if (const DeclRefExpr *Result =
58  findUsageRange(ChildNode, DeclIdentifiers)) {
59  return Result;
60  }
61  }
62  }
63  return nullptr;
64 }
65 
66 const DeclRefExpr *checkInitDeclUsageInElse(const IfStmt *If) {
67  const auto *InitDeclStmt = dyn_cast_or_null<DeclStmt>(If->getInit());
68  if (!InitDeclStmt)
69  return nullptr;
70  if (InitDeclStmt->isSingleDecl()) {
71  const Decl *InitDecl = InitDeclStmt->getSingleDecl();
72  assert(isa<VarDecl>(InitDecl) && "SingleDecl must be a VarDecl");
73  return findUsage(If->getElse(), InitDecl->getID());
74  }
75  llvm::SmallVector<int64_t, 4> DeclIdentifiers;
76  for (const Decl *ChildDecl : InitDeclStmt->decls()) {
77  assert(isa<VarDecl>(ChildDecl) && "Init Decls must be a VarDecl");
78  DeclIdentifiers.push_back(ChildDecl->getID());
79  }
80  return findUsageRange(If->getElse(), DeclIdentifiers);
81 }
82 
83 const DeclRefExpr *checkConditionVarUsageInElse(const IfStmt *If) {
84  const VarDecl *CondVar = If->getConditionVariable();
85  return CondVar != nullptr ? findUsage(If->getElse(), CondVar->getID())
86  : nullptr;
87 }
88 
89 bool containsDeclInScope(const Stmt *Node) {
90  if (isa<DeclStmt>(Node)) {
91  return true;
92  }
93  if (const auto *Compound = dyn_cast<CompoundStmt>(Node)) {
94  return llvm::any_of(Compound->body(), [](const Stmt *SubNode) {
95  return isa<DeclStmt>(SubNode);
96  });
97  }
98  return false;
99 }
100 
101 void removeElseAndBrackets(DiagnosticBuilder &Diag, ASTContext &Context,
102  const Stmt *Else, SourceLocation ElseLoc) {
103  auto Remap = [&](SourceLocation Loc) {
104  return Context.getSourceManager().getExpansionLoc(Loc);
105  };
106  auto TokLen = [&](SourceLocation Loc) {
107  return Lexer::MeasureTokenLength(Loc, Context.getSourceManager(),
108  Context.getLangOpts());
109  };
110 
111  if (const auto *CS = dyn_cast<CompoundStmt>(Else)) {
112  Diag << tooling::fixit::createRemoval(ElseLoc);
113  SourceLocation LBrace = CS->getLBracLoc();
114  SourceLocation RBrace = CS->getRBracLoc();
115  SourceLocation RangeStart =
116  Remap(LBrace).getLocWithOffset(TokLen(LBrace) + 1);
117  SourceLocation RangeEnd = Remap(RBrace).getLocWithOffset(-1);
118 
119  llvm::StringRef Repl = Lexer::getSourceText(
120  CharSourceRange::getTokenRange(RangeStart, RangeEnd),
121  Context.getSourceManager(), Context.getLangOpts());
122  Diag << tooling::fixit::createReplacement(CS->getSourceRange(), Repl);
123  } else {
124  SourceLocation ElseExpandedLoc = Remap(ElseLoc);
125  SourceLocation EndLoc = Remap(Else->getEndLoc());
126 
127  llvm::StringRef Repl = Lexer::getSourceText(
129  ElseExpandedLoc.getLocWithOffset(TokLen(ElseLoc) + 1), EndLoc),
130  Context.getSourceManager(), Context.getLangOpts());
131  Diag << tooling::fixit::createReplacement(
132  SourceRange(ElseExpandedLoc, EndLoc), Repl);
133  }
134 }
135 } // namespace
136 
137 ElseAfterReturnCheck::ElseAfterReturnCheck(StringRef Name,
138  ClangTidyContext *Context)
139  : ClangTidyCheck(Name, Context),
140  WarnOnUnfixable(Options.get(WarnOnUnfixableStr, true)) {}
141 
143  Options.store(Opts, WarnOnUnfixableStr, WarnOnUnfixable);
144 }
145 
146 void ElseAfterReturnCheck::registerMatchers(MatchFinder *Finder) {
147  const auto InterruptsControlFlow =
148  stmt(anyOf(returnStmt().bind(ReturnStr), continueStmt().bind(ContinueStr),
149  breakStmt().bind(BreakStr),
150  expr(ignoringImplicit(cxxThrowExpr().bind(ThrowStr)))));
151  Finder->addMatcher(
152  compoundStmt(
153  forEach(ifStmt(unless(isConstexpr()),
154  hasThen(stmt(
155  anyOf(InterruptsControlFlow,
156  compoundStmt(has(InterruptsControlFlow))))),
157  hasElse(stmt().bind("else")))
158  .bind("if")))
159  .bind("cs"),
160  this);
161 }
162 
163 void ElseAfterReturnCheck::check(const MatchFinder::MatchResult &Result) {
164  const auto *If = Result.Nodes.getNodeAs<IfStmt>("if");
165  const auto *Else = Result.Nodes.getNodeAs<Stmt>("else");
166  const auto *OuterScope = Result.Nodes.getNodeAs<CompoundStmt>("cs");
167 
168  bool IsLastInScope = OuterScope->body_back() == If;
169  SourceLocation ElseLoc = If->getElseLoc();
170 
171  auto ControlFlowInterruptor = [&]() -> llvm::StringRef {
172  for (llvm::StringRef BindingName :
173  {ReturnStr, ContinueStr, BreakStr, ThrowStr})
174  if (Result.Nodes.getNodeAs<Stmt>(BindingName))
175  return BindingName;
176  return {};
177  }();
178 
179  if (!IsLastInScope && containsDeclInScope(Else)) {
180  if (WarnOnUnfixable) {
181  // Warn, but don't attempt an autofix.
182  diag(ElseLoc, WarningMessage) << ControlFlowInterruptor;
183  }
184  return;
185  }
186 
187  if (checkConditionVarUsageInElse(If) != nullptr) {
188  if (IsLastInScope) {
189  // If the if statement is the last statement its enclosing statements
190  // scope, we can pull the decl out of the if statement.
191  DiagnosticBuilder Diag =
192  diag(ElseLoc, WarningMessage, clang::DiagnosticIDs::Level::Remark)
193  << ControlFlowInterruptor;
194  if (checkInitDeclUsageInElse(If) != nullptr) {
195  Diag << tooling::fixit::createReplacement(
196  SourceRange(If->getIfLoc()),
197  (tooling::fixit::getText(*If->getInit(), *Result.Context) +
198  llvm::StringRef("\n"))
199  .str())
200  << tooling::fixit::createRemoval(If->getInit()->getSourceRange());
201  }
202  const DeclStmt *VDeclStmt = If->getConditionVariableDeclStmt();
203  const VarDecl *VDecl = If->getConditionVariable();
204  std::string Repl =
205  (tooling::fixit::getText(*VDeclStmt, *Result.Context) +
206  llvm::StringRef(";\n") +
207  tooling::fixit::getText(If->getIfLoc(), *Result.Context))
208  .str();
209  Diag << tooling::fixit::createReplacement(SourceRange(If->getIfLoc()),
210  Repl)
211  << tooling::fixit::createReplacement(VDeclStmt->getSourceRange(),
212  VDecl->getName());
213  removeElseAndBrackets(Diag, *Result.Context, Else, ElseLoc);
214  } else if (WarnOnUnfixable) {
215  // Warn, but don't attempt an autofix.
216  diag(ElseLoc, WarningMessage) << ControlFlowInterruptor;
217  }
218  return;
219  }
220 
221  if (checkInitDeclUsageInElse(If) != nullptr) {
222  if (IsLastInScope) {
223  // If the if statement is the last statement its enclosing statements
224  // scope, we can pull the decl out of the if statement.
225  DiagnosticBuilder Diag = diag(ElseLoc, WarningMessage)
226  << ControlFlowInterruptor;
227  Diag << tooling::fixit::createReplacement(
228  SourceRange(If->getIfLoc()),
229  (tooling::fixit::getText(*If->getInit(), *Result.Context) +
230  "\n" +
231  tooling::fixit::getText(If->getIfLoc(), *Result.Context))
232  .str())
233  << tooling::fixit::createRemoval(If->getInit()->getSourceRange());
234  removeElseAndBrackets(Diag, *Result.Context, Else, ElseLoc);
235  } else if (WarnOnUnfixable) {
236  // Warn, but don't attempt an autofix.
237  diag(ElseLoc, WarningMessage) << ControlFlowInterruptor;
238  }
239  return;
240  }
241 
242  DiagnosticBuilder Diag = diag(ElseLoc, WarningMessage)
243  << ControlFlowInterruptor;
244  removeElseAndBrackets(Diag, *Result.Context, Else, ElseLoc);
245 }
246 
247 } // namespace readability
248 } // namespace tidy
249 } // 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.