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