Skip to content

Commit 2ec6b3b

Browse files
authored
[Clang-Tidy] Handle nested-name-specifier in "llvm-prefer-isa-or-dyn-cast-in-conditionals" (#155982)
Use `declRefExpr` matcher to match callee so that we can get the `SourceRange` of the identifier of the callee for replacement. Drive-by changes: - Use `hasConditionVariableStatement` matcher to handle `if` statements with init-statement. - Support `for` loops. Fixes #154790
1 parent 2ed3f49 commit 2ec6b3b

File tree

3 files changed

+135
-64
lines changed

3 files changed

+135
-64
lines changed

clang-tools-extra/clang-tidy/llvm/PreferIsaOrDynCastInConditionalsCheck.cpp

Lines changed: 64 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include "clang/AST/ASTContext.h"
1212
#include "clang/ASTMatchers/ASTMatchFinder.h"
1313
#include "clang/Lex/Lexer.h"
14+
#include "llvm/Support/FormatVariadic.h"
1415

1516
using namespace clang::ast_matchers;
1617

@@ -22,105 +23,104 @@ AST_MATCHER(Expr, isMacroID) { return Node.getExprLoc().isMacroID(); }
2223

2324
void PreferIsaOrDynCastInConditionalsCheck::registerMatchers(
2425
MatchFinder *Finder) {
25-
auto Condition = hasCondition(implicitCastExpr(has(
26-
callExpr(unless(isMacroID()), unless(cxxMemberCallExpr()),
27-
anyOf(callee(namedDecl(hasName("cast"))),
28-
callee(namedDecl(hasName("dyn_cast")).bind("dyn_cast"))))
29-
.bind("call"))));
30-
31-
auto Any = anyOf(
32-
has(declStmt(containsDeclaration(
33-
0, varDecl(hasInitializer(callExpr(unless(isMacroID()),
34-
unless(cxxMemberCallExpr()),
35-
callee(namedDecl(hasName("cast"))))
36-
.bind("assign")))))),
37-
Condition);
38-
39-
auto CallExpression =
26+
auto AnyCalleeName = [](ArrayRef<StringRef> CalleeName) {
27+
return allOf(unless(isMacroID()), unless(cxxMemberCallExpr()),
28+
callee(expr(ignoringImpCasts(
29+
declRefExpr(to(namedDecl(hasAnyName(CalleeName))),
30+
hasAnyTemplateArgumentLoc(anything()))
31+
.bind("callee")))));
32+
};
33+
34+
auto CondExpr = hasCondition(implicitCastExpr(
35+
has(callExpr(AnyCalleeName({"cast", "dyn_cast"})).bind("cond"))));
36+
37+
auto CondExprOrCondVar =
38+
anyOf(hasConditionVariableStatement(containsDeclaration(
39+
0, varDecl(hasInitializer(callExpr(AnyCalleeName({"cast"}))))
40+
.bind("var"))),
41+
CondExpr);
42+
43+
auto CallWithBindedArg =
4044
callExpr(
41-
42-
unless(isMacroID()), unless(cxxMemberCallExpr()),
43-
callee(namedDecl(hasAnyName("isa", "cast", "cast_or_null", "dyn_cast",
44-
"dyn_cast_or_null"))
45-
.bind("func")),
45+
AnyCalleeName(
46+
{"isa", "cast", "cast_or_null", "dyn_cast", "dyn_cast_or_null"}),
4647
hasArgument(0, mapAnyOf(declRefExpr, cxxMemberCallExpr).bind("arg")))
4748
.bind("rhs");
4849

4950
Finder->addMatcher(
50-
traverse(
51-
TK_AsIs,
52-
stmt(anyOf(
53-
ifStmt(Any), whileStmt(Any), doStmt(Condition),
54-
binaryOperator(unless(isExpansionInFileMatching(
55-
"llvm/include/llvm/Support/Casting.h")),
56-
hasOperatorName("&&"),
57-
hasLHS(implicitCastExpr().bind("lhs")),
58-
hasRHS(anyOf(implicitCastExpr(has(CallExpression)),
59-
CallExpression)))
60-
.bind("and")))),
51+
stmt(anyOf(ifStmt(CondExprOrCondVar), forStmt(CondExprOrCondVar),
52+
whileStmt(CondExprOrCondVar), doStmt(CondExpr),
53+
binaryOperator(unless(isExpansionInFileMatching(
54+
"llvm/include/llvm/Support/Casting.h")),
55+
hasOperatorName("&&"),
56+
hasLHS(implicitCastExpr().bind("lhs")),
57+
hasRHS(ignoringImpCasts(CallWithBindedArg)))
58+
.bind("and"))),
6159
this);
6260
}
6361

6462
void PreferIsaOrDynCastInConditionalsCheck::check(
6563
const MatchFinder::MatchResult &Result) {
66-
if (const auto *MatchedDecl = Result.Nodes.getNodeAs<CallExpr>("assign")) {
67-
SourceLocation StartLoc = MatchedDecl->getCallee()->getExprLoc();
68-
SourceLocation EndLoc =
69-
StartLoc.getLocWithOffset(StringRef("cast").size() - 1);
64+
const auto *Callee = Result.Nodes.getNodeAs<DeclRefExpr>("callee");
65+
66+
assert(Callee && "Callee should be binded if anything is matched");
67+
68+
// The first and last letter of the identifier
69+
// llvm::cast<T>(x)
70+
// ^ ^
71+
// StartLoc EndLoc
72+
SourceLocation StartLoc = Callee->getLocation();
73+
SourceLocation EndLoc = Callee->getNameInfo().getEndLoc();
7074

71-
diag(MatchedDecl->getBeginLoc(),
75+
if (Result.Nodes.getNodeAs<VarDecl>("var")) {
76+
diag(StartLoc,
7277
"cast<> in conditional will assert rather than return a null pointer")
7378
<< FixItHint::CreateReplacement(SourceRange(StartLoc, EndLoc),
7479
"dyn_cast");
75-
} else if (const auto *MatchedDecl =
76-
Result.Nodes.getNodeAs<CallExpr>("call")) {
77-
SourceLocation StartLoc = MatchedDecl->getCallee()->getExprLoc();
78-
SourceLocation EndLoc =
79-
StartLoc.getLocWithOffset(StringRef("cast").size() - 1);
80-
80+
} else if (Result.Nodes.getNodeAs<CallExpr>("cond")) {
8181
StringRef Message =
8282
"cast<> in conditional will assert rather than return a null pointer";
83-
if (Result.Nodes.getNodeAs<NamedDecl>("dyn_cast"))
83+
if (Callee->getDecl()->getName() == "dyn_cast")
8484
Message = "return value from dyn_cast<> not used";
8585

86-
diag(MatchedDecl->getBeginLoc(), Message)
86+
diag(StartLoc, Message)
8787
<< FixItHint::CreateReplacement(SourceRange(StartLoc, EndLoc), "isa");
88-
} else if (const auto *MatchedDecl =
89-
Result.Nodes.getNodeAs<BinaryOperator>("and")) {
88+
} else if (Result.Nodes.getNodeAs<BinaryOperator>("and")) {
9089
const auto *LHS = Result.Nodes.getNodeAs<ImplicitCastExpr>("lhs");
9190
const auto *RHS = Result.Nodes.getNodeAs<CallExpr>("rhs");
9291
const auto *Arg = Result.Nodes.getNodeAs<Expr>("arg");
93-
const auto *Func = Result.Nodes.getNodeAs<NamedDecl>("func");
9492

9593
assert(LHS && "LHS is null");
9694
assert(RHS && "RHS is null");
9795
assert(Arg && "Arg is null");
98-
assert(Func && "Func is null");
9996

100-
StringRef LHSString(Lexer::getSourceText(
101-
CharSourceRange::getTokenRange(LHS->getSourceRange()),
102-
*Result.SourceManager, getLangOpts()));
97+
auto GetText = [&](SourceRange R) {
98+
return Lexer::getSourceText(CharSourceRange::getTokenRange(R),
99+
*Result.SourceManager, getLangOpts());
100+
};
103101

104-
StringRef ArgString(Lexer::getSourceText(
105-
CharSourceRange::getTokenRange(Arg->getSourceRange()),
106-
*Result.SourceManager, getLangOpts()));
102+
const StringRef LHSString = GetText(LHS->getSourceRange());
103+
const StringRef ArgString = GetText(Arg->getSourceRange());
107104

108105
if (ArgString != LHSString)
109106
return;
110107

111-
StringRef RHSString(Lexer::getSourceText(
112-
CharSourceRange::getTokenRange(RHS->getSourceRange()),
113-
*Result.SourceManager, getLangOpts()));
114-
115-
std::string Replacement("isa_and_nonnull");
116-
Replacement += RHSString.substr(Func->getName().size());
108+
// It is not clear which is preferred between `isa_and_nonnull` and
109+
// `isa_and_present`. See
110+
// https://discourse.llvm.org/t/psa-swapping-out-or-null-with-if-present/65018
111+
const std::string Replacement = llvm::formatv(
112+
"{}isa_and_nonnull{}",
113+
GetText(Callee->getQualifierLoc().getSourceRange()),
114+
GetText(SourceRange(Callee->getLAngleLoc(), RHS->getEndLoc())));
117115

118-
diag(MatchedDecl->getBeginLoc(),
116+
diag(LHS->getBeginLoc(),
119117
"isa_and_nonnull<> is preferred over an explicit test for null "
120118
"followed by calling isa<>")
121-
<< FixItHint::CreateReplacement(SourceRange(MatchedDecl->getBeginLoc(),
122-
MatchedDecl->getEndLoc()),
123-
Replacement);
119+
<< FixItHint::CreateReplacement(
120+
SourceRange(LHS->getBeginLoc(), RHS->getEndLoc()), Replacement);
121+
} else {
122+
llvm_unreachable(
123+
R"(One of "var", "cond" and "and" should be binded if anything is matched)");
124124
}
125125
}
126126

clang-tools-extra/docs/ReleaseNotes.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,16 @@ Changes in existing checks
215215
adding an option to allow pointer arithmetic via prefix/postfix increment or
216216
decrement operators.
217217

218+
- Improved :doc:`llvm-prefer-isa-or-dyn-cast-in-conditionals
219+
<clang-tidy/checks/llvm/prefer-isa-or-dyn-cast-in-conditionals>` check:
220+
221+
- Fix-it handles callees with nested-name-specifier correctly.
222+
223+
- ``if`` statements with init-statement (``if (auto X = ...; ...)``) are
224+
handled correctly.
225+
226+
- ``for`` loops are supported.
227+
218228
- Improved :doc:`misc-header-include-cycle
219229
<clang-tidy/checks/misc/header-include-cycle>` check performance.
220230

clang-tools-extra/test/clang-tidy/checkers/llvm/prefer-isa-or-dyn-cast-in-conditionals.cpp

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,47 @@ struct Z {
99
bool baz(Y*);
1010
};
1111

12+
namespace llvm {
1213
template <class X, class Y>
1314
bool isa(Y *);
1415
template <class X, class Y>
1516
X *cast(Y *);
1617
template <class X, class Y>
18+
X *cast_or_null(Y *);
19+
template <class X, class Y>
1720
X *dyn_cast(Y *);
1821
template <class X, class Y>
1922
X *dyn_cast_or_null(Y *);
23+
} // namespace llvm
24+
25+
using namespace llvm;
2026

2127
bool foo(Y *y, Z *z) {
2228
if (auto x = cast<X>(y))
2329
return true;
2430
// CHECK-MESSAGES: :[[@LINE-2]]:16: warning: cast<> in conditional will assert rather than return a null pointer [llvm-prefer-isa-or-dyn-cast-in-conditionals]
2531
// CHECK-FIXES: if (auto x = dyn_cast<X>(y))
2632

33+
if (auto x = ::cast<X>(y))
34+
return true;
35+
// CHECK-MESSAGES: :[[@LINE-2]]:18: warning: cast<> in conditional will assert rather than return a null pointer [llvm-prefer-isa-or-dyn-cast-in-conditionals]
36+
// CHECK-FIXES: if (auto x = ::dyn_cast<X>(y))
37+
38+
if (auto x = llvm::cast<X>(y))
39+
return true;
40+
// CHECK-MESSAGES: :[[@LINE-2]]:22: warning: cast<> in conditional will assert rather than return a null pointer [llvm-prefer-isa-or-dyn-cast-in-conditionals]
41+
// CHECK-FIXES: if (auto x = llvm::dyn_cast<X>(y))
42+
43+
if (auto x = ::llvm::cast<X>(y))
44+
return true;
45+
// CHECK-MESSAGES: :[[@LINE-2]]:24: warning: cast<> in conditional will assert rather than return a null pointer [llvm-prefer-isa-or-dyn-cast-in-conditionals]
46+
// CHECK-FIXES: if (auto x = ::llvm::dyn_cast<X>(y))
47+
48+
for (; auto x = cast<X>(y); )
49+
break;
50+
// CHECK-MESSAGES: :[[@LINE-2]]:19: warning: cast<> in conditional
51+
// CHECK-FIXES: for (; auto x = dyn_cast<X>(y); )
52+
2753
while (auto x = cast<X>(y))
2854
break;
2955
// CHECK-MESSAGES: :[[@LINE-2]]:19: warning: cast<> in conditional
@@ -34,6 +60,16 @@ bool foo(Y *y, Z *z) {
3460
// CHECK-MESSAGES: :[[@LINE-2]]:7: warning: cast<> in conditional
3561
// CHECK-FIXES: if (isa<X>(y))
3662

63+
if (auto x = cast<X>(y); cast<X>(y))
64+
return true;
65+
// CHECK-MESSAGES: :[[@LINE-2]]:28: warning: cast<> in conditional will assert rather than return a null pointer [llvm-prefer-isa-or-dyn-cast-in-conditionals]
66+
// CHECK-FIXES: if (auto x = cast<X>(y); isa<X>(y))
67+
68+
for (; cast<X>(y); )
69+
break;
70+
// CHECK-MESSAGES: :[[@LINE-2]]:10: warning: cast<> in conditional
71+
// CHECK-FIXES: for (; isa<X>(y); )
72+
3773
while (cast<X>(y))
3874
break;
3975
// CHECK-MESSAGES: :[[@LINE-2]]:10: warning: cast<> in conditional
@@ -50,6 +86,11 @@ bool foo(Y *y, Z *z) {
5086
// CHECK-MESSAGES: :[[@LINE-2]]:7: warning: return value from dyn_cast<> not used [llvm-prefer-isa-or-dyn-cast-in-conditionals]
5187
// CHECK-FIXES: if (isa<X>(y))
5288

89+
for (; dyn_cast<X>(y); )
90+
break;
91+
// CHECK-MESSAGES: :[[@LINE-2]]:10: warning: return value from dyn_cast<> not used
92+
// CHECK-FIXES: for (; isa<X>(y); )
93+
5394
while (dyn_cast<X>(y))
5495
break;
5596
// CHECK-MESSAGES: :[[@LINE-2]]:10: warning: return value from dyn_cast<> not used
@@ -66,6 +107,21 @@ bool foo(Y *y, Z *z) {
66107
// CHECK-MESSAGES: :[[@LINE-2]]:7: warning: isa_and_nonnull<> is preferred over an explicit test for null followed by calling isa<> [llvm-prefer-isa-or-dyn-cast-in-conditionals]
67108
// CHECK-FIXES: if (isa_and_nonnull<X>(y))
68109

110+
if (y && ::isa<X>(y))
111+
return true;
112+
// CHECK-MESSAGES: :[[@LINE-2]]:7: warning: isa_and_nonnull<> is preferred over an explicit test for null followed by calling isa<> [llvm-prefer-isa-or-dyn-cast-in-conditionals]
113+
// CHECK-FIXES: if (::isa_and_nonnull<X>(y))
114+
115+
if (y && llvm::isa<X>(y))
116+
return true;
117+
// CHECK-MESSAGES: :[[@LINE-2]]:7: warning: isa_and_nonnull<> is preferred over an explicit test for null followed by calling isa<> [llvm-prefer-isa-or-dyn-cast-in-conditionals]
118+
// CHECK-FIXES: if (llvm::isa_and_nonnull<X>(y))
119+
120+
if (y && ::llvm::isa<X>(y))
121+
return true;
122+
// CHECK-MESSAGES: :[[@LINE-2]]:7: warning: isa_and_nonnull<> is preferred over an explicit test for null followed by calling isa<> [llvm-prefer-isa-or-dyn-cast-in-conditionals]
123+
// CHECK-FIXES: if (::llvm::isa_and_nonnull<X>(y))
124+
69125
if (z->bar() && isa<Y>(z->bar()))
70126
return true;
71127
// CHECK-MESSAGES: :[[@LINE-2]]:7: warning: isa_and_nonnull<> is preferred
@@ -76,6 +132,11 @@ bool foo(Y *y, Z *z) {
76132
// CHECK-MESSAGES: :[[@LINE-2]]:7: warning: isa_and_nonnull<> is preferred
77133
// CHECK-FIXES: if (isa_and_nonnull<Y>(z->bar()))
78134

135+
if (z->bar() && cast_or_null<Y>(z->bar()))
136+
return true;
137+
// CHECK-MESSAGES: :[[@LINE-2]]:7: warning: isa_and_nonnull<> is preferred
138+
// CHECK-FIXES: if (isa_and_nonnull<Y>(z->bar()))
139+
79140
if (z->bar() && dyn_cast<Y>(z->bar()))
80141
return true;
81142
// CHECK-MESSAGES: :[[@LINE-2]]:7: warning: isa_and_nonnull<> is preferred

0 commit comments

Comments
 (0)