| Alexander Kornienko | 1b677db | 2015-03-09 12:18:39 +0000 | [diff] [blame] | 1 | //===--- RedundantSmartptrGetCheck.cpp - clang-tidy -----------------------===// | 
| Samuel Benzaquen | 3a57101 | 2014-03-27 17:42:26 +0000 | [diff] [blame] | 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 |  | 
| Alexander Kornienko | 1b677db | 2015-03-09 12:18:39 +0000 | [diff] [blame] | 10 | #include "RedundantSmartptrGetCheck.h" | 
| Samuel Benzaquen | 3a57101 | 2014-03-27 17:42:26 +0000 | [diff] [blame] | 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 { | 
| Alexander Kornienko | 35ddae4 | 2014-10-15 10:51:57 +0000 | [diff] [blame] | 18 | namespace readability { | 
| Samuel Benzaquen | 3a57101 | 2014-03-27 17:42:26 +0000 | [diff] [blame] | 19 |  | 
| Samuel Benzaquen | 110f3cc | 2014-04-09 14:17:23 +0000 | [diff] [blame] | 20 | namespace { | 
| Benjamin Kramer | 51a9cc9 | 2016-06-15 15:46:10 +0000 | [diff] [blame] | 21 | internal::Matcher<Expr> callToGet(const internal::Matcher<Decl> &OnClass) { | 
| Aaron Ballman | b9ea09c | 2015-09-17 13:31:25 +0000 | [diff] [blame] | 22 | return cxxMemberCallExpr( | 
| Samuel Benzaquen | 110f3cc | 2014-04-09 14:17:23 +0000 | [diff] [blame] | 23 | on(expr(anyOf(hasType(OnClass), | 
| Aaron Ballman | b9ea09c | 2015-09-17 13:31:25 +0000 | [diff] [blame] | 24 | hasType(qualType( | 
|  | 25 | pointsTo(decl(OnClass).bind("ptr_to_ptr")))))) | 
|  | 26 | .bind("smart_pointer")), | 
|  | 27 | unless(callee(memberExpr(hasObjectExpression(cxxThisExpr())))), | 
| Samuel Benzaquen | 1fb8bc7 | 2016-02-17 16:13:14 +0000 | [diff] [blame] | 28 | callee(cxxMethodDecl( | 
|  | 29 | hasName("get"), | 
|  | 30 | returns(qualType(pointsTo(type().bind("getType"))))))) | 
| Aaron Ballman | b9ea09c | 2015-09-17 13:31:25 +0000 | [diff] [blame] | 31 | .bind("redundant_get"); | 
| Samuel Benzaquen | 110f3cc | 2014-04-09 14:17:23 +0000 | [diff] [blame] | 32 | } | 
|  | 33 |  | 
|  | 34 | void registerMatchersForGetArrowStart(MatchFinder *Finder, | 
|  | 35 | MatchFinder::MatchCallback *Callback) { | 
| Samuel Benzaquen | 3a57101 | 2014-03-27 17:42:26 +0000 | [diff] [blame] | 36 | const auto QuacksLikeASmartptr = recordDecl( | 
| Samuel Benzaquen | 110f3cc | 2014-04-09 14:17:23 +0000 | [diff] [blame] | 37 | recordDecl().bind("duck_typing"), | 
| Aaron Ballman | b9ea09c | 2015-09-17 13:31:25 +0000 | [diff] [blame] | 38 | has(cxxMethodDecl(hasName("operator->"), | 
|  | 39 | returns(qualType(pointsTo(type().bind("op->Type")))))), | 
| Samuel Benzaquen | 1fb8bc7 | 2016-02-17 16:13:14 +0000 | [diff] [blame] | 40 | has(cxxMethodDecl(hasName("operator*"), returns(qualType(references( | 
|  | 41 | type().bind("op*Type"))))))); | 
| Samuel Benzaquen | 3a57101 | 2014-03-27 17:42:26 +0000 | [diff] [blame] | 42 |  | 
| Samuel Benzaquen | 3a57101 | 2014-03-27 17:42:26 +0000 | [diff] [blame] | 43 | // Catch 'ptr.get()->Foo()' | 
| Samuel Benzaquen | 110f3cc | 2014-04-09 14:17:23 +0000 | [diff] [blame] | 44 | Finder->addMatcher(memberExpr(expr().bind("memberExpr"), isArrow(), | 
|  | 45 | hasObjectExpression(ignoringImpCasts( | 
|  | 46 | callToGet(QuacksLikeASmartptr)))), | 
|  | 47 | Callback); | 
| Samuel Benzaquen | 3a57101 | 2014-03-27 17:42:26 +0000 | [diff] [blame] | 48 |  | 
| Samuel Benzaquen | 110f3cc | 2014-04-09 14:17:23 +0000 | [diff] [blame] | 49 | // Catch '*ptr.get()' or '*ptr->get()' | 
| Samuel Benzaquen | 3a57101 | 2014-03-27 17:42:26 +0000 | [diff] [blame] | 50 | Finder->addMatcher( | 
| Samuel Benzaquen | 110f3cc | 2014-04-09 14:17:23 +0000 | [diff] [blame] | 51 | unaryOperator(hasOperatorName("*"), | 
|  | 52 | hasUnaryOperand(callToGet(QuacksLikeASmartptr))), | 
|  | 53 | Callback); | 
| Samuel Benzaquen | c814872 | 2018-01-15 18:03:20 +0000 | [diff] [blame^] | 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); | 
| Samuel Benzaquen | 110f3cc | 2014-04-09 14:17:23 +0000 | [diff] [blame] | 68 | } | 
| Samuel Benzaquen | 3a57101 | 2014-03-27 17:42:26 +0000 | [diff] [blame] | 69 |  | 
| Samuel Benzaquen | 110f3cc | 2014-04-09 14:17:23 +0000 | [diff] [blame] | 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 |  | 
| Kirill Bobyrev | 9559f04 | 2016-09-26 07:22:37 +0000 | [diff] [blame] | 77 | const auto IsAKnownSmartptr = | 
|  | 78 | recordDecl(hasAnyName("::std::unique_ptr", "::std::shared_ptr")); | 
| Samuel Benzaquen | 110f3cc | 2014-04-09 14:17:23 +0000 | [diff] [blame] | 79 |  | 
|  | 80 | // Matches against nullptr. | 
| Samuel Benzaquen | 3a57101 | 2014-03-27 17:42:26 +0000 | [diff] [blame] | 81 | Finder->addMatcher( | 
| Kirill Bobyrev | 9559f04 | 2016-09-26 07:22:37 +0000 | [diff] [blame] | 82 | binaryOperator(anyOf(hasOperatorName("=="), hasOperatorName("!=")), | 
|  | 83 | hasEitherOperand(ignoringImpCasts( | 
|  | 84 | anyOf(cxxNullPtrLiteralExpr(), gnuNullExpr(), | 
|  | 85 | integerLiteral(equals(0))))), | 
|  | 86 | hasEitherOperand(callToGet(IsAKnownSmartptr))), | 
| Samuel Benzaquen | 110f3cc | 2014-04-09 14:17:23 +0000 | [diff] [blame] | 87 | Callback); | 
| Kirill Bobyrev | 9559f04 | 2016-09-26 07:22:37 +0000 | [diff] [blame] | 88 |  | 
| Kirill Bobyrev | 9559f04 | 2016-09-26 07:22:37 +0000 | [diff] [blame] | 89 | // FIXME: Match and fix if (l.get() == r.get()). | 
| Samuel Benzaquen | 110f3cc | 2014-04-09 14:17:23 +0000 | [diff] [blame] | 90 | } | 
|  | 91 |  | 
| Kirill Bobyrev | 9559f04 | 2016-09-26 07:22:37 +0000 | [diff] [blame] | 92 | } // namespace | 
| Samuel Benzaquen | 110f3cc | 2014-04-09 14:17:23 +0000 | [diff] [blame] | 93 |  | 
| Alexander Kornienko | 1b677db | 2015-03-09 12:18:39 +0000 | [diff] [blame] | 94 | void RedundantSmartptrGetCheck::registerMatchers(MatchFinder *Finder) { | 
| Aaron Ballman | 1f1b067 | 2015-09-02 16:05:21 +0000 | [diff] [blame] | 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 |  | 
| Samuel Benzaquen | 110f3cc | 2014-04-09 14:17:23 +0000 | [diff] [blame] | 100 | registerMatchersForGetArrowStart(Finder, this); | 
|  | 101 | registerMatchersForGetEquals(Finder, this); | 
| Samuel Benzaquen | 3a57101 | 2014-03-27 17:42:26 +0000 | [diff] [blame] | 102 | } | 
|  | 103 |  | 
|  | 104 | namespace { | 
|  | 105 | bool allReturnTypesMatch(const MatchFinder::MatchResult &Result) { | 
| Samuel Benzaquen | 110f3cc | 2014-04-09 14:17:23 +0000 | [diff] [blame] | 106 | if (Result.Nodes.getNodeAs<Decl>("duck_typing") == nullptr) | 
|  | 107 | return true; | 
| Samuel Benzaquen | 3a57101 | 2014-03-27 17:42:26 +0000 | [diff] [blame] | 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 | } | 
| Kirill Bobyrev | 9559f04 | 2016-09-26 07:22:37 +0000 | [diff] [blame] | 120 | } // namespace | 
| Samuel Benzaquen | 3a57101 | 2014-03-27 17:42:26 +0000 | [diff] [blame] | 121 |  | 
| Alexander Kornienko | 1b677db | 2015-03-09 12:18:39 +0000 | [diff] [blame] | 122 | void RedundantSmartptrGetCheck::check(const MatchFinder::MatchResult &Result) { | 
| Kirill Bobyrev | 9559f04 | 2016-09-26 07:22:37 +0000 | [diff] [blame] | 123 | if (!allReturnTypesMatch(Result)) | 
|  | 124 | return; | 
| Samuel Benzaquen | 3a57101 | 2014-03-27 17:42:26 +0000 | [diff] [blame] | 125 |  | 
| Samuel Benzaquen | 110f3cc | 2014-04-09 14:17:23 +0000 | [diff] [blame] | 126 | bool IsPtrToPtr = Result.Nodes.getNodeAs<Decl>("ptr_to_ptr") != nullptr; | 
|  | 127 | bool IsMemberExpr = Result.Nodes.getNodeAs<Expr>("memberExpr") != nullptr; | 
| Piotr Padlewski | 08124b1 | 2016-12-14 15:29:23 +0000 | [diff] [blame] | 128 | const auto *GetCall = Result.Nodes.getNodeAs<Expr>("redundant_get"); | 
|  | 129 | const auto *Smartptr = Result.Nodes.getNodeAs<Expr>("smart_pointer"); | 
| Samuel Benzaquen | 3a57101 | 2014-03-27 17:42:26 +0000 | [diff] [blame] | 130 |  | 
| Samuel Benzaquen | 110f3cc | 2014-04-09 14:17:23 +0000 | [diff] [blame] | 131 | if (IsPtrToPtr && IsMemberExpr) { | 
|  | 132 | // Ignore this case (eg. Foo->get()->DoSomething()); | 
|  | 133 | return; | 
|  | 134 | } | 
|  | 135 |  | 
| Samuel Benzaquen | 3a57101 | 2014-03-27 17:42:26 +0000 | [diff] [blame] | 136 | StringRef SmartptrText = Lexer::getSourceText( | 
|  | 137 | CharSourceRange::getTokenRange(Smartptr->getSourceRange()), | 
| Gabor Horvath | afad84c | 2016-09-24 02:13:45 +0000 | [diff] [blame] | 138 | *Result.SourceManager, getLangOpts()); | 
| Samuel Benzaquen | 110f3cc | 2014-04-09 14:17:23 +0000 | [diff] [blame] | 139 | // Replace foo->get() with *foo, and foo.get() with foo. | 
| Samuel Benzaquen | 3a57101 | 2014-03-27 17:42:26 +0000 | [diff] [blame] | 140 | std::string Replacement = Twine(IsPtrToPtr ? "*" : "", SmartptrText).str(); | 
| Alexander Kornienko | b1b2f87 | 2016-01-08 15:21:40 +0000 | [diff] [blame] | 141 | diag(GetCall->getLocStart(), "redundant get() call on smart pointer") | 
| Samuel Benzaquen | 3a57101 | 2014-03-27 17:42:26 +0000 | [diff] [blame] | 142 | << FixItHint::CreateReplacement(GetCall->getSourceRange(), Replacement); | 
|  | 143 | } | 
|  | 144 |  | 
| Alexander Kornienko | 35ddae4 | 2014-10-15 10:51:57 +0000 | [diff] [blame] | 145 | } // namespace readability | 
| Samuel Benzaquen | 3a57101 | 2014-03-27 17:42:26 +0000 | [diff] [blame] | 146 | } // namespace tidy | 
|  | 147 | } // namespace clang |