| //===--- PreferIsaOrDynCastInConditionalsCheck.cpp - clang-tidy |
| //---------------------===// |
| // |
| // 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" |
| |
| using namespace clang::ast_matchers; |
| |
| namespace clang { |
| namespace ast_matchers { |
| AST_MATCHER(Expr, isMacroID) { return Node.getExprLoc().isMacroID(); } |
| } // namespace ast_matchers |
| |
| namespace tidy { |
| namespace llvm_check { |
| |
| void PreferIsaOrDynCastInConditionalsCheck::registerMatchers( |
| MatchFinder *Finder) { |
| auto Condition = hasCondition(implicitCastExpr(has( |
| callExpr( |
| allOf(unless(isMacroID()), unless(cxxMemberCallExpr()), |
| anyOf(callee(namedDecl(hasName("cast"))), |
| callee(namedDecl(hasName("dyn_cast")).bind("dyn_cast"))))) |
| .bind("call")))); |
| |
| auto Any = anyOf( |
| has(declStmt(containsDeclaration( |
| 0, |
| varDecl(hasInitializer( |
| callExpr(allOf(unless(isMacroID()), unless(cxxMemberCallExpr()), |
| callee(namedDecl(hasName("cast"))))) |
| .bind("assign")))))), |
| Condition); |
| |
| auto CallExpression = |
| callExpr( |
| allOf( |
| unless(isMacroID()), unless(cxxMemberCallExpr()), |
| allOf(callee(namedDecl(hasAnyName("isa", "cast", "cast_or_null", |
| "dyn_cast", "dyn_cast_or_null")) |
| .bind("func")), |
| hasArgument( |
| 0, |
| mapAnyOf(declRefExpr, cxxMemberCallExpr).bind("arg"))))) |
| .bind("rhs"); |
| |
| Finder->addMatcher( |
| traverse(TK_AsIs, |
| stmt(anyOf( |
| ifStmt(Any), whileStmt(Any), doStmt(Condition), |
| binaryOperator( |
| allOf(unless(isExpansionInFileMatching( |
| "llvm/include/llvm/Support/Casting.h")), |
| hasOperatorName("&&"), |
| hasLHS(implicitCastExpr().bind("lhs")), |
| hasRHS(anyOf(implicitCastExpr(has(CallExpression)), |
| CallExpression)))) |
| .bind("and")))), |
| this); |
| } |
| |
| void PreferIsaOrDynCastInConditionalsCheck::check( |
| const MatchFinder::MatchResult &Result) { |
| if (const auto *MatchedDecl = Result.Nodes.getNodeAs<CallExpr>("assign")) { |
| SourceLocation StartLoc = MatchedDecl->getCallee()->getExprLoc(); |
| SourceLocation EndLoc = |
| StartLoc.getLocWithOffset(StringRef("cast").size() - 1); |
| |
| diag(MatchedDecl->getBeginLoc(), |
| "cast<> in conditional will assert rather than return a null pointer") |
| << FixItHint::CreateReplacement(SourceRange(StartLoc, EndLoc), |
| "dyn_cast"); |
| } else if (const auto *MatchedDecl = |
| Result.Nodes.getNodeAs<CallExpr>("call")) { |
| SourceLocation StartLoc = MatchedDecl->getCallee()->getExprLoc(); |
| SourceLocation EndLoc = |
| StartLoc.getLocWithOffset(StringRef("cast").size() - 1); |
| |
| StringRef Message = |
| "cast<> in conditional will assert rather than return a null pointer"; |
| if (Result.Nodes.getNodeAs<NamedDecl>("dyn_cast")) |
| Message = "return value from dyn_cast<> not used"; |
| |
| diag(MatchedDecl->getBeginLoc(), Message) |
| << FixItHint::CreateReplacement(SourceRange(StartLoc, EndLoc), "isa"); |
| } else if (const auto *MatchedDecl = |
| Result.Nodes.getNodeAs<BinaryOperator>("and")) { |
| const auto *LHS = Result.Nodes.getNodeAs<ImplicitCastExpr>("lhs"); |
| const auto *RHS = Result.Nodes.getNodeAs<CallExpr>("rhs"); |
| const auto *Arg = Result.Nodes.getNodeAs<Expr>("arg"); |
| const auto *Func = Result.Nodes.getNodeAs<NamedDecl>("func"); |
| |
| assert(LHS && "LHS is null"); |
| assert(RHS && "RHS is null"); |
| assert(Arg && "Arg is null"); |
| assert(Func && "Func is null"); |
| |
| StringRef LHSString(Lexer::getSourceText( |
| CharSourceRange::getTokenRange(LHS->getSourceRange()), |
| *Result.SourceManager, getLangOpts())); |
| |
| StringRef ArgString(Lexer::getSourceText( |
| CharSourceRange::getTokenRange(Arg->getSourceRange()), |
| *Result.SourceManager, getLangOpts())); |
| |
| if (ArgString != LHSString) |
| return; |
| |
| StringRef RHSString(Lexer::getSourceText( |
| CharSourceRange::getTokenRange(RHS->getSourceRange()), |
| *Result.SourceManager, getLangOpts())); |
| |
| std::string Replacement("isa_and_nonnull"); |
| Replacement += RHSString.substr(Func->getName().size()); |
| |
| diag(MatchedDecl->getBeginLoc(), |
| "isa_and_nonnull<> is preferred over an explicit test for null " |
| "followed by calling isa<>") |
| << FixItHint::CreateReplacement(SourceRange(MatchedDecl->getBeginLoc(), |
| MatchedDecl->getEndLoc()), |
| Replacement); |
| } |
| } |
| |
| } // namespace llvm_check |
| } // namespace tidy |
| } // namespace clang |