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