189 lines · cpp
1//===----------------------------------------------------------------------===//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-exception6//7//===----------------------------------------------------------------------===//8 9#include "RedundantSmartptrGetCheck.h"10#include "clang/ASTMatchers/ASTMatchFinder.h"11#include "clang/Lex/Lexer.h"12 13using namespace clang::ast_matchers;14 15namespace clang::tidy::readability {16 17static internal::Matcher<Expr>18callToGet(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 46static internal::Matcher<Decl> knownSmartptr() {47 return recordDecl(hasAnyName("::std::unique_ptr", "::std::shared_ptr"));48}49 50static void51registerMatchersForGetArrowStart(MatchFinder *Finder,52 MatchFinder::MatchCallback *Callback) {53 const auto MatchesOpArrow =54 allOf(hasName("operator->"),55 returns(qualType(pointsTo(type().bind("op->Type")))));56 const auto MatchesOpStar =57 allOf(hasName("operator*"),58 returns(qualType(references(type().bind("op*Type")))));59 const auto HasRelevantOps =60 allOf(anyOf(hasMethod(MatchesOpArrow),61 has(functionTemplateDecl(has(functionDecl(MatchesOpArrow))))),62 anyOf(hasMethod(MatchesOpStar),63 has(functionTemplateDecl(has(functionDecl(MatchesOpStar))))));64 65 const auto QuacksLikeASmartptr =66 cxxRecordDecl(cxxRecordDecl().bind("duck_typing"), HasRelevantOps);67 68 // Make sure we are not missing the known standard types.69 const auto SmartptrAny = anyOf(knownSmartptr(), QuacksLikeASmartptr);70 const auto SmartptrWithDeref = anyOf(71 cxxRecordDecl(knownSmartptr(), HasRelevantOps), QuacksLikeASmartptr);72 73 // Catch 'ptr.get()->Foo()'74 Finder->addMatcher(75 memberExpr(expr().bind("memberExpr"), isArrow(),76 hasObjectExpression(callToGet(SmartptrWithDeref))),77 Callback);78 79 // Catch '*ptr.get()' or '*ptr->get()'80 Finder->addMatcher(81 unaryOperator(hasOperatorName("*"),82 hasUnaryOperand(callToGet(SmartptrWithDeref))),83 Callback);84 85 // Catch '!ptr.get()'86 const auto CallToGetAsBool = callToGet(87 recordDecl(SmartptrAny, has(cxxConversionDecl(returns(booleanType())))));88 Finder->addMatcher(89 unaryOperator(hasOperatorName("!"), hasUnaryOperand(CallToGetAsBool)),90 Callback);91 92 // Catch 'if(ptr.get())'93 Finder->addMatcher(ifStmt(hasCondition(CallToGetAsBool)), Callback);94 95 // Catch 'ptr.get() ? X : Y'96 Finder->addMatcher(conditionalOperator(hasCondition(CallToGetAsBool)),97 Callback);98 99 Finder->addMatcher(cxxDependentScopeMemberExpr(hasObjectExpression(100 callExpr(has(callToGet(SmartptrAny))))),101 Callback);102}103 104static void registerMatchersForGetEquals(MatchFinder *Finder,105 MatchFinder::MatchCallback *Callback) {106 // This one is harder to do with duck typing.107 // The operator==/!= that we are looking for might be member or non-member,108 // might be on global namespace or found by ADL, might be a template, etc.109 // For now, lets keep it to the known standard types.110 111 // Matches against nullptr.112 Finder->addMatcher(113 binaryOperator(hasAnyOperatorName("==", "!="),114 hasOperands(anyOf(cxxNullPtrLiteralExpr(), gnuNullExpr(),115 integerLiteral(equals(0))),116 callToGet(knownSmartptr()))),117 Callback);118 119 // FIXME: Match and fix if (l.get() == r.get()).120}121 122void RedundantSmartptrGetCheck::storeOptions(123 ClangTidyOptions::OptionMap &Opts) {124 Options.store(Opts, "IgnoreMacros", IgnoreMacros);125}126 127void RedundantSmartptrGetCheck::registerMatchers(MatchFinder *Finder) {128 registerMatchersForGetArrowStart(Finder, this);129 registerMatchersForGetEquals(Finder, this);130}131 132static bool allReturnTypesMatch(const MatchFinder::MatchResult &Result) {133 if (Result.Nodes.getNodeAs<Decl>("duck_typing") == nullptr)134 return true;135 // Verify that the types match.136 // We can't do this on the matcher because the type nodes can be different,137 // even though they represent the same type. This difference comes from how138 // the type is referenced (eg. through a typedef, a type trait, etc).139 const Type *OpArrowType =140 Result.Nodes.getNodeAs<Type>("op->Type")->getUnqualifiedDesugaredType();141 const Type *OpStarType =142 Result.Nodes.getNodeAs<Type>("op*Type")->getUnqualifiedDesugaredType();143 const Type *GetType =144 Result.Nodes.getNodeAs<Type>("getType")->getUnqualifiedDesugaredType();145 return OpArrowType == OpStarType && OpArrowType == GetType;146}147 148void RedundantSmartptrGetCheck::check(const MatchFinder::MatchResult &Result) {149 if (!allReturnTypesMatch(Result))150 return;151 152 const bool IsPtrToPtr = Result.Nodes.getNodeAs<Decl>("ptr_to_ptr") != nullptr;153 const bool IsMemberExpr =154 Result.Nodes.getNodeAs<Expr>("memberExpr") != nullptr;155 const auto *GetCall = Result.Nodes.getNodeAs<Expr>("redundant_get");156 if (GetCall->getBeginLoc().isMacroID() && IgnoreMacros)157 return;158 159 const auto *Smartptr = Result.Nodes.getNodeAs<Expr>("smart_pointer");160 161 if (IsPtrToPtr && IsMemberExpr) {162 // Ignore this case (eg. Foo->get()->DoSomething());163 return;164 }165 166 auto SR = GetCall->getSourceRange();167 // CXXDependentScopeMemberExpr source range does not include parens168 // Extend the source range of the get call to account for them.169 if (isa<CXXDependentScopeMemberExpr>(GetCall))170 SR.setEnd(Lexer::getLocForEndOfToken(SR.getEnd(), 0, *Result.SourceManager,171 getLangOpts())172 .getLocWithOffset(1));173 174 StringRef SmartptrText = Lexer::getSourceText(175 CharSourceRange::getTokenRange(Smartptr->getSourceRange()),176 *Result.SourceManager, getLangOpts());177 // Check if the last two characters are "->" and remove them178 if (SmartptrText.ends_with("->")) {179 SmartptrText = SmartptrText.drop_back(2);180 }181 // Replace foo->get() with *foo, and foo.get() with foo.182 const std::string Replacement =183 Twine(IsPtrToPtr ? "*" : "", SmartptrText).str();184 diag(GetCall->getBeginLoc(), "redundant get() call on smart pointer")185 << FixItHint::CreateReplacement(SR, Replacement);186}187 188} // namespace clang::tidy::readability189