xref: /llvm-project/clang-tools-extra/clang-tidy/llvm/PreferIsaOrDynCastInConditionalsCheck.cpp (revision df0c8f25145047731fb95b4ce7153ce6fb5b6f5d)
1 //===--- PreferIsaOrDynCastInConditionalsCheck.cpp - clang-tidy
2 //---------------------===//
3 //
4 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
5 // See https://llvm.org/LICENSE.txt for license information.
6 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7 //
8 //===----------------------------------------------------------------------===//
9 
10 #include "PreferIsaOrDynCastInConditionalsCheck.h"
11 #include "clang/AST/ASTContext.h"
12 #include "clang/ASTMatchers/ASTMatchFinder.h"
13 #include "clang/Lex/Lexer.h"
14 
15 using namespace clang::ast_matchers;
16 
17 namespace clang {
18 namespace ast_matchers {
AST_MATCHER(Expr,isMacroID)19 AST_MATCHER(Expr, isMacroID) { return Node.getExprLoc().isMacroID(); }
20 } // namespace ast_matchers
21 
22 namespace tidy::llvm_check {
23 
registerMatchers(MatchFinder * Finder)24 void PreferIsaOrDynCastInConditionalsCheck::registerMatchers(
25     MatchFinder *Finder) {
26   auto Condition = hasCondition(implicitCastExpr(has(
27       callExpr(unless(isMacroID()), unless(cxxMemberCallExpr()),
28                anyOf(callee(namedDecl(hasName("cast"))),
29                      callee(namedDecl(hasName("dyn_cast")).bind("dyn_cast"))))
30           .bind("call"))));
31 
32   auto Any = anyOf(
33       has(declStmt(containsDeclaration(
34           0, varDecl(hasInitializer(callExpr(unless(isMacroID()),
35                                              unless(cxxMemberCallExpr()),
36                                              callee(namedDecl(hasName("cast"))))
37                                         .bind("assign")))))),
38       Condition);
39 
40   auto CallExpression =
41       callExpr(
42 
43           unless(isMacroID()), unless(cxxMemberCallExpr()),
44           callee(namedDecl(hasAnyName("isa", "cast", "cast_or_null", "dyn_cast",
45                                       "dyn_cast_or_null"))
46                      .bind("func")),
47           hasArgument(0, mapAnyOf(declRefExpr, cxxMemberCallExpr).bind("arg")))
48           .bind("rhs");
49 
50   Finder->addMatcher(
51       traverse(
52           TK_AsIs,
53           stmt(anyOf(
54               ifStmt(Any), whileStmt(Any), doStmt(Condition),
55               binaryOperator(unless(isExpansionInFileMatching(
56                                  "llvm/include/llvm/Support/Casting.h")),
57                              hasOperatorName("&&"),
58                              hasLHS(implicitCastExpr().bind("lhs")),
59                              hasRHS(anyOf(implicitCastExpr(has(CallExpression)),
60                                           CallExpression)))
61                   .bind("and")))),
62       this);
63 }
64 
check(const MatchFinder::MatchResult & Result)65 void PreferIsaOrDynCastInConditionalsCheck::check(
66     const MatchFinder::MatchResult &Result) {
67   if (const auto *MatchedDecl = Result.Nodes.getNodeAs<CallExpr>("assign")) {
68     SourceLocation StartLoc = MatchedDecl->getCallee()->getExprLoc();
69     SourceLocation EndLoc =
70         StartLoc.getLocWithOffset(StringRef("cast").size() - 1);
71 
72     diag(MatchedDecl->getBeginLoc(),
73          "cast<> in conditional will assert rather than return a null pointer")
74         << FixItHint::CreateReplacement(SourceRange(StartLoc, EndLoc),
75                                         "dyn_cast");
76   } else if (const auto *MatchedDecl =
77                  Result.Nodes.getNodeAs<CallExpr>("call")) {
78     SourceLocation StartLoc = MatchedDecl->getCallee()->getExprLoc();
79     SourceLocation EndLoc =
80         StartLoc.getLocWithOffset(StringRef("cast").size() - 1);
81 
82     StringRef Message =
83         "cast<> in conditional will assert rather than return a null pointer";
84     if (Result.Nodes.getNodeAs<NamedDecl>("dyn_cast"))
85       Message = "return value from dyn_cast<> not used";
86 
87     diag(MatchedDecl->getBeginLoc(), Message)
88         << FixItHint::CreateReplacement(SourceRange(StartLoc, EndLoc), "isa");
89   } else if (const auto *MatchedDecl =
90                  Result.Nodes.getNodeAs<BinaryOperator>("and")) {
91     const auto *LHS = Result.Nodes.getNodeAs<ImplicitCastExpr>("lhs");
92     const auto *RHS = Result.Nodes.getNodeAs<CallExpr>("rhs");
93     const auto *Arg = Result.Nodes.getNodeAs<Expr>("arg");
94     const auto *Func = Result.Nodes.getNodeAs<NamedDecl>("func");
95 
96     assert(LHS && "LHS is null");
97     assert(RHS && "RHS is null");
98     assert(Arg && "Arg is null");
99     assert(Func && "Func is null");
100 
101     StringRef LHSString(Lexer::getSourceText(
102         CharSourceRange::getTokenRange(LHS->getSourceRange()),
103         *Result.SourceManager, getLangOpts()));
104 
105     StringRef ArgString(Lexer::getSourceText(
106         CharSourceRange::getTokenRange(Arg->getSourceRange()),
107         *Result.SourceManager, getLangOpts()));
108 
109     if (ArgString != LHSString)
110       return;
111 
112     StringRef RHSString(Lexer::getSourceText(
113         CharSourceRange::getTokenRange(RHS->getSourceRange()),
114         *Result.SourceManager, getLangOpts()));
115 
116     std::string Replacement("isa_and_nonnull");
117     Replacement += RHSString.substr(Func->getName().size());
118 
119     diag(MatchedDecl->getBeginLoc(),
120          "isa_and_nonnull<> is preferred over an explicit test for null "
121          "followed by calling isa<>")
122         << FixItHint::CreateReplacement(SourceRange(MatchedDecl->getBeginLoc(),
123                                                     MatchedDecl->getEndLoc()),
124                                         Replacement);
125   }
126 }
127 
128 } // namespace tidy::llvm_check
129 } // namespace clang
130