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