|  | //===--- RedundantSmartptrGetCheck.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 "RedundantSmartptrGetCheck.h" | 
|  | #include "clang/ASTMatchers/ASTMatchFinder.h" | 
|  | #include "clang/Lex/Lexer.h" | 
|  |  | 
|  | using namespace clang::ast_matchers; | 
|  |  | 
|  | namespace clang::tidy::readability { | 
|  |  | 
|  | namespace { | 
|  | internal::Matcher<Expr> callToGet(const internal::Matcher<Decl> &OnClass) { | 
|  | return expr( | 
|  | anyOf(cxxMemberCallExpr( | 
|  | on(expr(anyOf(hasType(OnClass), | 
|  | hasType(qualType(pointsTo( | 
|  | decl(OnClass).bind("ptr_to_ptr")))))) | 
|  | .bind("smart_pointer")), | 
|  | unless(callee( | 
|  | memberExpr(hasObjectExpression(cxxThisExpr())))), | 
|  | callee(cxxMethodDecl(hasName("get"), | 
|  | returns(qualType(pointsTo( | 
|  | type().bind("getType"))))))), | 
|  | cxxDependentScopeMemberExpr( | 
|  | hasMemberName("get"), | 
|  | hasObjectExpression( | 
|  | expr(hasType(qualType(hasCanonicalType( | 
|  | templateSpecializationType(hasDeclaration( | 
|  | classTemplateDecl(has(cxxRecordDecl( | 
|  | OnClass, | 
|  | hasMethod(cxxMethodDecl( | 
|  | hasName("get"), | 
|  | returns(qualType( | 
|  | pointsTo(type().bind( | 
|  | "getType"))))))))))))))) | 
|  | .bind("smart_pointer"))))) | 
|  | .bind("redundant_get"); | 
|  | } | 
|  |  | 
|  | internal::Matcher<Decl> knownSmartptr() { | 
|  | return recordDecl(hasAnyName("::std::unique_ptr", "::std::shared_ptr")); | 
|  | } | 
|  |  | 
|  | void registerMatchersForGetArrowStart(MatchFinder *Finder, | 
|  | MatchFinder::MatchCallback *Callback) { | 
|  | const auto QuacksLikeASmartptr = recordDecl( | 
|  | recordDecl().bind("duck_typing"), | 
|  | has(cxxMethodDecl(hasName("operator->"), | 
|  | returns(qualType(pointsTo(type().bind("op->Type")))))), | 
|  | has(cxxMethodDecl(hasName("operator*"), returns(qualType(references( | 
|  | type().bind("op*Type"))))))); | 
|  |  | 
|  | // Make sure we are not missing the known standard types. | 
|  | const auto Smartptr = anyOf(knownSmartptr(), QuacksLikeASmartptr); | 
|  |  | 
|  | // Catch 'ptr.get()->Foo()' | 
|  | Finder->addMatcher(memberExpr(expr().bind("memberExpr"), isArrow(), | 
|  | hasObjectExpression(callToGet(Smartptr))), | 
|  | Callback); | 
|  |  | 
|  | // Catch '*ptr.get()' or '*ptr->get()' | 
|  | Finder->addMatcher( | 
|  | unaryOperator(hasOperatorName("*"), hasUnaryOperand(callToGet(Smartptr))), | 
|  | Callback); | 
|  |  | 
|  | // Catch '!ptr.get()' | 
|  | const auto CallToGetAsBool = callToGet( | 
|  | recordDecl(Smartptr, has(cxxConversionDecl(returns(booleanType()))))); | 
|  | Finder->addMatcher( | 
|  | unaryOperator(hasOperatorName("!"), hasUnaryOperand(CallToGetAsBool)), | 
|  | Callback); | 
|  |  | 
|  | // Catch 'if(ptr.get())' | 
|  | Finder->addMatcher(ifStmt(hasCondition(CallToGetAsBool)), Callback); | 
|  |  | 
|  | // Catch 'ptr.get() ? X : Y' | 
|  | Finder->addMatcher(conditionalOperator(hasCondition(CallToGetAsBool)), | 
|  | Callback); | 
|  |  | 
|  | Finder->addMatcher(cxxDependentScopeMemberExpr(hasObjectExpression( | 
|  | callExpr(has(callToGet(Smartptr))).bind("obj"))), | 
|  | Callback); | 
|  | } | 
|  |  | 
|  | void registerMatchersForGetEquals(MatchFinder *Finder, | 
|  | MatchFinder::MatchCallback *Callback) { | 
|  | // This one is harder to do with duck typing. | 
|  | // The operator==/!= that we are looking for might be member or non-member, | 
|  | // might be on global namespace or found by ADL, might be a template, etc. | 
|  | // For now, lets keep it to the known standard types. | 
|  |  | 
|  | // Matches against nullptr. | 
|  | Finder->addMatcher( | 
|  | binaryOperator(hasAnyOperatorName("==", "!="), | 
|  | hasOperands(anyOf(cxxNullPtrLiteralExpr(), gnuNullExpr(), | 
|  | integerLiteral(equals(0))), | 
|  | callToGet(knownSmartptr()))), | 
|  | Callback); | 
|  |  | 
|  | // FIXME: Match and fix if (l.get() == r.get()). | 
|  | } | 
|  |  | 
|  | } // namespace | 
|  |  | 
|  | void RedundantSmartptrGetCheck::storeOptions( | 
|  | ClangTidyOptions::OptionMap &Opts) { | 
|  | Options.store(Opts, "IgnoreMacros", IgnoreMacros); | 
|  | } | 
|  |  | 
|  | void RedundantSmartptrGetCheck::registerMatchers(MatchFinder *Finder) { | 
|  | registerMatchersForGetArrowStart(Finder, this); | 
|  | registerMatchersForGetEquals(Finder, this); | 
|  | } | 
|  |  | 
|  | namespace { | 
|  | bool allReturnTypesMatch(const MatchFinder::MatchResult &Result) { | 
|  | if (Result.Nodes.getNodeAs<Decl>("duck_typing") == nullptr) | 
|  | return true; | 
|  | // Verify that the types match. | 
|  | // We can't do this on the matcher because the type nodes can be different, | 
|  | // even though they represent the same type. This difference comes from how | 
|  | // the type is referenced (eg. through a typedef, a type trait, etc). | 
|  | const Type *OpArrowType = | 
|  | Result.Nodes.getNodeAs<Type>("op->Type")->getUnqualifiedDesugaredType(); | 
|  | const Type *OpStarType = | 
|  | Result.Nodes.getNodeAs<Type>("op*Type")->getUnqualifiedDesugaredType(); | 
|  | const Type *GetType = | 
|  | Result.Nodes.getNodeAs<Type>("getType")->getUnqualifiedDesugaredType(); | 
|  | return OpArrowType == OpStarType && OpArrowType == GetType; | 
|  | } | 
|  | } // namespace | 
|  |  | 
|  | void RedundantSmartptrGetCheck::check(const MatchFinder::MatchResult &Result) { | 
|  | if (!allReturnTypesMatch(Result)) | 
|  | return; | 
|  |  | 
|  | bool IsPtrToPtr = Result.Nodes.getNodeAs<Decl>("ptr_to_ptr") != nullptr; | 
|  | bool IsMemberExpr = Result.Nodes.getNodeAs<Expr>("memberExpr") != nullptr; | 
|  | const auto *GetCall = Result.Nodes.getNodeAs<Expr>("redundant_get"); | 
|  | if (GetCall->getBeginLoc().isMacroID() && IgnoreMacros) | 
|  | return; | 
|  |  | 
|  | const auto *Smartptr = Result.Nodes.getNodeAs<Expr>("smart_pointer"); | 
|  |  | 
|  | if (IsPtrToPtr && IsMemberExpr) { | 
|  | // Ignore this case (eg. Foo->get()->DoSomething()); | 
|  | return; | 
|  | } | 
|  |  | 
|  | auto SR = GetCall->getSourceRange(); | 
|  | // CXXDependentScopeMemberExpr source range does not include parens | 
|  | // Extend the source range of the get call to account for them. | 
|  | if (isa<CXXDependentScopeMemberExpr>(GetCall)) | 
|  | SR.setEnd(Lexer::getLocForEndOfToken(SR.getEnd(), 0, *Result.SourceManager, | 
|  | getLangOpts()) | 
|  | .getLocWithOffset(1)); | 
|  |  | 
|  | StringRef SmartptrText = Lexer::getSourceText( | 
|  | CharSourceRange::getTokenRange(Smartptr->getSourceRange()), | 
|  | *Result.SourceManager, getLangOpts()); | 
|  | // Check if the last two characters are "->" and remove them | 
|  | if (SmartptrText.ends_with("->")) { | 
|  | SmartptrText = SmartptrText.drop_back(2); | 
|  | } | 
|  | // Replace foo->get() with *foo, and foo.get() with foo. | 
|  | std::string Replacement = Twine(IsPtrToPtr ? "*" : "", SmartptrText).str(); | 
|  | diag(GetCall->getBeginLoc(), "redundant get() call on smart pointer") | 
|  | << FixItHint::CreateReplacement(SR, Replacement); | 
|  | } | 
|  |  | 
|  | } // namespace clang::tidy::readability |