clang-tools 10.0.0

ElseAfterReturnCheck.cpp
Go to the documentation of this file.
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/Tooling/FixIt.h"
13 #include "llvm/ADT/SmallVector.h"
14
15 using namespace clang::ast_matchers;
16
17 namespace clang {
18 namespace tidy {
19 namespace readability {
20
21 namespace {
22 static const char ReturnStr[] = "return";
23 static const char ContinueStr[] = "continue";
24 static const char BreakStr[] = "break";
25 static const char ThrowStr[] = "throw";
26 static const char WarningMessage[] = "do not use 'else' after '%0'";
27 static const char WarnOnUnfixableStr[] = "WarnOnUnfixable";
28
29 const DeclRefExpr *findUsage(const Stmt *Node, int64_t DeclIdentifier) {
30 if (!Node)
31 return nullptr;
32 if (const auto *DeclRef = dyn_cast<DeclRefExpr>(Node)) {
33 if (DeclRef->getDecl()->getID() == DeclIdentifier) {
34 return DeclRef;
35 }
36 } else {
37 for (const Stmt *ChildNode : Node->children()) {
38 if (const DeclRefExpr *Result = findUsage(ChildNode, DeclIdentifier)) {
39 return Result;
40 }
41 }
42 }
43 return nullptr;
44 }
45
46 const DeclRefExpr *
47 findUsageRange(const Stmt *Node,
48 const llvm::iterator_range<int64_t *> &DeclIdentifiers) {
49 if (!Node)
50 return nullptr;
51 if (const auto *DeclRef = dyn_cast<DeclRefExpr>(Node)) {
52 if (llvm::is_contained(DeclIdentifiers, DeclRef->getDecl()->getID())) {
53 return DeclRef;
54 }
55 } else {
56 for (const Stmt *ChildNode : Node->children()) {
57 if (const DeclRefExpr *Result =
58 findUsageRange(ChildNode, DeclIdentifiers)) {
59 return Result;
60 }
61 }
62 }
63 return nullptr;
64 }
65
66 const DeclRefExpr *checkInitDeclUsageInElse(const IfStmt *If) {
67 const auto *InitDeclStmt = dyn_cast_or_null<DeclStmt>(If->getInit());
68 if (!InitDeclStmt)
69 return nullptr;
70 if (InitDeclStmt->isSingleDecl()) {
71 const Decl *InitDecl = InitDeclStmt->getSingleDecl();
72 assert(isa<VarDecl>(InitDecl) && "SingleDecl must be a VarDecl");
73 return findUsage(If->getElse(), InitDecl->getID());
74 }
75 llvm::SmallVector<int64_t, 4> DeclIdentifiers;
76 for (const Decl *ChildDecl : InitDeclStmt->decls()) {
77 assert(isa<VarDecl>(ChildDecl) && "Init Decls must be a VarDecl");
78 DeclIdentifiers.push_back(ChildDecl->getID());
79 }
80 return findUsageRange(If->getElse(), DeclIdentifiers);
81 }
82
83 const DeclRefExpr *checkConditionVarUsageInElse(const IfStmt *If) {
84 const VarDecl *CondVar = If->getConditionVariable();
85 return CondVar != nullptr ? findUsage(If->getElse(), CondVar->getID())
86 : nullptr;
87 }
88
89 bool containsDeclInScope(const Stmt *Node) {
90 if (isa<DeclStmt>(Node)) {
91 return true;
92 }
93 if (const auto *Compound = dyn_cast<CompoundStmt>(Node)) {
94 return llvm::any_of(Compound->body(), [](const Stmt *SubNode) {
95 return isa<DeclStmt>(SubNode);
96 });
97 }
98 return false;
99 }
100
101 void removeElseAndBrackets(DiagnosticBuilder &Diag, ASTContext &Context,
102 const Stmt *Else, SourceLocation ElseLoc) {
103 auto Remap = [&](SourceLocation Loc) {
104 return Context.getSourceManager().getExpansionLoc(Loc);
105 };
106 auto TokLen = [&](SourceLocation Loc) {
107 return Lexer::MeasureTokenLength(Loc, Context.getSourceManager(),
108 Context.getLangOpts());
109 };
110
111 if (const auto *CS = dyn_cast<CompoundStmt>(Else)) {
112 Diag << tooling::fixit::createRemoval(ElseLoc);
113 SourceLocation LBrace = CS->getLBracLoc();
114 SourceLocation RBrace = CS->getRBracLoc();
115 SourceLocation RangeStart =
116 Remap(LBrace).getLocWithOffset(TokLen(LBrace) + 1);
117 SourceLocation RangeEnd = Remap(RBrace).getLocWithOffset(-1);
118
119 llvm::StringRef Repl = Lexer::getSourceText(
120 CharSourceRange::getTokenRange(RangeStart, RangeEnd),
121 Context.getSourceManager(), Context.getLangOpts());
122 Diag << tooling::fixit::createReplacement(CS->getSourceRange(), Repl);
123 } else {
124 SourceLocation ElseExpandedLoc = Remap(ElseLoc);
125 SourceLocation EndLoc = Remap(Else->getEndLoc());
126
127 llvm::StringRef Repl = Lexer::getSourceText(
128 CharSourceRange::getTokenRange(
129 ElseExpandedLoc.getLocWithOffset(TokLen(ElseLoc) + 1), EndLoc),
130 Context.getSourceManager(), Context.getLangOpts());
131 Diag << tooling::fixit::createReplacement(
132 SourceRange(ElseExpandedLoc, EndLoc), Repl);
133 }
134 }
135 } // namespace
136
137 ElseAfterReturnCheck::ElseAfterReturnCheck(StringRef Name,
138 ClangTidyContext *Context)
139 : ClangTidyCheck(Name, Context),
140 WarnOnUnfixable(Options.get(WarnOnUnfixableStr, true)) {}
141
142 void ElseAfterReturnCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
143 Options.store(Opts, WarnOnUnfixableStr, WarnOnUnfixable);
144 }
145
146 void ElseAfterReturnCheck::registerMatchers(MatchFinder *Finder) {
147 const auto InterruptsControlFlow =
148 stmt(anyOf(returnStmt().bind(ReturnStr), continueStmt().bind(ContinueStr),
149 breakStmt().bind(BreakStr),
150 expr(ignoringImplicit(cxxThrowExpr().bind(ThrowStr)))));
151 Finder->addMatcher(
152 compoundStmt(
153 forEach(ifStmt(unless(isConstexpr()),
154 hasThen(stmt(
155 anyOf(InterruptsControlFlow,
156 compoundStmt(has(InterruptsControlFlow))))),
157 hasElse(stmt().bind("else")))
158 .bind("if")))
159 .bind("cs"),
160 this);
161 }
162
163 void ElseAfterReturnCheck::check(const MatchFinder::MatchResult &Result) {
164 const auto *If = Result.Nodes.getNodeAs<IfStmt>("if");
165 const auto *Else = Result.Nodes.getNodeAs<Stmt>("else");
166 const auto *OuterScope = Result.Nodes.getNodeAs<CompoundStmt>("cs");
167
168 bool IsLastInScope = OuterScope->body_back() == If;
169 SourceLocation ElseLoc = If->getElseLoc();
170
171 auto ControlFlowInterruptor = [&]() -> llvm::StringRef {
172 for (llvm::StringRef BindingName :
173 {ReturnStr, ContinueStr, BreakStr, ThrowStr})
174 if (Result.Nodes.getNodeAs<Stmt>(BindingName))
175 return BindingName;
176 return {};
177 }();
178
179 if (!IsLastInScope && containsDeclInScope(Else)) {
180 if (WarnOnUnfixable) {
181 // Warn, but don't attempt an autofix.
182 diag(ElseLoc, WarningMessage) << ControlFlowInterruptor;
183 }
184 return;
185 }
186
187 if (checkConditionVarUsageInElse(If) != nullptr) {
188 if (IsLastInScope) {
189 // If the if statement is the last statement its enclosing statements
190 // scope, we can pull the decl out of the if statement.
191 DiagnosticBuilder Diag =
192 diag(ElseLoc, WarningMessage, clang::DiagnosticIDs::Level::Remark)
193 << ControlFlowInterruptor;
194 if (checkInitDeclUsageInElse(If) != nullptr) {
195 Diag << tooling::fixit::createReplacement(
196 SourceRange(If->getIfLoc()),
197 (tooling::fixit::getText(*If->getInit(), *Result.Context) +
198 llvm::StringRef("\n"))
199 .str())
200 << tooling::fixit::createRemoval(If->getInit()->getSourceRange());
201 }
202 const DeclStmt *VDeclStmt = If->getConditionVariableDeclStmt();
203 const VarDecl *VDecl = If->getConditionVariable();
204 std::string Repl =
205 (tooling::fixit::getText(*VDeclStmt, *Result.Context) +
206 llvm::StringRef(";\n") +
207 tooling::fixit::getText(If->getIfLoc(), *Result.Context))
208 .str();
209 Diag << tooling::fixit::createReplacement(SourceRange(If->getIfLoc()),
210 Repl)
211 << tooling::fixit::createReplacement(VDeclStmt->getSourceRange(),
212 VDecl->getName());
213 removeElseAndBrackets(Diag, *Result.Context, Else, ElseLoc);
214 } else if (WarnOnUnfixable) {
215 // Warn, but don't attempt an autofix.
216 diag(ElseLoc, WarningMessage) << ControlFlowInterruptor;
217 }
218 return;
219 }
220
221 if (checkInitDeclUsageInElse(If) != nullptr) {
222 if (IsLastInScope) {
223 // If the if statement is the last statement its enclosing statements
224 // scope, we can pull the decl out of the if statement.
225 DiagnosticBuilder Diag = diag(ElseLoc, WarningMessage)
226 << ControlFlowInterruptor;
227 Diag << tooling::fixit::createReplacement(
228 SourceRange(If->getIfLoc()),
229 (tooling::fixit::getText(*If->getInit(), *Result.Context) +
230 "\n" +
231 tooling::fixit::getText(If->getIfLoc(), *Result.Context))
232 .str())
233 << tooling::fixit::createRemoval(If->getInit()->getSourceRange());
234 removeElseAndBrackets(Diag, *Result.Context, Else, ElseLoc);
235 } else if (WarnOnUnfixable) {
236 // Warn, but don't attempt an autofix.
237 diag(ElseLoc, WarningMessage) << ControlFlowInterruptor;
238 }
239 return;
240 }
241
242 DiagnosticBuilder Diag = diag(ElseLoc, WarningMessage)
243 << ControlFlowInterruptor;
244 removeElseAndBrackets(Diag, *Result.Context, Else, ElseLoc);
245 }
246
247 } // namespace readability
248 } // namespace tidy
249 } // namespace clang
Loc
SourceLocation Loc
&#39;#&#39; location in the include directive
Definition: IncludeOrderCheck.cpp:37
clang::ast_matchers
Definition: AbseilMatcher.h:14
Decl
const FunctionDecl * Decl
Definition: AvoidBindCheck.cpp:93
clang::tidy::readability::ElseAfterReturnCheck::check
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
ClangTidyChecks that register ASTMatchers should do the actual work in here.
Definition: ElseAfterReturnCheck.cpp:163
clang::tidy::ClangTidyCheck
Base class for all clang-tidy checks.
Definition: ClangTidyCheck.h:47
clang::tidy::ClangTidyCheck::Options
OptionsView Options
Definition: ClangTidyCheck.h:184
clang::tidy::readability::ElseAfterReturnCheck::registerMatchers
void registerMatchers(ast_matchers::MatchFinder *Finder) override
Override this to register AST matchers with Finder.
Definition: ElseAfterReturnCheck.cpp:146
clang::tidy::ClangTidyCheck::OptionsView::store
void store(ClangTidyOptions::OptionMap &Options, StringRef LocalName, StringRef Value) const
Stores an option with the check-local name LocalName with string value Value to Options.
Definition: ClangTidyCheck.cpp:58
Name
static constexpr llvm::StringLiteral Name
Definition: UppercaseLiteralSuffixCheck.cpp:27
ElseAfterReturnCheck.h
clang::tidy::ClangTidyOptions::OptionMap
std::map< std::string, std::string > OptionMap
Definition: ClangTidyOptions.h:97
clang::clangd::getTokenRange
llvm::Optional< Range > getTokenRange(const SourceManager &SM, const LangOptions &LangOpts, SourceLocation TokLoc)
Returns the taken range at TokLoc.
Definition: SourceCode.cpp:227
clang::tidy::readability::ElseAfterReturnCheck::storeOptions
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
Should store all options supported by this check with their current values or default values for opti...
Definition: ElseAfterReturnCheck.cpp:142
clang
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
Definition: ApplyReplacements.h:27
clang::tidy::ClangTidyContext
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
Definition: ClangTidyDiagnosticConsumer.h:77
DeclRef
const DeclRefExpr * DeclRef
Definition: UseAfterMoveCheck.cpp:29
clang::tidy::ClangTidyCheck::diag
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check&#39;s name.
Definition: ClangTidyCheck.cpp:21