Skip to content

Commit 617e8e5

Browse files
committed
[clang-tidy] ElseAfterReturn check wont suggest fixes if preprocessor branches are involved
Consider this code: ``` if (Cond) { #ifdef X_SUPPORTED X(); #else return; #endif } else { Y(); } Z();``` In this example, if `X_SUPPORTED` is not defined, currently we'll get a warning from the else-after-return check. However If we apply that fix, and then the code is recompiled with `X_SUPPORTED` defined, we have inadvertently changed the behaviour of the if statement due to the else being removed. Code flow when `Cond` is `true` will be: ``` X(); Y(); Z();``` where as before the fix it was: ``` X(); Z();``` This patch adds checks that guard against `#endif` directives appearing between the control flow interrupter and the else and not applying the fix if they are detected. Reviewed By: aaron.ballman Differential Revision: https://reviews.llvm.org/D91485
1 parent 1ac9b54 commit 617e8e5

File tree

4 files changed

+211
-17
lines changed

4 files changed

+211
-17
lines changed

clang-tools-extra/clang-tidy/readability/ElseAfterReturnCheck.cpp

Lines changed: 96 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include "clang/AST/ASTContext.h"
1111
#include "clang/ASTMatchers/ASTMatchFinder.h"
1212
#include "clang/Lex/Lexer.h"
13+
#include "clang/Lex/Preprocessor.h"
1314
#include "clang/Tooling/FixIt.h"
1415
#include "llvm/ADT/SmallVector.h"
1516

@@ -19,10 +20,30 @@ namespace clang {
1920
namespace tidy {
2021
namespace readability {
2122

22-
static const char ReturnStr[] = "return";
23-
static const char ContinueStr[] = "continue";
24-
static const char BreakStr[] = "break";
25-
static const char ThrowStr[] = "throw";
23+
namespace {
24+
25+
class PPConditionalCollector : public PPCallbacks {
26+
public:
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))
33+
return;
34+
SmallVectorImpl<SourceRange> &Collection = Collections[SM.getFileID(Loc)];
35+
assert(Collection.empty() || Collection.back().getEnd() < Loc);
36+
Collection.emplace_back(IfLoc, Loc);
37+
}
38+
39+
private:
40+
ElseAfterReturnCheck::ConditionalBranchMap &Collections;
41+
const SourceManager &SM;
42+
};
43+
44+
} // namespace
45+
46+
static const char InterruptingStr[] = "interrupting";
2647
static const char WarningMessage[] = "do not use 'else' after '%0'";
2748
static const char WarnOnUnfixableStr[] = "WarnOnUnfixable";
2849
static const char WarnOnConditionVariablesStr[] = "WarnOnConditionVariables";
@@ -140,11 +161,18 @@ void ElseAfterReturnCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
140161
Options.store(Opts, WarnOnConditionVariablesStr, WarnOnConditionVariables);
141162
}
142163

