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