clang-tools  14.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/Lex/Lexer.h"
13 #include "clang/Lex/Preprocessor.h"
14 #include "clang/Tooling/FixIt.h"
15 #include "llvm/ADT/SmallVector.h"
16 
17 using namespace clang::ast_matchers;
18 
19 namespace clang {
20 namespace tidy {
21 namespace readability {
22 
23 namespace {
24 
25 class PPConditionalCollector : public PPCallbacks {
26 public:
27  PPConditionalCollector(
28  ElseAfterReturnCheck::ConditionalBranchMap &Collections,
29  const SourceManager &SM)
30  : Collections(Collections), SM(SM) {}
31  void Endif(SourceLocation Loc, SourceLocation IfLoc) override {
32  if (!SM.isWrittenInSameFile(Loc, IfLoc))
33  return;
34  SmallVectorImpl<SourceRange> &Collection = Collections[SM.getFileID(Loc)];
35  assert(Collection.empty() || Collection.back().getEnd() < Loc);
36  Collection.emplace_back(IfLoc, Loc);
37  }
38 
39 private:
40  ElseAfterReturnCheck::ConditionalBranchMap &Collections;
41  const SourceManager &SM;
42 };
43 
44 } // namespace
45 
46 static const char InterruptingStr[] = "interrupting";
47 static const char WarningMessage[] = "do not use 'else' after '%0'";
48 static const char WarnOnUnfixableStr[] = "WarnOnUnfixable";
49 static const char WarnOnConditionVariablesStr[] = "WarnOnConditionVariables";
50 
51 static const DeclRefExpr *findUsage(const Stmt *Node, int64_t DeclIdentifier) {
52  if (!Node)
53  return nullptr;
54  if (const auto *DeclRef = dyn_cast<DeclRefExpr>(Node)) {
55  if (DeclRef->getDecl()->getID() == DeclIdentifier)
56  return DeclRef;
57  } else {
58  for (const Stmt *ChildNode : Node->children()) {
59  if (const DeclRefExpr *Result = findUsage(ChildNode, DeclIdentifier))
60  return Result;
61  }
62  }
63  return nullptr;
64 }
65 
66 static const DeclRefExpr *
67 findUsageRange(const Stmt *Node,
68  const llvm::ArrayRef<int64_t> &DeclIdentifiers) {
69  if (!Node)
70  return nullptr;
71  if (const auto *DeclRef = dyn_cast<DeclRefExpr>(Node)) {
72  if (llvm::is_contained(DeclIdentifiers, DeclRef->getDecl()->getID()))
73  return DeclRef;
74  } else {
75  for (const Stmt *ChildNode : Node->children()) {
76  if (const DeclRefExpr *Result =
77  findUsageRange(ChildNode, DeclIdentifiers))
78  return Result;
79  }
80  }
81  return nullptr;
82 }
83 
84 static const DeclRefExpr *checkInitDeclUsageInElse(const IfStmt *If) {
85  const auto *InitDeclStmt = dyn_cast_or_null<DeclStmt>(If->getInit());
86  if (!InitDeclStmt)
87  return nullptr;
88  if (InitDeclStmt->isSingleDecl()) {
89  const Decl *InitDecl = InitDeclStmt->getSingleDecl();
90  assert(isa<VarDecl>(InitDecl) && "SingleDecl must be a VarDecl");
91  return findUsage(If->getElse(), InitDecl->getID());
92  }
93  llvm::SmallVector<int64_t, 4> DeclIdentifiers;
94  for (const Decl *ChildDecl : InitDeclStmt->decls()) {
95  assert(isa<VarDecl>(ChildDecl) && "Init Decls must be a VarDecl");
96  DeclIdentifiers.push_back(ChildDecl->getID());
97  }
98  return findUsageRange(If->getElse(), DeclIdentifiers);
99 }
100 
101 static const DeclRefExpr *checkConditionVarUsageInElse(const IfStmt *If) {
102  if (const VarDecl *CondVar = If->getConditionVariable())
103  return findUsage(If->getElse(), CondVar->getID());
104  return nullptr;
105 }
106 
107 static bool containsDeclInScope(const Stmt *Node) {
108  if (isa<DeclStmt>(Node))
109  return true;
110  if (const auto *Compound = dyn_cast<CompoundStmt>(Node))
111  return llvm::any_of(Compound->body(), [](const Stmt *SubNode) {
112  return isa<DeclStmt>(SubNode);
113  });
114  return false;
115 }
116 
117 static void removeElseAndBrackets(DiagnosticBuilder &Diag, ASTContext &Context,
118  const Stmt *Else, SourceLocation ElseLoc) {
119  auto Remap = [&](SourceLocation Loc) {
120  return Context.getSourceManager().getExpansionLoc(Loc);
121  };
122  auto TokLen = [&](SourceLocation Loc) {
123  return Lexer::MeasureTokenLength(Loc, Context.getSourceManager(),
124  Context.getLangOpts());
125  };
126 
127  if (const auto *CS = dyn_cast<CompoundStmt>(Else)) {
128  Diag << tooling::fixit::createRemoval(ElseLoc);
129  SourceLocation LBrace = CS->getLBracLoc();
130  SourceLocation RBrace = CS->getRBracLoc();
131  SourceLocation RangeStart =
132  Remap(LBrace).getLocWithOffset(TokLen(LBrace) + 1);
133  SourceLocation RangeEnd = Remap(RBrace).getLocWithOffset(-1);
134 
135  llvm::StringRef Repl = Lexer::getSourceText(
136  CharSourceRange::getTokenRange(RangeStart, RangeEnd),
137  Context.getSourceManager(), Context.getLangOpts());
138  Diag << tooling::fixit::createReplacement(CS->getSourceRange(), Repl);
139  } else {
140  SourceLocation ElseExpandedLoc = Remap(ElseLoc);
141  SourceLocation EndLoc = Remap(Else->getEndLoc());
142 
143  llvm::StringRef Repl = Lexer::getSourceText(
144  CharSourceRange::getTokenRange(
145  ElseExpandedLoc.getLocWithOffset(TokLen(ElseLoc) + 1), EndLoc),
146  Context.getSourceManager(), Context.getLangOpts());
147  Diag << tooling::fixit::createReplacement(
148  SourceRange(ElseExpandedLoc, EndLoc), Repl);
149  }
150 }
151 
152 ElseAfterReturnCheck::ElseAfterReturnCheck(StringRef Name,
153  ClangTidyContext *Context)
154  : ClangTidyCheck(Name, Context),
155  WarnOnUnfixable(Options.get(WarnOnUnfixableStr, true)),
156  WarnOnConditionVariables(Options.get(WarnOnConditionVariablesStr, true)) {
157 }
158 
160  Options.store(Opts, WarnOnUnfixableStr, WarnOnUnfixable);
161  Options.store(Opts, WarnOnConditionVariablesStr, WarnOnConditionVariables);
162 }
163 
164 void ElseAfterReturnCheck::registerPPCallbacks(const SourceManager &SM,
165  Preprocessor *PP,
166  Preprocessor *ModuleExpanderPP) {
167  PP->addPPCallbacks(
168  std::make_unique<PPConditionalCollector>(this->PPConditionals, SM));
169 }
170 
171 void ElseAfterReturnCheck::registerMatchers(MatchFinder *Finder) {
172  const auto InterruptsControlFlow = stmt(anyOf(
173  returnStmt().bind(InterruptingStr), continueStmt().bind(InterruptingStr),
174  breakStmt().bind(InterruptingStr), cxxThrowExpr().bind(InterruptingStr)));
175  Finder->addMatcher(
176  compoundStmt(
177  forEach(ifStmt(unless(isConstexpr()),
178  hasThen(stmt(
179  anyOf(InterruptsControlFlow,
180  compoundStmt(has(InterruptsControlFlow))))),
181  hasElse(stmt().bind("else")))
182  .bind("if")))
183  .bind("cs"),
184  this);
185 }
186 
188  const ElseAfterReturnCheck::ConditionalBranchMap &ConditionalBranchMap,
189  const SourceManager &SM, SourceLocation StartLoc, SourceLocation EndLoc) {
190 
191  SourceLocation ExpandedStartLoc = SM.getExpansionLoc(StartLoc);
192  SourceLocation ExpandedEndLoc = SM.getExpansionLoc(EndLoc);
193  if (!SM.isWrittenInSameFile(ExpandedStartLoc, ExpandedEndLoc))
194  return false;
195 
196  // StartLoc and EndLoc expand to the same macro.
197  if (ExpandedStartLoc == ExpandedEndLoc)
198  return false;
199 
200  assert(ExpandedStartLoc < ExpandedEndLoc);
201 
202  auto Iter = ConditionalBranchMap.find(SM.getFileID(ExpandedEndLoc));
203 
204  if (Iter == ConditionalBranchMap.end() || Iter->getSecond().empty())
205  return false;
206 
207  const SmallVectorImpl<SourceRange> &ConditionalBranches = Iter->getSecond();
208 
209  assert(llvm::is_sorted(ConditionalBranches,
210  [](const SourceRange &LHS, const SourceRange &RHS) {
211  return LHS.getEnd() < RHS.getEnd();
212  }));
213 
214  // First conditional block that ends after ExpandedStartLoc.
215  const auto *Begin =
216  llvm::lower_bound(ConditionalBranches, ExpandedStartLoc,
217  [](const SourceRange &LHS, const SourceLocation &RHS) {
218  return LHS.getEnd() < RHS;
219  });
220  const auto *End = ConditionalBranches.end();
221  for (; Begin != End && Begin->getEnd() < ExpandedEndLoc; ++Begin)
222  if (Begin->getBegin() < ExpandedStartLoc)
223  return true;
224  return false;
225 }
226 
227 static StringRef getControlFlowString(const Stmt &Stmt) {
228  if (isa<ReturnStmt>(Stmt))
229  return "return";
230  if (isa<ContinueStmt>(Stmt))
231  return "continue";
232  if (isa<BreakStmt>(Stmt))
233  return "break";
234  if (isa<CXXThrowExpr>(Stmt))
235  return "throw";
236  llvm_unreachable("Unknown control flow interruptor");
237 }
238 
239 void ElseAfterReturnCheck::check(const MatchFinder::MatchResult &Result) {
240  const auto *If = Result.Nodes.getNodeAs<IfStmt>("if");
241  const auto *Else = Result.Nodes.getNodeAs<Stmt>("else");
242  const auto *OuterScope = Result.Nodes.getNodeAs<CompoundStmt>("cs");
243  const auto *Interrupt = Result.Nodes.getNodeAs<Stmt>(InterruptingStr);
244  SourceLocation ElseLoc = If->getElseLoc();
245 
247  PPConditionals, *Result.SourceManager, Interrupt->getBeginLoc(),
248  ElseLoc))
249  return;
250 
251  bool IsLastInScope = OuterScope->body_back() == If;
252  StringRef ControlFlowInterruptor = getControlFlowString(*Interrupt);
253 
254  if (!IsLastInScope && containsDeclInScope(Else)) {
255  if (WarnOnUnfixable) {
256  // Warn, but don't attempt an autofix.
257  diag(ElseLoc, WarningMessage) << ControlFlowInterruptor;
258  }
259  return;
260  }
261 
262  if (checkConditionVarUsageInElse(If) != nullptr) {
263  if (!WarnOnConditionVariables)
264  return;
265  if (IsLastInScope) {
266  // If the if statement is the last statement its enclosing statements
267  // scope, we can pull the decl out of the if statement.
268  DiagnosticBuilder Diag = diag(ElseLoc, WarningMessage)
269  << ControlFlowInterruptor
270  << SourceRange(ElseLoc);
271  if (checkInitDeclUsageInElse(If) != nullptr) {
272  Diag << tooling::fixit::createReplacement(
273  SourceRange(If->getIfLoc()),
274  (tooling::fixit::getText(*If->getInit(), *Result.Context) +
275  llvm::StringRef("\n"))
276  .str())
277  << tooling::fixit::createRemoval(If->getInit()->getSourceRange());
278  }
279  const DeclStmt *VDeclStmt = If->getConditionVariableDeclStmt();
280  const VarDecl *VDecl = If->getConditionVariable();
281  std::string Repl =
282  (tooling::fixit::getText(*VDeclStmt, *Result.Context) +
283  llvm::StringRef(";\n") +
284  tooling::fixit::getText(If->getIfLoc(), *Result.Context))
285  .str();
286  Diag << tooling::fixit::createReplacement(SourceRange(If->getIfLoc()),
287  Repl)
288  << tooling::fixit::createReplacement(VDeclStmt->getSourceRange(),
289  VDecl->getName());
290  removeElseAndBrackets(Diag, *Result.Context, Else, ElseLoc);
291  } else if (WarnOnUnfixable) {
292  // Warn, but don't attempt an autofix.
293  diag(ElseLoc, WarningMessage) << ControlFlowInterruptor;
294  }
295  return;
296  }
297 
298  if (checkInitDeclUsageInElse(If) != nullptr) {
299  if (!WarnOnConditionVariables)
300  return;
301  if (IsLastInScope) {
302  // If the if statement is the last statement its enclosing statements
303  // scope, we can pull the decl out of the if statement.
304  DiagnosticBuilder Diag = diag(ElseLoc, WarningMessage)
305  << ControlFlowInterruptor
306  << SourceRange(ElseLoc);
307  Diag << tooling::fixit::createReplacement(
308  SourceRange(If->getIfLoc()),
309  (tooling::fixit::getText(*If->getInit(), *Result.Context) +
310  "\n" +
311  tooling::fixit::getText(If->getIfLoc(), *Result.Context))
312  .str())
313  << tooling::fixit::createRemoval(If->getInit()->getSourceRange());
314  removeElseAndBrackets(Diag, *Result.Context, Else, ElseLoc);
315  } else if (WarnOnUnfixable) {
316  // Warn, but don't attempt an autofix.
317  diag(ElseLoc, WarningMessage) << ControlFlowInterruptor;
318  }
319  return;
320  }
321 
322  DiagnosticBuilder Diag = diag(ElseLoc, WarningMessage)
323  << ControlFlowInterruptor << SourceRange(ElseLoc);
324  removeElseAndBrackets(Diag, *Result.Context, Else, ElseLoc);
325 }
326 
327 } // namespace readability
328 } // namespace tidy
329 } // namespace clang
Loc
SourceLocation Loc
Definition: KernelNameRestrictionCheck.cpp:45
clang::tidy::readability::getControlFlowString
static StringRef getControlFlowString(const Stmt &Stmt)
Definition: ElseAfterReturnCheck.cpp:227
clang::tidy::ClangTidyOptions::OptionMap
llvm::StringMap< ClangTidyValue > OptionMap
Definition: ClangTidyOptions.h:115
clang::tidy::cppcoreguidelines::getSourceText
static std::string getSourceText(const CXXDestructorDecl &Destructor)
Definition: VirtualClassDestructorCheck.cpp:112
clang::tidy::readability::ElseAfterReturnCheck::check
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
ClangTidyChecks that register ASTMatchers should do the actual work in here.
Definition: ElseAfterReturnCheck.cpp:239
clang::tidy::ClangTidyCheck
Base class for all clang-tidy checks.
Definition: ClangTidyCheck.h:54
clang::tidy::modernize::getText
static StringRef getText(const Token &Tok, const SourceManager &Sources)
Definition: UseOverrideCheck.cpp:78
clang::tidy::readability::findUsageRange
static const DeclRefExpr * findUsageRange(const Stmt *Node, const llvm::ArrayRef< int64_t > &DeclIdentifiers)
Definition: ElseAfterReturnCheck.cpp:67
clang::tidy::readability::ElseAfterReturnCheck::registerMatchers
void registerMatchers(ast_matchers::MatchFinder *Finder) override
Override this to register AST matchers with Finder.
Definition: ElseAfterReturnCheck.cpp:171
clang::ast_matchers
Definition: AbseilMatcher.h:14
clang::tidy::readability::removeElseAndBrackets
static void removeElseAndBrackets(DiagnosticBuilder &Diag, ASTContext &Context, const Stmt *Else, SourceLocation ElseLoc)
Definition: ElseAfterReturnCheck.cpp:117
clang::tidy::readability::WarningMessage
static const char WarningMessage[]
Definition: ElseAfterReturnCheck.cpp:47
clang::tidy::readability::InterruptingStr
static const char InterruptingStr[]
Definition: ElseAfterReturnCheck.cpp:46
Decl
const FunctionDecl * Decl
Definition: AvoidBindCheck.cpp:100
clang::tidy::ClangTidyCheck::Options
OptionsView Options
Definition: ClangTidyCheck.h:416
clang::tidy::readability::hasPreprocessorBranchEndBetweenLocations
static bool hasPreprocessorBranchEndBetweenLocations(const ElseAfterReturnCheck::ConditionalBranchMap &ConditionalBranchMap, const SourceManager &SM, SourceLocation StartLoc, SourceLocation EndLoc)
Definition: ElseAfterReturnCheck.cpp:187
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::readability::findUsage
static const DeclRefExpr * findUsage(const Stmt *Node, int64_t DeclIdentifier)
Definition: ElseAfterReturnCheck.cpp:51
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::tidy::readability::checkConditionVarUsageInElse
static const DeclRefExpr * checkConditionVarUsageInElse(const IfStmt *If)
Definition: ElseAfterReturnCheck.cpp:101
clang::tidy::bugprone::PP
static Preprocessor * PP
Definition: BadSignalToKillThreadCheck.cpp:29
clang::tidy::readability::WarnOnConditionVariablesStr
static const char WarnOnConditionVariablesStr[]
Definition: ElseAfterReturnCheck.cpp:49
DeclRef
const DeclRefExpr * DeclRef
Definition: UseAfterMoveCheck.cpp:50
clang::tidy::readability::ElseAfterReturnCheck::ConditionalBranchMap
llvm::DenseMap< FileID, SmallVector< SourceRange, 1 > > ConditionalBranchMap
Definition: ElseAfterReturnCheck.h:36
clang::tidy::readability::containsDeclInScope
static bool containsDeclInScope(const Stmt *Node)
Definition: ElseAfterReturnCheck.cpp:107
clang::tidy::readability::checkInitDeclUsageInElse
static const DeclRefExpr * checkInitDeclUsageInElse(const IfStmt *If)
Definition: ElseAfterReturnCheck.cpp:84
clang
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
Definition: ApplyReplacements.h:27
clang::tidy::readability::ElseAfterReturnCheck::storeOptions
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
Should store all options supported by this check with their current values or default values for opti...
Definition: ElseAfterReturnCheck.cpp:159
ElseAfterReturnCheck.h
clang::tidy::ClangTidyCheck::OptionsView::store
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.
Definition: ClangTidyCheck.cpp:120
clang::tidy::readability::WarnOnUnfixableStr
static const char WarnOnUnfixableStr[]
Definition: ElseAfterReturnCheck.cpp:48
clang::tidy::readability::ElseAfterReturnCheck::registerPPCallbacks
void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) override
Override this to register PPCallbacks in the preprocessor.
Definition: ElseAfterReturnCheck.cpp:164