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