127 lines · cpp
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-exception6//7//===----------------------------------------------------------------------===//8 9#include "PreferIsaOrDynCastInConditionalsCheck.h"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 17namespace clang::tidy::llvm_check {18 19namespace {20AST_MATCHER(Expr, isMacroID) { return Node.getExprLoc().isMacroID(); }21} // namespace22 23void PreferIsaOrDynCastInConditionalsCheck::registerMatchers(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 61void PreferIsaOrDynCastInConditionalsCheck::check(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 identifier68 // llvm::cast<T>(x)69 // ^ ^70 // StartLoc EndLoc71 const SourceLocation StartLoc = Callee->getLocation();72 const 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` and108 // `isa_and_present`. See109 // https://discourse.llvm.org/t/psa-swapping-out-or-null-with-if-present/65018110 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_check127