clang-tools 24.0.0git
PreferIsaOrDynCastInConditionalsCheck.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 "../utils/Matchers.h"
11#include "clang/AST/ASTContext.h"
12#include "clang/ASTMatchers/ASTMatchFinder.h"
13#include "clang/Lex/Lexer.h"
14#include "llvm/Support/FormatVariadic.h"
15
16using namespace clang::ast_matchers;
17using namespace clang::tidy::matchers;
18
20
22 MatchFinder *Finder) {
23 const auto AnyCalleeName = [](ArrayRef<StringRef> CalleeName) {
24 return allOf(unless(isMacroID()), unless(cxxMemberCallExpr()),
25 callee(expr(ignoringImpCasts(
26 declRefExpr(to(namedDecl(hasAnyName(CalleeName))),
27 hasAnyTemplateArgumentLoc(anything()))
28 .bind("callee")))));
29 };
30
31 auto CondExpr = hasCondition(implicitCastExpr(
32 has(callExpr(AnyCalleeName({"cast", "dyn_cast"})).bind("cond"))));
33
34 const auto CondExprOrCondVar =
35 anyOf(hasConditionVariableStatement(containsDeclaration(
36 0, varDecl(hasInitializer(callExpr(AnyCalleeName({"cast"}))))
37 .bind("var"))),
38 CondExpr);
39
40 const auto CallWithBindedArg =
41 callExpr(
42 AnyCalleeName(
43 {"isa", "cast", "cast_or_null", "dyn_cast", "dyn_cast_or_null"}),
44 hasArgument(0, mapAnyOf(declRefExpr, cxxMemberCallExpr).bind("arg")))
45 .bind("rhs");
46
47 Finder->addMatcher(ifStmt(CondExprOrCondVar), this);
48 Finder->addMatcher(forStmt(CondExprOrCondVar), this);
49 Finder->addMatcher(whileStmt(CondExprOrCondVar), this);
50 Finder->addMatcher(doStmt(CondExpr), this);
51 Finder->addMatcher(binaryOperator(hasRHS(ignoringImpCasts(CallWithBindedArg)),
52 hasLHS(implicitCastExpr().bind("lhs")),
53 hasOperatorName("&&"),
54 unless(isExpansionInFileMatching(
55 "llvm/include/llvm/Support/Casting.h")))
56 .bind("and"),
57 this);
58}
59
61 const MatchFinder::MatchResult &Result) {
62 const auto *Callee = Result.Nodes.getNodeAs<DeclRefExpr>("callee");
63
64 assert(Callee && "Callee should be binded if anything is matched");
65
66 // The first and last letter of the identifier
67 // llvm::cast<T>(x)
68 // ^ ^
69 // StartLoc EndLoc
70 const SourceLocation StartLoc = Callee->getLocation();
71 const SourceLocation EndLoc = Callee->getNameInfo().getEndLoc();
72
73 if (Result.Nodes.getNodeAs<VarDecl>("var")) {
74 diag(StartLoc,
75 "cast<> in conditional will assert rather than return a null pointer")
76 << FixItHint::CreateReplacement(SourceRange(StartLoc, EndLoc),
77 "dyn_cast");
78 } else if (Result.Nodes.getNodeAs<CallExpr>("cond")) {
79 StringRef Message =
80 "cast<> in conditional will assert rather than return a null pointer";
81 if (Callee->getDecl()->getName() == "dyn_cast")
82 Message = "return value from dyn_cast<> not used";
83
84 diag(StartLoc, Message)
85 << FixItHint::CreateReplacement(SourceRange(StartLoc, EndLoc), "isa");
86 } else if (Result.Nodes.getNodeAs<BinaryOperator>("and")) {
87 const auto *LHS = Result.Nodes.getNodeAs<ImplicitCastExpr>("lhs");
88 const auto *RHS = Result.Nodes.getNodeAs<CallExpr>("rhs");
89 const auto *Arg = Result.Nodes.getNodeAs<Expr>("arg");
90
91 assert(LHS && "LHS is null");
92 assert(RHS && "RHS is null");
93 assert(Arg && "Arg is null");
94
95 const auto GetText = [&](SourceRange R) {
96 return Lexer::getSourceText(CharSourceRange::getTokenRange(R),
97 *Result.SourceManager, getLangOpts());
98 };
99
100 const StringRef LHSString = GetText(LHS->getSourceRange());
101 const StringRef ArgString = GetText(Arg->getSourceRange());
102
103 if (ArgString != LHSString)
104 return;
105
106 // It is not clear which is preferred between `isa_and_nonnull` and
107 // `isa_and_present`. See
108 // https://discourse.llvm.org/t/psa-swapping-out-or-null-with-if-present/65018
109 const std::string Replacement = llvm::formatv(
110 "{}isa_and_nonnull{}",
111 GetText(Callee->getQualifierLoc().getSourceRange()),
112 GetText(SourceRange(Callee->getLAngleLoc(), RHS->getEndLoc())));
113
114 diag(LHS->getBeginLoc(),
115 "isa_and_nonnull<> is preferred over an explicit test for null "
116 "followed by calling isa<>")
117 << FixItHint::CreateReplacement(
118 SourceRange(LHS->getBeginLoc(), RHS->getEndLoc()), Replacement);
119 } else {
120 llvm_unreachable(
121 R"(One of "var", "cond" and "and" should be binded if anything is matched)");
122 }
123}
124
125} // namespace clang::tidy::llvm_check
void check(const ast_matchers::MatchFinder::MatchResult &Result) override