//===----------------------------------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "PreferIsaOrDynCastInConditionalsCheck.h" #include "clang/AST/ASTContext.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/Lex/Lexer.h" #include "llvm/Support/FormatVariadic.h" using namespace clang::ast_matchers; namespace clang::tidy::llvm_check { namespace { AST_MATCHER(Expr, isMacroID) { return Node.getExprLoc().isMacroID(); } } // namespace void PreferIsaOrDynCastInConditionalsCheck::registerMatchers( MatchFinder *Finder) { auto AnyCalleeName = [](ArrayRef CalleeName) { return allOf(unless(isMacroID()), unless(cxxMemberCallExpr()), callee(expr(ignoringImpCasts( declRefExpr(to(namedDecl(hasAnyName(CalleeName))), hasAnyTemplateArgumentLoc(anything())) .bind("callee"))))); }; auto CondExpr = hasCondition(implicitCastExpr( has(callExpr(AnyCalleeName({"cast", "dyn_cast"})).bind("cond")))); auto CondExprOrCondVar = anyOf(hasConditionVariableStatement(containsDeclaration( 0, varDecl(hasInitializer(callExpr(AnyCalleeName({"cast"})))) .bind("var"))), CondExpr); auto CallWithBindedArg = callExpr( AnyCalleeName( {"isa", "cast", "cast_or_null", "dyn_cast", "dyn_cast_or_null"}), hasArgument(0, mapAnyOf(declRefExpr, cxxMemberCallExpr).bind("arg"))) .bind("rhs"); Finder->addMatcher( stmt(anyOf(ifStmt(CondExprOrCondVar), forStmt(CondExprOrCondVar), whileStmt(CondExprOrCondVar), doStmt(CondExpr), binaryOperator(unless(isExpansionInFileMatching( "llvm/include/llvm/Support/Casting.h")), hasOperatorName("&&"), hasLHS(implicitCastExpr().bind("lhs")), hasRHS(ignoringImpCasts(CallWithBindedArg))) .bind("and"))), this); } void PreferIsaOrDynCastInConditionalsCheck::check( const MatchFinder::MatchResult &Result) { const auto *Callee = Result.Nodes.getNodeAs("callee"); assert(Callee && "Callee should be binded if anything is matched"); // The first and last letter of the identifier // llvm::cast(x) // ^ ^ // StartLoc EndLoc SourceLocation StartLoc = Callee->getLocation(); SourceLocation EndLoc = Callee->getNameInfo().getEndLoc(); if (Result.Nodes.getNodeAs("var")) { diag(StartLoc, "cast<> in conditional will assert rather than return a null pointer") << FixItHint::CreateReplacement(SourceRange(StartLoc, EndLoc), "dyn_cast"); } else if (Result.Nodes.getNodeAs("cond")) { StringRef Message = "cast<> in conditional will assert rather than return a null pointer"; if (Callee->getDecl()->getName() == "dyn_cast") Message = "return value from dyn_cast<> not used"; diag(StartLoc, Message) << FixItHint::CreateReplacement(SourceRange(StartLoc, EndLoc), "isa"); } else if (Result.Nodes.getNodeAs("and")) { const auto *LHS = Result.Nodes.getNodeAs("lhs"); const auto *RHS = Result.Nodes.getNodeAs("rhs"); const auto *Arg = Result.Nodes.getNodeAs("arg"); assert(LHS && "LHS is null"); assert(RHS && "RHS is null"); assert(Arg && "Arg is null"); auto GetText = [&](SourceRange R) { return Lexer::getSourceText(CharSourceRange::getTokenRange(R), *Result.SourceManager, getLangOpts()); }; const StringRef LHSString = GetText(LHS->getSourceRange()); const StringRef ArgString = GetText(Arg->getSourceRange()); if (ArgString != LHSString) return; // It is not clear which is preferred between `isa_and_nonnull` and // `isa_and_present`. See // https://discourse.llvm.org/t/psa-swapping-out-or-null-with-if-present/65018 const std::string Replacement = llvm::formatv( "{}isa_and_nonnull{}", GetText(Callee->getQualifierLoc().getSourceRange()), GetText(SourceRange(Callee->getLAngleLoc(), RHS->getEndLoc()))); diag(LHS->getBeginLoc(), "isa_and_nonnull<> is preferred over an explicit test for null " "followed by calling isa<>") << FixItHint::CreateReplacement( SourceRange(LHS->getBeginLoc(), RHS->getEndLoc()), Replacement); } else { llvm_unreachable( R"(One of "var", "cond" and "and" should be binded if anything is matched)"); } } } // namespace clang::tidy::llvm_check