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