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