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