clang-tools 22.0.0git
RedundantSmartptrGetCheck.cpp
Go to the documentation of this file.
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-exception
6//
7//===----------------------------------------------------------------------===//
8
10#include "clang/ASTMatchers/ASTMatchFinder.h"
11#include "clang/Lex/Lexer.h"
12
13using namespace clang::ast_matchers;
14
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 void
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))),
117 Callback);
118
119 // FIXME: Match and fix if (l.get() == r.get()).
120}
121
124 Options.store(Opts, "IgnoreMacros", IgnoreMacros);
125}
126
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 how
138 // 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 bool IsPtrToPtr = Result.Nodes.getNodeAs<Decl>("ptr_to_ptr") != nullptr;
153 bool IsMemberExpr = Result.Nodes.getNodeAs<Expr>("memberExpr") != nullptr;
154 const auto *GetCall = Result.Nodes.getNodeAs<Expr>("redundant_get");
155 if (GetCall->getBeginLoc().isMacroID() && IgnoreMacros)
156 return;
157
158 const auto *Smartptr = Result.Nodes.getNodeAs<Expr>("smart_pointer");
159
160 if (IsPtrToPtr && IsMemberExpr) {
161 // Ignore this case (eg. Foo->get()->DoSomething());
162 return;
163 }
164
165 auto SR = GetCall->getSourceRange();
166 // CXXDependentScopeMemberExpr source range does not include parens
167 // Extend the source range of the get call to account for them.
168 if (isa<CXXDependentScopeMemberExpr>(GetCall))
169 SR.setEnd(Lexer::getLocForEndOfToken(SR.getEnd(), 0, *Result.SourceManager,
170 getLangOpts())
171 .getLocWithOffset(1));
172
173 StringRef SmartptrText = Lexer::getSourceText(
174 CharSourceRange::getTokenRange(Smartptr->getSourceRange()),
175 *Result.SourceManager, getLangOpts());
176 // Check if the last two characters are "->" and remove them
177 if (SmartptrText.ends_with("->")) {
178 SmartptrText = SmartptrText.drop_back(2);
179 }
180 // Replace foo->get() with *foo, and foo.get() with foo.
181 std::string Replacement = Twine(IsPtrToPtr ? "*" : "", SmartptrText).str();
182 diag(GetCall->getBeginLoc(), "redundant get() call on smart pointer")
183 << FixItHint::CreateReplacement(SR, Replacement);
184}
185
186} // namespace clang::tidy::readability
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
void registerMatchers(ast_matchers::MatchFinder *Finder) override
static internal::Matcher< Expr > callToGet(const internal::Matcher< Decl > &OnClass)
static internal::Matcher< Decl > knownSmartptr()
static void registerMatchersForGetArrowStart(MatchFinder *Finder, MatchFinder::MatchCallback *Callback)
static void registerMatchersForGetEquals(MatchFinder *Finder, MatchFinder::MatchCallback *Callback)
static bool allReturnTypesMatch(const MatchFinder::MatchResult &Result)
llvm::StringMap< ClangTidyValue > OptionMap