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 {
23 namespace llvm_check {
24 
registerMatchers(MatchFinder * Finder)25 void PreferIsaOrDynCastInConditionalsCheck::registerMatchers(
26     MatchFinder *Finder) {
27   auto Condition = hasCondition(implicitCastExpr(has(
28       callExpr(
29           allOf(unless(isMacroID()), unless(cxxMemberCallExpr()),
30                 anyOf(callee(namedDecl(hasName("cast"))),
31                       callee(namedDecl(hasName("dyn_cast")).bind("dyn_cast")))))
32           .bind("call"))));
33 
34   auto Any = anyOf(
35       has(declStmt(containsDeclaration(
36           0,
37           varDecl(hasInitializer(
38               callExpr(allOf(unless(isMacroID()), unless(cxxMemberCallExpr()),
39                              callee(namedDecl(hasName("cast")))))
40                   .bind("assign")))))),
41       Condition);
42 
43   auto CallExpression =
44       callExpr(
45           allOf(
46               unless(isMacroID()), unless(cxxMemberCallExpr()),
47               allOf(callee(namedDecl(hasAnyName("isa", "cast", "cast_or_null",
48                                                 "dyn_cast", "dyn_cast_or_null"))
49                                .bind("func")),
50                     hasArgument(
51                         0,
52                         mapAnyOf(declRefExpr, cxxMemberCallExpr).bind("arg")))))
53           .bind("rhs");
54 
55   Finder->addMatcher(
56       traverse(TK_AsIs,
57                stmt(anyOf(
58                    ifStmt(Any), whileStmt(Any), doStmt(Condition),
59                    binaryOperator(
60                        allOf(unless(isExpansionInFileMatching(
61                                  "llvm/include/llvm/Support/Casting.h")),
62                              hasOperatorName("&&"),
63                              hasLHS(implicitCastExpr().bind("lhs")),
64                              hasRHS(anyOf(implicitCastExpr(has(CallExpression)),
65                                           CallExpression))))
66                        .bind("and")))),
67       this);
68 }
69 
check(const MatchFinder::MatchResult & Result)70 void PreferIsaOrDynCastInConditionalsCheck::check(
71     const MatchFinder::MatchResult &Result) {
72   if (const auto *MatchedDecl = Result.Nodes.getNodeAs<CallExpr>("assign")) {
73     SourceLocation StartLoc = MatchedDecl->getCallee()->getExprLoc();
74     SourceLocation EndLoc =
75         StartLoc.getLocWithOffset(StringRef("cast").size() - 1);
76 
77     diag(MatchedDecl->getBeginLoc(),
78          "cast<> in conditional will assert rather than return a null pointer")
79         << FixItHint::CreateReplacement(SourceRange(StartLoc, EndLoc),
80                                         "dyn_cast");
81   } else if (const auto *MatchedDecl =
82                  Result.Nodes.getNodeAs<CallExpr>("call")) {
83     SourceLocation StartLoc = MatchedDecl->getCallee()->getExprLoc();
84     SourceLocation EndLoc =
85         StartLoc.getLocWithOffset(StringRef("cast").size() - 1);
86 
87     StringRef Message =
88         "cast<> in conditional will assert rather than return a null pointer";
89     if (Result.Nodes.getNodeAs<NamedDecl>("dyn_cast"))
90       Message = "return value from dyn_cast<> not used";
91 
92     diag(MatchedDecl->getBeginLoc(), Message)
93         << FixItHint::CreateReplacement(SourceRange(StartLoc, EndLoc), "isa");
94   } else if (const auto *MatchedDecl =
95                  Result.Nodes.getNodeAs<BinaryOperator>("and")) {
96     const auto *LHS = Result.Nodes.getNodeAs<ImplicitCastExpr>("lhs");
97     const auto *RHS = Result.Nodes.getNodeAs<CallExpr>("rhs");
98     const auto *Arg = Result.Nodes.getNodeAs<Expr>("arg");
99     const auto *Func = Result.Nodes.getNodeAs<NamedDecl>("func");
100 
101     assert(LHS && "LHS is null");
102     assert(RHS && "RHS is null");
103     assert(Arg && "Arg is null");
104     assert(Func && "Func is null");
105 
106     StringRef LHSString(Lexer::getSourceText(
107         CharSourceRange::getTokenRange(LHS->getSourceRange()),
108         *Result.SourceManager, getLangOpts()));
109 
110     StringRef ArgString(Lexer::getSourceText(
111         CharSourceRange::getTokenRange(Arg->getSourceRange()),
112         *Result.SourceManager, getLangOpts()));
113 
114     if (ArgString != LHSString)
115       return;
116 
117     StringRef RHSString(Lexer::getSourceText(
118         CharSourceRange::getTokenRange(RHS->getSourceRange()),
119         *Result.SourceManager, getLangOpts()));
120 
121     std::string Replacement("isa_and_nonnull");
122     Replacement += RHSString.substr(Func->getName().size());
123 
124     diag(MatchedDecl->getBeginLoc(),
125          "isa_and_nonnull<> is preferred over an explicit test for null "
126          "followed by calling isa<>")
127         << FixItHint::CreateReplacement(SourceRange(MatchedDecl->getBeginLoc(),
128                                                     MatchedDecl->getEndLoc()),
129                                         Replacement);
130   }
131 }
132 
133 } // namespace llvm_check
134 } // namespace tidy
135 } // namespace clang
136