1 //===--- ElseAfterReturnCheck.cpp - clang-tidy-----------------------------===//
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
7 //===----------------------------------------------------------------------===//
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"
17 using namespace clang::ast_matchers
;
21 namespace readability
{
25 class PPConditionalCollector
: public PPCallbacks
{
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
))
34 SmallVectorImpl
<SourceRange
> &Collection
= Collections
[SM
.getFileID(Loc
)];
35 assert(Collection
.empty() || Collection
.back().getEnd() < Loc
);
36 Collection
.emplace_back(IfLoc
, Loc
);
40 ElseAfterReturnCheck::ConditionalBranchMap
&Collections
;
41 const SourceManager
&SM
;
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";
51 static const DeclRefExpr
*findUsage(const Stmt
*Node
, int64_t DeclIdentifier
) {
54 if (const auto *DeclRef
= dyn_cast
<DeclRefExpr
>(Node
)) {
55 if (DeclRef
->getDecl()->getID() == DeclIdentifier
)
58 for (const Stmt
*ChildNode
: Node
->children()) {
59 if (const DeclRefExpr
*Result
= findUsage(ChildNode
, DeclIdentifier
))
66 static const DeclRefExpr
*
67 findUsageRange(const Stmt
*Node
,
68 const llvm::ArrayRef
<int64_t> &DeclIdentifiers
) {
71 if (const auto *DeclRef
= dyn_cast
<DeclRefExpr
>(Node
)) {
72 if (llvm::is_contained(DeclIdentifiers
, DeclRef
->getDecl()->getID()))
75 for (const Stmt
*ChildNode
: Node
->children()) {
76 if (const DeclRefExpr
*Result
=
77 findUsageRange(ChildNode
, DeclIdentifiers
))
84 static const DeclRefExpr
*checkInitDeclUsageInElse(const IfStmt
*If
) {
85 const auto *InitDeclStmt
= dyn_cast_or_null
<DeclStmt
>(If
->getInit());
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());
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());
98 return findUsageRange(If
->getElse(), DeclIdentifiers
);
101 static const DeclRefExpr
*checkConditionVarUsageInElse(const IfStmt
*If
) {
102 if (const VarDecl
*CondVar
= If
->getConditionVariable())
103 return findUsage(If
->getElse(), CondVar
->getID());
107 static bool containsDeclInScope(const Stmt
*Node
) {
108 if (isa
<DeclStmt
>(Node
))
110 if (const auto *Compound
= dyn_cast
<CompoundStmt
>(Node
))
111 return llvm::any_of(Compound
->body(), [](const Stmt
*SubNode
) {
112 return isa
<DeclStmt
>(SubNode
);
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
);
122 auto TokLen
= [&](SourceLocation Loc
) {
123 return Lexer::MeasureTokenLength(Loc
, Context
.getSourceManager(),
124 Context
.getLangOpts());
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);
135 llvm::StringRef Repl
= Lexer::getSourceText(
136 CharSourceRange::getTokenRange(RangeStart
, RangeEnd
),
137 Context
.getSourceManager(), Context
.getLangOpts());
138 Diag
<< tooling::fixit::createReplacement(CS
->getSourceRange(), Repl
);
140 SourceLocation ElseExpandedLoc
= Remap(ElseLoc
);
141 SourceLocation EndLoc
= Remap(Else
->getEndLoc());
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
);
152 ElseAfterReturnCheck::ElseAfterReturnCheck(StringRef Name
,
153 ClangTidyContext
*Context
)
154 : ClangTidyCheck(Name
, Context
),
155 WarnOnUnfixable(Options
.get(WarnOnUnfixableStr
, true)),
156 WarnOnConditionVariables(Options
.get(WarnOnConditionVariablesStr
, true)) {
159 void ElseAfterReturnCheck::storeOptions(ClangTidyOptions::OptionMap
&Opts
) {
160 Options
.store(Opts
, WarnOnUnfixableStr
, WarnOnUnfixable
);
161 Options
.store(Opts
, WarnOnConditionVariablesStr
, WarnOnConditionVariables
);
164 void ElseAfterReturnCheck::registerPPCallbacks(const SourceManager
&SM
,
166 Preprocessor
*ModuleExpanderPP
) {
168 std::make_unique
<PPConditionalCollector
>(this->PPConditionals
, SM
));
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
)));
177 forEach(ifStmt(unless(isConstexpr()),
179 anyOf(InterruptsControlFlow
,
180 compoundStmt(has(InterruptsControlFlow
))))),
181 hasElse(stmt().bind("else")))
187 static bool hasPreprocessorBranchEndBetweenLocations(
188 const ElseAfterReturnCheck::ConditionalBranchMap
&ConditionalBranchMap
,
189 const SourceManager
&SM
, SourceLocation StartLoc
, SourceLocation EndLoc
) {
191 SourceLocation ExpandedStartLoc
= SM
.getExpansionLoc(StartLoc
);
192 SourceLocation ExpandedEndLoc
= SM
.getExpansionLoc(EndLoc
);
193 if (!SM
.isWrittenInSameFile(ExpandedStartLoc
, ExpandedEndLoc
))
196 // StartLoc and EndLoc expand to the same macro.
197 if (ExpandedStartLoc
== ExpandedEndLoc
)
200 assert(ExpandedStartLoc
< ExpandedEndLoc
);
202 auto Iter
= ConditionalBranchMap
.find(SM
.getFileID(ExpandedEndLoc
));
204 if (Iter
== ConditionalBranchMap
.end() || Iter
->getSecond().empty())
207 const SmallVectorImpl
<SourceRange
> &ConditionalBranches
= Iter
->getSecond();
209 assert(llvm::is_sorted(ConditionalBranches
,
210 [](const SourceRange
&LHS
, const SourceRange
&RHS
) {
211 return LHS
.getEnd() < RHS
.getEnd();
214 // First conditional block that ends after ExpandedStartLoc.
216 llvm::lower_bound(ConditionalBranches
, ExpandedStartLoc
,
217 [](const SourceRange
&LHS
, const SourceLocation
&RHS
) {
218 return LHS
.getEnd() < RHS
;
220 const auto *End
= ConditionalBranches
.end();
221 for (; Begin
!= End
&& Begin
->getEnd() < ExpandedEndLoc
; ++Begin
)
222 if (Begin
->getBegin() < ExpandedStartLoc
)
227 static StringRef
getControlFlowString(const Stmt
&Stmt
) {
228 if (isa
<ReturnStmt
>(Stmt
))
230 if (isa
<ContinueStmt
>(Stmt
))
232 if (isa
<BreakStmt
>(Stmt
))
234 if (isa
<CXXThrowExpr
>(Stmt
))
236 llvm_unreachable("Unknown control flow interruptor");
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();
246 if (hasPreprocessorBranchEndBetweenLocations(
247 PPConditionals
, *Result
.SourceManager
, Interrupt
->getBeginLoc(),
251 bool IsLastInScope
= OuterScope
->body_back() == If
;
252 StringRef ControlFlowInterruptor
= getControlFlowString(*Interrupt
);
254 if (!IsLastInScope
&& containsDeclInScope(Else
)) {
255 if (WarnOnUnfixable
) {
256 // Warn, but don't attempt an autofix.
257 diag(ElseLoc
, WarningMessage
) << ControlFlowInterruptor
;
262 if (checkConditionVarUsageInElse(If
) != nullptr) {
263 if (!WarnOnConditionVariables
)
266 // If the if statement is the last statement of 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"))
277 << tooling::fixit::createRemoval(If
->getInit()->getSourceRange());
279 const DeclStmt
*VDeclStmt
= If
->getConditionVariableDeclStmt();
280 const VarDecl
*VDecl
= If
->getConditionVariable();
282 (tooling::fixit::getText(*VDeclStmt
, *Result
.Context
) +
283 llvm::StringRef(";\n") +
284 tooling::fixit::getText(If
->getIfLoc(), *Result
.Context
))
286 Diag
<< tooling::fixit::createReplacement(SourceRange(If
->getIfLoc()),
288 << tooling::fixit::createReplacement(VDeclStmt
->getSourceRange(),
290 removeElseAndBrackets(Diag
, *Result
.Context
, Else
, ElseLoc
);
291 } else if (WarnOnUnfixable
) {
292 // Warn, but don't attempt an autofix.
293 diag(ElseLoc
, WarningMessage
) << ControlFlowInterruptor
;
298 if (checkInitDeclUsageInElse(If
) != nullptr) {
299 if (!WarnOnConditionVariables
)
302 // If the if statement is the last statement of 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
) +
311 tooling::fixit::getText(If
->getIfLoc(), *Result
.Context
))
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
;
322 DiagnosticBuilder Diag
= diag(ElseLoc
, WarningMessage
)
323 << ControlFlowInterruptor
<< SourceRange(ElseLoc
);
324 removeElseAndBrackets(Diag
, *Result
.Context
, Else
, ElseLoc
);
327 } // namespace readability