| 1 | //===--- RedundantSmartptrGetCheck.cpp - clang-tidy -----------------------===// |
| 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 | |
| 9 | #include "RedundantSmartptrGetCheck.h" |
| 10 | #include "clang/ASTMatchers/ASTMatchFinder.h" |
| 11 | #include "clang/Lex/Lexer.h" |
| 12 | |
| 13 | using namespace clang::ast_matchers; |
| 14 | |
| 15 | namespace clang::tidy::readability { |
| 16 | |
| 17 | namespace { |
| 18 | internal::Matcher<Expr> callToGet(const internal::Matcher<Decl> &OnClass) { |
| 19 | return expr( |
| 20 | anyOf(cxxMemberCallExpr( |
| 21 | on(InnerMatcher: expr(anyOf(hasType(InnerMatcher: OnClass), |
| 22 | hasType(InnerMatcher: qualType(pointsTo( |
| 23 | InnerMatcher: decl(OnClass).bind(ID: "ptr_to_ptr" )))))) |
| 24 | .bind(ID: "smart_pointer" )), |
| 25 | unless(callee( |
| 26 | InnerMatcher: memberExpr(hasObjectExpression(InnerMatcher: cxxThisExpr())))), |
| 27 | callee(InnerMatcher: cxxMethodDecl(hasName(Name: "get" ), |
| 28 | returns(InnerMatcher: qualType(pointsTo( |
| 29 | InnerMatcher: type().bind(ID: "getType" ))))))), |
| 30 | cxxDependentScopeMemberExpr( |
| 31 | hasMemberName(N: "get" ), |
| 32 | hasObjectExpression( |
| 33 | InnerMatcher: expr(hasType(InnerMatcher: qualType(hasCanonicalType( |
| 34 | InnerMatcher: templateSpecializationType(hasDeclaration( |
| 35 | InnerMatcher: classTemplateDecl(has(cxxRecordDecl( |
| 36 | OnClass, |
| 37 | hasMethod(InnerMatcher: cxxMethodDecl( |
| 38 | hasName(Name: "get" ), |
| 39 | returns(InnerMatcher: qualType( |
| 40 | pointsTo(InnerMatcher: type().bind( |
| 41 | ID: "getType" ))))))))))))))) |
| 42 | .bind(ID: "smart_pointer" ))))) |
| 43 | .bind(ID: "redundant_get" ); |
| 44 | } |
| 45 | |
| 46 | internal::Matcher<Decl> knownSmartptr() { |
| 47 | return recordDecl(hasAnyName("::std::unique_ptr" , "::std::shared_ptr" )); |
| 48 | } |
| 49 | |
| 50 | void registerMatchersForGetArrowStart(MatchFinder *Finder, |
| 51 | MatchFinder::MatchCallback *Callback) { |
| 52 | const auto MatchesOpArrow = |
| 53 | allOf(hasName(Name: "operator->" ), |
| 54 | returns(InnerMatcher: qualType(pointsTo(InnerMatcher: type().bind(ID: "op->Type" ))))); |
| 55 | const auto MatchesOpStar = |
| 56 | allOf(hasName(Name: "operator*" ), |
| 57 | returns(InnerMatcher: qualType(references(InnerMatcher: type().bind(ID: "op*Type" ))))); |
| 58 | const auto HasRelevantOps = |
| 59 | allOf(anyOf(hasMethod(InnerMatcher: MatchesOpArrow), |
| 60 | has(functionTemplateDecl(has(functionDecl(MatchesOpArrow))))), |
| 61 | anyOf(hasMethod(InnerMatcher: MatchesOpStar), |
| 62 | has(functionTemplateDecl(has(functionDecl(MatchesOpStar)))))); |
| 63 | |
| 64 | const auto QuacksLikeASmartptr = |
| 65 | cxxRecordDecl(cxxRecordDecl().bind(ID: "duck_typing" ), HasRelevantOps); |
| 66 | |
| 67 | // Make sure we are not missing the known standard types. |
| 68 | const auto SmartptrAny = anyOf(knownSmartptr(), QuacksLikeASmartptr); |
| 69 | const auto SmartptrWithDeref = anyOf( |
| 70 | cxxRecordDecl(knownSmartptr(), HasRelevantOps), QuacksLikeASmartptr); |
| 71 | |
| 72 | // Catch 'ptr.get()->Foo()' |
| 73 | Finder->addMatcher( |
| 74 | NodeMatch: memberExpr(expr().bind(ID: "memberExpr" ), isArrow(), |
| 75 | hasObjectExpression(InnerMatcher: callToGet(OnClass: SmartptrWithDeref))), |
| 76 | Action: Callback); |
| 77 | |
| 78 | // Catch '*ptr.get()' or '*ptr->get()' |
| 79 | Finder->addMatcher( |
| 80 | NodeMatch: unaryOperator(hasOperatorName(Name: "*" ), |
| 81 | hasUnaryOperand(InnerMatcher: callToGet(OnClass: SmartptrWithDeref))), |
| 82 | Action: Callback); |
| 83 | |
| 84 | // Catch '!ptr.get()' |
| 85 | const auto CallToGetAsBool = callToGet( |
| 86 | OnClass: recordDecl(SmartptrAny, has(cxxConversionDecl(returns(InnerMatcher: booleanType()))))); |
| 87 | Finder->addMatcher( |
| 88 | NodeMatch: unaryOperator(hasOperatorName(Name: "!" ), hasUnaryOperand(InnerMatcher: CallToGetAsBool)), |
| 89 | Action: Callback); |
| 90 | |
| 91 | // Catch 'if(ptr.get())' |
| 92 | Finder->addMatcher(NodeMatch: ifStmt(hasCondition(InnerMatcher: CallToGetAsBool)), Action: Callback); |
| 93 | |
| 94 | // Catch 'ptr.get() ? X : Y' |
| 95 | Finder->addMatcher(NodeMatch: conditionalOperator(hasCondition(InnerMatcher: CallToGetAsBool)), |
| 96 | Action: Callback); |
| 97 | |
| 98 | Finder->addMatcher(NodeMatch: cxxDependentScopeMemberExpr(hasObjectExpression( |
| 99 | InnerMatcher: callExpr(has(callToGet(OnClass: SmartptrAny))))), |
| 100 | Action: Callback); |
| 101 | } |
| 102 | |
| 103 | void registerMatchersForGetEquals(MatchFinder *Finder, |
| 104 | MatchFinder::MatchCallback *Callback) { |
| 105 | // This one is harder to do with duck typing. |
| 106 | // The operator==/!= that we are looking for might be member or non-member, |
| 107 | // might be on global namespace or found by ADL, might be a template, etc. |
| 108 | // For now, lets keep it to the known standard types. |
| 109 | |
| 110 | // Matches against nullptr. |
| 111 | Finder->addMatcher( |
| 112 | NodeMatch: binaryOperator(hasAnyOperatorName("==" , "!=" ), |
| 113 | hasOperands(Matcher1: anyOf(cxxNullPtrLiteralExpr(), gnuNullExpr(), |
| 114 | integerLiteral(equals(Value: 0))), |
| 115 | Matcher2: callToGet(OnClass: knownSmartptr()))), |
| 116 | Action: Callback); |
| 117 | |
| 118 | // FIXME: Match and fix if (l.get() == r.get()). |
| 119 | } |
| 120 | |
| 121 | } // namespace |
| 122 | |
| 123 | void RedundantSmartptrGetCheck::storeOptions( |
| 124 | ClangTidyOptions::OptionMap &Opts) { |
| 125 | Options.store(Options&: Opts, LocalName: "IgnoreMacros" , Value: IgnoreMacros); |
| 126 | } |
| 127 | |
| 128 | void RedundantSmartptrGetCheck::registerMatchers(MatchFinder *Finder) { |
| 129 | registerMatchersForGetArrowStart(Finder, Callback: this); |
| 130 | registerMatchersForGetEquals(Finder, Callback: this); |
| 131 | } |
| 132 | |
| 133 | namespace { |
| 134 | bool allReturnTypesMatch(const MatchFinder::MatchResult &Result) { |
| 135 | if (Result.Nodes.getNodeAs<Decl>(ID: "duck_typing" ) == nullptr) |
| 136 | return true; |
| 137 | // Verify that the types match. |
| 138 | // We can't do this on the matcher because the type nodes can be different, |
| 139 | // even though they represent the same type. This difference comes from how |
| 140 | // the type is referenced (eg. through a typedef, a type trait, etc). |
| 141 | const Type *OpArrowType = |
| 142 | Result.Nodes.getNodeAs<Type>(ID: "op->Type" )->getUnqualifiedDesugaredType(); |
| 143 | const Type *OpStarType = |
| 144 | Result.Nodes.getNodeAs<Type>(ID: "op*Type" )->getUnqualifiedDesugaredType(); |
| 145 | const Type *GetType = |
| 146 | Result.Nodes.getNodeAs<Type>(ID: "getType" )->getUnqualifiedDesugaredType(); |
| 147 | return OpArrowType == OpStarType && OpArrowType == GetType; |
| 148 | } |
| 149 | } // namespace |
| 150 | |
| 151 | void RedundantSmartptrGetCheck::check(const MatchFinder::MatchResult &Result) { |
| 152 | if (!allReturnTypesMatch(Result)) |
| 153 | return; |
| 154 | |
| 155 | bool IsPtrToPtr = Result.Nodes.getNodeAs<Decl>(ID: "ptr_to_ptr" ) != nullptr; |
| 156 | bool IsMemberExpr = Result.Nodes.getNodeAs<Expr>(ID: "memberExpr" ) != nullptr; |
| 157 | const auto *GetCall = Result.Nodes.getNodeAs<Expr>(ID: "redundant_get" ); |
| 158 | if (GetCall->getBeginLoc().isMacroID() && IgnoreMacros) |
| 159 | return; |
| 160 | |
| 161 | const auto *Smartptr = Result.Nodes.getNodeAs<Expr>(ID: "smart_pointer" ); |
| 162 | |
| 163 | if (IsPtrToPtr && IsMemberExpr) { |
| 164 | // Ignore this case (eg. Foo->get()->DoSomething()); |
| 165 | return; |
| 166 | } |
| 167 | |
| 168 | auto SR = GetCall->getSourceRange(); |
| 169 | // CXXDependentScopeMemberExpr source range does not include parens |
| 170 | // Extend the source range of the get call to account for them. |
| 171 | if (isa<CXXDependentScopeMemberExpr>(Val: GetCall)) |
| 172 | SR.setEnd(Lexer::getLocForEndOfToken(Loc: SR.getEnd(), Offset: 0, SM: *Result.SourceManager, |
| 173 | LangOpts: getLangOpts()) |
| 174 | .getLocWithOffset(1)); |
| 175 | |
| 176 | StringRef SmartptrText = Lexer::getSourceText( |
| 177 | Range: CharSourceRange::getTokenRange(Smartptr->getSourceRange()), |
| 178 | SM: *Result.SourceManager, LangOpts: getLangOpts()); |
| 179 | // Check if the last two characters are "->" and remove them |
| 180 | if (SmartptrText.ends_with(Suffix: "->" )) { |
| 181 | SmartptrText = SmartptrText.drop_back(N: 2); |
| 182 | } |
| 183 | // Replace foo->get() with *foo, and foo.get() with foo. |
| 184 | std::string Replacement = Twine(IsPtrToPtr ? "*" : "" , SmartptrText).str(); |
| 185 | diag(GetCall->getBeginLoc(), "redundant get() call on smart pointer" ) |
| 186 | << FixItHint::CreateReplacement(SR, Replacement); |
| 187 | } |
| 188 | |
| 189 | } // namespace clang::tidy::readability |
| 190 | |