• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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:
PPConditionalCollector(ElseAfterReturnCheck::ConditionalBranchMap & Collections,const SourceManager & SM)27   PPConditionalCollector(
28       ElseAfterReturnCheck::ConditionalBranchMap &Collections,
29       const SourceManager &SM)
30       : Collections(Collections), SM(SM) {}
Endif(SourceLocation Loc,SourceLocation IfLoc)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 
findUsage(const Stmt * Node,int64_t DeclIdentifier)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 *
findUsageRange(const Stmt * Node,const llvm::ArrayRef<int64_t> & DeclIdentifiers)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 
checkInitDeclUsageInElse(const IfStmt * If)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 
checkConditionVarUsageInElse(const IfStmt * If)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 
containsDeclInScope(const Stmt * Node)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 
removeElseAndBrackets(DiagnosticBuilder & Diag,ASTContext & Context,const Stmt * Else,SourceLocation ElseLoc)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 
ElseAfterReturnCheck(StringRef Name,ClangTidyContext * Context)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 
storeOptions(ClangTidyOptions::OptionMap & Opts)159 void ElseAfterReturnCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
160   Options.store(Opts, WarnOnUnfixableStr, WarnOnUnfixable);
161   Options.store(Opts, WarnOnConditionVariablesStr, WarnOnConditionVariables);
162 }
163 
registerPPCallbacks(const SourceManager & SM,Preprocessor * PP,Preprocessor * ModuleExpanderPP)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 
registerMatchers(MatchFinder * Finder)171 void ElseAfterReturnCheck::registerMatchers(MatchFinder *Finder) {
172   const auto InterruptsControlFlow = stmt(anyOf(
173       returnStmt().bind(InterruptingStr), continueStmt().bind(InterruptingStr),
174       breakStmt().bind(InterruptingStr),
175       expr(ignoringImplicit(cxxThrowExpr().bind(InterruptingStr)))));
176   Finder->addMatcher(
177       compoundStmt(
178           forEach(ifStmt(unless(isConstexpr()),
179                          hasThen(stmt(
180                              anyOf(InterruptsControlFlow,
181                                    compoundStmt(has(InterruptsControlFlow))))),
182                          hasElse(stmt().bind("else")))
183                       .bind("if")))
184           .bind("cs"),
185       this);
186 }
187 
hasPreprocessorBranchEndBetweenLocations(const ElseAfterReturnCheck::ConditionalBranchMap & ConditionalBranchMap,const SourceManager & SM,SourceLocation StartLoc,SourceLocation EndLoc)188 static bool hasPreprocessorBranchEndBetweenLocations(
189     const ElseAfterReturnCheck::ConditionalBranchMap &ConditionalBranchMap,
190     const SourceManager &SM, SourceLocation StartLoc, SourceLocation EndLoc) {
191 
192   SourceLocation ExpandedStartLoc = SM.getExpansionLoc(StartLoc);
193   SourceLocation ExpandedEndLoc = SM.getExpansionLoc(EndLoc);
194   if (!SM.isWrittenInSameFile(ExpandedStartLoc, ExpandedEndLoc))
195     return false;
196 
197   // StartLoc and EndLoc expand to the same macro.
198   if (ExpandedStartLoc == ExpandedEndLoc)
199     return false;
200 
201   assert(ExpandedStartLoc < ExpandedEndLoc);
202 
203   auto Iter = ConditionalBranchMap.find(SM.getFileID(ExpandedEndLoc));
204 
205   if (Iter == ConditionalBranchMap.end() || Iter->getSecond().empty())
206     return false;
207 
208   const SmallVectorImpl<SourceRange> &ConditionalBranches = Iter->getSecond();
209 
210   assert(llvm::is_sorted(ConditionalBranches,
211                          [](const SourceRange &LHS, const SourceRange &RHS) {
212                            return LHS.getEnd() < RHS.getEnd();
213                          }));
214 
215   // First conditional block that ends after ExpandedStartLoc.
216   const auto *Begin =
217       llvm::lower_bound(ConditionalBranches, ExpandedStartLoc,
218                         [](const SourceRange &LHS, const SourceLocation &RHS) {
219                           return LHS.getEnd() < RHS;
220                         });
221   const auto *End = ConditionalBranches.end();
222   for (; Begin != End && Begin->getEnd() < ExpandedEndLoc; ++Begin)
223     if (Begin->getBegin() < ExpandedStartLoc)
224       return true;
225   return false;
226 }
227 
getControlFlowString(const Stmt & Stmt)228 static StringRef getControlFlowString(const Stmt &Stmt) {
229   if (isa<ReturnStmt>(Stmt))
230     return "return";
231   if (isa<ContinueStmt>(Stmt))
232     return "continue";
233   if (isa<BreakStmt>(Stmt))
234     return "break";
235   if (isa<CXXThrowExpr>(Stmt))
236     return "throw";
237   llvm_unreachable("Unknown control flow interruptor");
238 }
239 
check(const MatchFinder::MatchResult & Result)240 void ElseAfterReturnCheck::check(const MatchFinder::MatchResult &Result) {
241   const auto *If = Result.Nodes.getNodeAs<IfStmt>("if");
242   const auto *Else = Result.Nodes.getNodeAs<Stmt>("else");
243   const auto *OuterScope = Result.Nodes.getNodeAs<CompoundStmt>("cs");
244   const auto *Interrupt = Result.Nodes.getNodeAs<Stmt>(InterruptingStr);
245   SourceLocation ElseLoc = If->getElseLoc();
246 
247   if (hasPreprocessorBranchEndBetweenLocations(
248           PPConditionals, *Result.SourceManager, Interrupt->getBeginLoc(),
249           ElseLoc))
250     return;
251 
252   bool IsLastInScope = OuterScope->body_back() == If;
253   StringRef ControlFlowInterruptor = getControlFlowString(*Interrupt);
254 
255   if (!IsLastInScope && containsDeclInScope(Else)) {
256     if (WarnOnUnfixable) {
257       // Warn, but don't attempt an autofix.
258       diag(ElseLoc, WarningMessage) << ControlFlowInterruptor;
259     }
260     return;
261   }
262 
263   if (checkConditionVarUsageInElse(If) != nullptr) {
264     if (!WarnOnConditionVariables)
265       return;
266     if (IsLastInScope) {
267       // If the if statement is the last statement its enclosing statements
268       // scope, we can pull the decl out of the if statement.
269       DiagnosticBuilder Diag = diag(ElseLoc, WarningMessage)
270                                << ControlFlowInterruptor
271                                << SourceRange(ElseLoc);
272       if (checkInitDeclUsageInElse(If) != nullptr) {
273         Diag << tooling::fixit::createReplacement(
274                     SourceRange(If->getIfLoc()),
275                     (tooling::fixit::getText(*If->getInit(), *Result.Context) +
276                      llvm::StringRef("\n"))
277                         .str())
278              << tooling::fixit::createRemoval(If->getInit()->getSourceRange());
279       }
280       const DeclStmt *VDeclStmt = If->getConditionVariableDeclStmt();
281       const VarDecl *VDecl = If->getConditionVariable();
282       std::string Repl =
283           (tooling::fixit::getText(*VDeclStmt, *Result.Context) +
284            llvm::StringRef(";\n") +
285            tooling::fixit::getText(If->getIfLoc(), *Result.Context))
286               .str();
287       Diag << tooling::fixit::createReplacement(SourceRange(If->getIfLoc()),
288                                                 Repl)
289            << tooling::fixit::createReplacement(VDeclStmt->getSourceRange(),
290                                                 VDecl->getName());
291       removeElseAndBrackets(Diag, *Result.Context, Else, ElseLoc);
292     } else if (WarnOnUnfixable) {
293       // Warn, but don't attempt an autofix.
294       diag(ElseLoc, WarningMessage) << ControlFlowInterruptor;
295     }
296     return;
297   }
298 
299   if (checkInitDeclUsageInElse(If) != nullptr) {
300     if (!WarnOnConditionVariables)
301       return;
302     if (IsLastInScope) {
303       // If the if statement is the last statement its enclosing statements
304       // scope, we can pull the decl out of the if statement.
305       DiagnosticBuilder Diag = diag(ElseLoc, WarningMessage)
306                                << ControlFlowInterruptor
307                                << SourceRange(ElseLoc);
308       Diag << tooling::fixit::createReplacement(
309                   SourceRange(If->getIfLoc()),
310                   (tooling::fixit::getText(*If->getInit(), *Result.Context) +
311                    "\n" +
312                    tooling::fixit::getText(If->getIfLoc(), *Result.Context))
313                       .str())
314            << tooling::fixit::createRemoval(If->getInit()->getSourceRange());
315       removeElseAndBrackets(Diag, *Result.Context, Else, ElseLoc);
316     } else if (WarnOnUnfixable) {
317       // Warn, but don't attempt an autofix.
318       diag(ElseLoc, WarningMessage) << ControlFlowInterruptor;
319     }
320     return;
321   }
322 
323   DiagnosticBuilder Diag = diag(ElseLoc, WarningMessage)
324                            << ControlFlowInterruptor << SourceRange(ElseLoc);
325   removeElseAndBrackets(Diag, *Result.Context, Else, ElseLoc);
326 }
327 
328 } // namespace readability
329 } // namespace tidy
330 } // namespace clang
331