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