1 //===--- RedundantSmartptrGetCheck.cpp - clang-tidy -----------------------===// 2 // 3 // The LLVM Compiler Infrastructure 4 // 5 // This file is distributed under the University of Illinois Open Source 6 // License. See LICENSE.TXT for details. 7 // 8 //===----------------------------------------------------------------------===// 9 10 #include "RedundantSmartptrGetCheck.h" 11 #include "clang/ASTMatchers/ASTMatchFinder.h" 12 #include "clang/Lex/Lexer.h" 13 14 using namespace clang::ast_matchers; 15 16 namespace clang { 17 namespace tidy { 18 namespace readability { 19 20 namespace { 21 internal::Matcher<Expr> callToGet(const internal::Matcher<Decl> &OnClass) { 22 return cxxMemberCallExpr( 23 on(expr(anyOf(hasType(OnClass), 24 hasType(qualType( 25 pointsTo(decl(OnClass).bind("ptr_to_ptr")))))) 26 .bind("smart_pointer")), 27 unless(callee(memberExpr(hasObjectExpression(cxxThisExpr())))), 28 callee(cxxMethodDecl( 29 hasName("get"), 30 returns(qualType(pointsTo(type().bind("getType"))))))) 31 .bind("redundant_get"); 32 } 33 34 void registerMatchersForGetArrowStart(MatchFinder *Finder, 35 MatchFinder::MatchCallback *Callback) { 36 const auto QuacksLikeASmartptr = recordDecl( 37 recordDecl().bind("duck_typing"), 38 has(cxxMethodDecl(hasName("operator->"), 39 returns(qualType(pointsTo(type().bind("op->Type")))))), 40 has(cxxMethodDecl(hasName("operator*"), returns(qualType(references( 41 type().bind("op*Type"))))))); 42 43 // Catch 'ptr.get()->Foo()' 44 Finder->addMatcher(memberExpr(expr().bind("memberExpr"), isArrow(), 45 hasObjectExpression(ignoringImpCasts( 46 callToGet(QuacksLikeASmartptr)))), 47 Callback); 48 49 // Catch '*ptr.get()' or '*ptr->get()' 50 Finder->addMatcher( 51 unaryOperator(hasOperatorName("*"), 52 hasUnaryOperand(callToGet(QuacksLikeASmartptr))), 53 Callback); 54 55 // Catch '!ptr.get()' 56 const auto CallToGetAsBool = ignoringParenImpCasts(callToGet(recordDecl( 57 QuacksLikeASmartptr, has(cxxConversionDecl(returns(booleanType())))))); 58 Finder->addMatcher( 59 unaryOperator(hasOperatorName("!"), hasUnaryOperand(CallToGetAsBool)), 60 Callback); 61 62 // Catch 'if(ptr.get())' 63 Finder->addMatcher(ifStmt(hasCondition(CallToGetAsBool)), Callback); 64 65 // Catch 'ptr.get() ? X : Y' 66 Finder->addMatcher(conditionalOperator(hasCondition(CallToGetAsBool)), 67 Callback); 68 } 69 70 void registerMatchersForGetEquals(MatchFinder *Finder, 71 MatchFinder::MatchCallback *Callback) { 72 // This one is harder to do with duck typing. 73 // The operator==/!= that we are looking for might be member or non-member, 74 // might be on global namespace or found by ADL, might be a template, etc. 75 // For now, lets keep a list of known standard types. 76 77 const auto IsAKnownSmartptr = 78 recordDecl(hasAnyName("::std::unique_ptr", "::std::shared_ptr")); 79 80 // Matches against nullptr. 81 Finder->addMatcher( 82 binaryOperator(anyOf(hasOperatorName("=="), hasOperatorName("!=")), 83 hasEitherOperand(ignoringImpCasts( 84 anyOf(cxxNullPtrLiteralExpr(), gnuNullExpr(), 85 integerLiteral(equals(0))))), 86 hasEitherOperand(callToGet(IsAKnownSmartptr))), 87 Callback); 88 89 // FIXME: Match and fix if (l.get() == r.get()). 90 } 91 92 } // namespace 93 94 void RedundantSmartptrGetCheck::registerMatchers(MatchFinder *Finder) { 95 // Only register the matchers for C++; the functionality currently does not 96 // provide any benefit to other languages, despite being benign. 97 if (!getLangOpts().CPlusPlus) 98 return; 99 100 registerMatchersForGetArrowStart(Finder, this); 101 registerMatchersForGetEquals(Finder, this); 102 } 103 104 namespace { 105 bool allReturnTypesMatch(const MatchFinder::MatchResult &Result) { 106 if (Result.Nodes.getNodeAs<Decl>("duck_typing") == nullptr) 107 return true; 108 // Verify that the types match. 109 // We can't do this on the matcher because the type nodes can be different, 110 // even though they represent the same type. This difference comes from how 111 // the type is referenced (eg. through a typedef, a type trait, etc). 112 const Type *OpArrowType = 113 Result.Nodes.getNodeAs<Type>("op->Type")->getUnqualifiedDesugaredType(); 114 const Type *OpStarType = 115 Result.Nodes.getNodeAs<Type>("op*Type")->getUnqualifiedDesugaredType(); 116 const Type *GetType = 117 Result.Nodes.getNodeAs<Type>("getType")->getUnqualifiedDesugaredType(); 118 return OpArrowType == OpStarType && OpArrowType == GetType; 119 } 120 } // namespace 121 122 void RedundantSmartptrGetCheck::check(const MatchFinder::MatchResult &Result) { 123 if (!allReturnTypesMatch(Result)) 124 return; 125 126 bool IsPtrToPtr = Result.Nodes.getNodeAs<Decl>("ptr_to_ptr") != nullptr; 127 bool IsMemberExpr = Result.Nodes.getNodeAs<Expr>("memberExpr") != nullptr; 128 const auto *GetCall = Result.Nodes.getNodeAs<Expr>("redundant_get"); 129 const auto *Smartptr = Result.Nodes.getNodeAs<Expr>("smart_pointer"); 130 131 if (IsPtrToPtr && IsMemberExpr) { 132 // Ignore this case (eg. Foo->get()->DoSomething()); 133 return; 134 } 135 136 StringRef SmartptrText = Lexer::getSourceText( 137 CharSourceRange::getTokenRange(Smartptr->getSourceRange()), 138 *Result.SourceManager, getLangOpts()); 139 // Replace foo->get() with *foo, and foo.get() with foo. 140 std::string Replacement = Twine(IsPtrToPtr ? "*" : "", SmartptrText).str(); 141 diag(GetCall->getLocStart(), "redundant get() call on smart pointer") 142 << FixItHint::CreateReplacement(GetCall->getSourceRange(), Replacement); 143 } 144 145 } // namespace readability 146 } // namespace tidy 147 } // namespace clang 148