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