164+
void ElseAfterReturnCheck::registerPPCallbacks(const SourceManager &SM,
165+
Preprocessor *PP,
166+
Preprocessor *ModuleExpanderPP) {
167+
PP->addPPCallbacks(
168+
std::make_unique<PPConditionalCollector>(this->PPConditionals, SM));
169+
}
170+
143171
void ElseAfterReturnCheck::registerMatchers(MatchFinder *Finder) {
144-
const auto InterruptsControlFlow =
145-
stmt(anyOf(returnStmt().bind(ReturnStr), continueStmt().bind(ContinueStr),
146-
breakStmt().bind(BreakStr),
147-
expr(ignoringImplicit(cxxThrowExpr().bind(ThrowStr)))));
172+
const auto InterruptsControlFlow = stmt(anyOf(
173+
returnStmt().bind(InterruptingStr), continueStmt().bind(InterruptingStr),
174+
breakStmt().bind(InterruptingStr),
175+
expr(ignoringImplicit(cxxThrowExpr().bind(InterruptingStr)))));
148176
Finder->addMatcher(
149177
compoundStmt(
150178
forEach(ifStmt(unless(isConstexpr()),
@@ -157,21 +185,72 @@ void ElseAfterReturnCheck::registerMatchers(MatchFinder *Finder) {
157185
this);
158186
}
159187

188+
static bool hasPreprocessorBranchEndBetweenLocations(
189+
const ElseAfterReturnCheck::ConditionalBranchMap &ConditionalBranchMap,
190+
const SourceManager &SM, SourceLocation StartLoc, SourceLocation EndLoc) {
191+
192+
SourceLocation ExpandedStartLoc = SM.getExpansionLoc(StartLoc);
193+
SourceLocation ExpandedEndLoc = SM.getExpansionLoc(EndLoc);
194+
if (!SM.isWrittenInSameFile(ExpandedStartLoc, ExpandedEndLoc))
195+
return false;
196+
197+
// StartLoc and EndLoc expand to the same macro.
198+
if (ExpandedStartLoc == ExpandedEndLoc)
199+
return false;
200+
201+
assert(ExpandedStartLoc < ExpandedEndLoc);
202+
203+
auto Iter = ConditionalBranchMap.find(SM.getFileID(ExpandedEndLoc));
204+
205+
if (Iter == ConditionalBranchMap.end() || Iter->getSecond().empty())
206+
return false;
207+
208+
const SmallVectorImpl<SourceRange> &ConditionalBranches = Iter->getSecond();
209+
210+
assert(llvm::is_sorted(ConditionalBranches,
211+
[](const SourceRange &LHS, const SourceRange &RHS) {
212+
return LHS.getEnd() < RHS.getEnd();
213+
}));
214+
215+
// First conditional block that ends after ExpandedStartLoc.
216+
const auto *Begin =
217+
llvm::lower_bound(ConditionalBranches, ExpandedStartLoc,
218+
[](const SourceRange &LHS, const SourceLocation &RHS) {
219+
return LHS.getEnd() < RHS;
220+
});
221+
const auto *End = ConditionalBranches.end();
222+
for (; Begin != End && Begin->getEnd() < ExpandedEndLoc; ++Begin)
223+
if (Begin->getBegin() < ExpandedStartLoc)
224+
return true;
225+
return false;
226+
}
227+
228+
static StringRef getControlFlowString(const Stmt &Stmt) {
229+
if (isa<ReturnStmt>(Stmt))
230+
return "return";
231+
if (isa<ContinueStmt>(Stmt))
232+
return "continue";
233+
if (isa<BreakStmt>(Stmt))
234+
return "break";
235+
if (isa<CXXThrowExpr>(Stmt))
236+
return "throw";
237+
llvm_unreachable("Unknown control flow interruptor");
238+
}
239+
160240
void ElseAfterReturnCheck::check(const MatchFinder::MatchResult &Result) {
161241
const auto *If = Result.Nodes.getNodeAs<IfStmt>("if");
162242
const auto *Else = Result.Nodes.getNodeAs<Stmt>("else");
163243
const auto *OuterScope = Result.Nodes.getNodeAs<CompoundStmt>("cs");
164-
165-
bool IsLastInScope = OuterScope->body_back() == If;
244+
const auto *Interrupt = Result.Nodes.getNodeAs<Stmt>(InterruptingStr);
166245
SourceLocation ElseLoc = If->getElseLoc();
167246

168-
auto ControlFlowInterruptor = [&]() -> llvm::StringRef {
169-
for (llvm::StringRef BindingName :
170-
{ReturnStr, ContinueStr, BreakStr, ThrowStr})
171-
if (Result.Nodes.getNodeAs<Stmt>(BindingName))
172-
return BindingName;
173-
return {};
174-
}();
247+
if (hasPreprocessorBranchEndBetweenLocations(
248+
PPConditionals, *Result.SourceManager, Interrupt->getBeginLoc(),
249+
ElseLoc))
250+
return;
251+
252+
bool IsLastInScope = OuterScope->body_back() == If;
253+
StringRef ControlFlowInterruptor = getControlFlowString(*Interrupt);
175254

176255
if (!IsLastInScope && containsDeclInScope(Else)) {
177256
if (WarnOnUnfixable) {

clang-tools-extra/clang-tidy/readability/ElseAfterReturnCheck.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_ELSEAFTERRETURNCHECK_H
1111

1212
#include "../ClangTidyCheck.h"
13+
#include "llvm/ADT/DenseMap.h"
1314

1415
namespace clang {
1516
namespace tidy {
@@ -23,12 +24,18 @@ class ElseAfterReturnCheck : public ClangTidyCheck {
2324
ElseAfterReturnCheck(StringRef Name, ClangTidyContext *Context);
2425

2526
void storeOptions(ClangTidyOptions::OptionMap &Opts) override;
27+
void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP,
28+
Preprocessor *ModuleExpanderPP) override;
2629
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
2730
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
2831

32+
using ConditionalBranchMap =
33+
llvm::DenseMap<FileID, SmallVector<SourceRange, 1>>;
34+
2935
private:
3036
const bool WarnOnUnfixable;
3137
const bool WarnOnConditionVariables;
38+
ConditionalBranchMap PPConditionals;
3239
};
3340

3441
} // namespace readability
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// RUN: clang-tidy %s -checks=-*,readability-else-after-return
2+
3+
// We aren't concerned about the output here, just want to ensure clang-tidy doesn't crash.
4+
void foo() {
5+
#if 1
6+
if (true) {
7+
return;
8+
#else
9+
{
10+
#endif
11+
} else {
12+
return;
13+
}
14+
15+
if (true) {
16+
#if 1
17+
return;
18+
} else {
19+
#endif
20+
return;
21+
}
22+
}

clang-tools-extra/test/clang-tidy/checkers/readability-else-after-return.cpp

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,3 +226,89 @@ void test_B44745() {
226226
}
227227
return;
228228
}
229+
230+
void testPPConditionals() {
231+
232+
// These cases the return isn't inside the conditional so diagnose as normal.
233+
if (true) {
234+
return;
235+
#if 1
236+
#endif
237+
} else {
238+
// CHECK-MESSAGES: :[[@LINE-1]]:5: warning: do not use 'else' after 'return'
239+
return;
240+
}
241+
if (true) {
242+
#if 1
243+
#endif
244+
return;
245+
} else {
246+
// CHECK-MESSAGES: :[[@LINE-1]]:5: warning: do not use 'else' after 'return'
247+
return;
248+
}
249+
250+
// No return here in the AST, no special handling needed.
251+
if (true) {
252+
#if 0
253+
return;
254+
#endif
255+
} else {
256+
return;
257+
}
258+
259+
// Return here is inside a preprocessor conditional block, ignore this case.
260+
if (true) {
261+
#if 1
262+
return;
263+
#endif
264+
} else {
265+
return;
266+
}
267+
268+
// These cases, same as above but with an #else block.
269+
if (true) {
270+
#if 1
271+
return;
272+
#else
273+
#endif
274+
} else {
275+
return;
276+
}
277+
if (true) {
278+
#if 0
279+
#else
280+
return;
281+
#endif
282+
} else {
283+
return;
284+
}
285+
286+
// Ensure it can handle macros.
287+
#define RETURN return
288+
if (true) {
289+
#if 1
290+
RETURN;
291+
#endif
292+
} else {
293+
return;
294+
}
295+
#define ELSE else
296+
if (true) {
297+
#if 1
298+
return;
299+
#endif
300+
}
301+
ELSE {
302+
return;
303+
}
304+
305+
// Whole statement is in a conditional block so diagnose as normal.
306+
#if 1
307+
if (true) {
308+
return;
309+
} else {
310+
// CHECK-MESSAGES: :[[@LINE-1]]:5: warning: do not use 'else' after 'return'
311+
return;
312+
}
313+
#endif
314+
}

0 commit comments

Comments
 (0)