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