blob: 3f1dcfe803e2d6d9316a1b9bc461475ab5252778 [file] [log] [blame]
Matthias Gehre24130d62019-09-22 23:19:41 +02001//===--- MakeMemberFunctionConstCheck.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 "MakeMemberFunctionConstCheck.h"
10#include "clang/AST/ASTContext.h"
Reid Kleckner8a81daa2019-12-09 17:03:47 -080011#include "clang/AST/ParentMapContext.h"
Matthias Gehre24130d62019-09-22 23:19:41 +020012#include "clang/AST/RecursiveASTVisitor.h"
13#include "clang/ASTMatchers/ASTMatchFinder.h"
14
15using namespace clang::ast_matchers;
16
17namespace clang {
18namespace tidy {
19namespace readability {
20
21AST_MATCHER(CXXMethodDecl, isStatic) { return Node.isStatic(); }
22
23AST_MATCHER(CXXMethodDecl, hasTrivialBody) { return Node.hasTrivialBody(); }
24
25AST_MATCHER(CXXRecordDecl, hasAnyDependentBases) {
26 return Node.hasAnyDependentBases();
27}
28
29AST_MATCHER(CXXMethodDecl, isTemplate) {
30 return Node.getTemplatedKind() != FunctionDecl::TK_NonTemplate;
31}
32
33AST_MATCHER(CXXMethodDecl, isDependentContext) {
34 return Node.isDependentContext();
35}
36
37AST_MATCHER(CXXMethodDecl, isInsideMacroDefinition) {
38 const ASTContext &Ctxt = Finder->getASTContext();
39 return clang::Lexer::makeFileCharRange(
40 clang::CharSourceRange::getCharRange(
41 Node.getTypeSourceInfo()->getTypeLoc().getSourceRange()),
42 Ctxt.getSourceManager(), Ctxt.getLangOpts())
43 .isInvalid();
44}
45
46AST_MATCHER_P(CXXMethodDecl, hasCanonicalDecl,
47 ast_matchers::internal::Matcher<CXXMethodDecl>, InnerMatcher) {
48 return InnerMatcher.matches(*Node.getCanonicalDecl(), Finder, Builder);
49}
50
51enum UsageKind { Unused, Const, NonConst };
52
53class FindUsageOfThis : public RecursiveASTVisitor<FindUsageOfThis> {
54 ASTContext &Ctxt;
55
56public:
57 FindUsageOfThis(ASTContext &Ctxt) : Ctxt(Ctxt) {}
58 UsageKind Usage = Unused;
59
60 template <class T> const T *getParent(const Expr *E) {
Reid Kleckner8a81daa2019-12-09 17:03:47 -080061 DynTypedNodeList Parents = Ctxt.getParents(*E);
Matthias Gehre24130d62019-09-22 23:19:41 +020062 if (Parents.size() != 1)
63 return nullptr;
64
65 return Parents.begin()->get<T>();
66 }
67
68 bool VisitUnresolvedMemberExpr(const UnresolvedMemberExpr *) {
69 // An UnresolvedMemberExpr might resolve to a non-const non-static
70 // member function.
71 Usage = NonConst;
72 return false; // Stop traversal.
73 }
74
75 bool VisitCXXConstCastExpr(const CXXConstCastExpr *) {
76 // Workaround to support the pattern
77 // class C {
78 // const S *get() const;
79 // S* get() {
80 // return const_cast<S*>(const_cast<const C*>(this)->get());
81 // }
82 // };
83 // Here, we don't want to make the second 'get' const even though
84 // it only calls a const member function on this.
85 Usage = NonConst;
86 return false; // Stop traversal.
87 }
88
89 // Our AST is
90 // `-ImplicitCastExpr
91 // (possibly `-UnaryOperator Deref)
92 // `-CXXThisExpr 'S *' this
93 bool VisitUser(const ImplicitCastExpr *Cast) {
94 if (Cast->getCastKind() != CK_NoOp)
95 return false; // Stop traversal.
96
97 // Only allow NoOp cast to 'const S' or 'const S *'.
98 QualType QT = Cast->getType();
99 if (QT->isPointerType())
100 QT = QT->getPointeeType();
101
102 if (!QT.isConstQualified())
103 return false; // Stop traversal.
104
105 const auto *Parent = getParent<Stmt>(Cast);
106 if (!Parent)
107 return false; // Stop traversal.
108
109 if (isa<ReturnStmt>(Parent))
110 return true; // return (const S*)this;
111
112 if (isa<CallExpr>(Parent))
113 return true; // use((const S*)this);
114
115 // ((const S*)this)->Member
116 if (const auto *Member = dyn_cast<MemberExpr>(Parent))
117 return VisitUser(Member, /*OnConstObject=*/true);
118
119 return false; // Stop traversal.
120 }
121
122 // If OnConstObject is true, then this is a MemberExpr using
123 // a constant this, i.e. 'const S' or 'const S *'.
124 bool VisitUser(const MemberExpr *Member, bool OnConstObject) {
125 if (Member->isBoundMemberFunction(Ctxt)) {
126 if (!OnConstObject || Member->getFoundDecl().getAccess() != AS_public) {
127 // Non-public non-static member functions might not preserve the
128 // logical costness. E.g. in
129 // class C {
130 // int &data() const;
131 // public:
132 // int &get() { return data(); }
133 // };
134 // get() uses a private const method, but must not be made const
135 // itself.
136 return false; // Stop traversal.
137 }
138 // Using a public non-static const member function.
139 return true;
140 }
141
142 const auto *Parent = getParent<Expr>(Member);
143
144 if (const auto *Cast = dyn_cast_or_null<ImplicitCastExpr>(Parent)) {
145 // A read access to a member is safe when the member either
146 // 1) has builtin type (a 'const int' cannot be modified),
147 // 2) or it's a public member (the pointee of a public 'int * const' can
148 // can be modified by any user of the class).
149 if (Member->getFoundDecl().getAccess() != AS_public &&
150 !Cast->getType()->isBuiltinType())
151 return false;
152
153 if (Cast->getCastKind() == CK_LValueToRValue)
154 return true;
155
156 if (Cast->getCastKind() == CK_NoOp && Cast->getType().isConstQualified())
157 return true;
158 }
159
160 if (const auto *M = dyn_cast_or_null<MemberExpr>(Parent))
161 return VisitUser(M, /*OnConstObject=*/false);
162
163 return false; // Stop traversal.
164 }
165
166 bool VisitCXXThisExpr(const CXXThisExpr *E) {
167 Usage = Const;
168
169 const auto *Parent = getParent<Expr>(E);
170
171 // Look through deref of this.
172 if (const auto *UnOp = dyn_cast_or_null<UnaryOperator>(Parent)) {
173 if (UnOp->getOpcode() == UO_Deref) {
174 Parent = getParent<Expr>(UnOp);
175 }
176 }
177
178 // It's okay to
179 // return (const S*)this;
180 // use((const S*)this);
181 // ((const S*)this)->f()
182 // when 'f' is a public member function.
183 if (const auto *Cast = dyn_cast_or_null<ImplicitCastExpr>(Parent)) {
184 if (VisitUser(Cast))
185 return true;
186
187 // And it's also okay to
188 // (const T)(S->t)
189 // (LValueToRValue)(S->t)
190 // when 't' is either of builtin type or a public member.
191 } else if (const auto *Member = dyn_cast_or_null<MemberExpr>(Parent)) {
192 if (VisitUser(Member, /*OnConstObject=*/false))
193 return true;
194 }
195
196 // Unknown user of this.
197 Usage = NonConst;
198 return false; // Stop traversal.
199 }
200};
201
202AST_MATCHER(CXXMethodDecl, usesThisAsConst) {
203 FindUsageOfThis UsageOfThis(Finder->getASTContext());
204
205 // TraverseStmt does not modify its argument.
206 UsageOfThis.TraverseStmt(const_cast<Stmt *>(Node.getBody()));
207
208 return UsageOfThis.Usage == Const;
209}
210
211void MakeMemberFunctionConstCheck::registerMatchers(MatchFinder *Finder) {
212 if (!getLangOpts().CPlusPlus)
213 return;
214
215 Finder->addMatcher(
216 cxxMethodDecl(
217 isDefinition(), isUserProvided(),
218 unless(anyOf(
219 isExpansionInSystemHeader(), isVirtual(), isConst(), isStatic(),
220 hasTrivialBody(), cxxConstructorDecl(), cxxDestructorDecl(),
221 isTemplate(), isDependentContext(),
222 ofClass(anyOf(
223 isLambda(),
224 hasAnyDependentBases()) // Method might become virtual
225 // depending on template base class.
226 ),
227 isInsideMacroDefinition(),
228 hasCanonicalDecl(isInsideMacroDefinition()))),
229 usesThisAsConst())
230 .bind("x"),
231 this);
232}
233
234static SourceLocation getConstInsertionPoint(const CXXMethodDecl *M) {
235 TypeSourceInfo *TSI = M->getTypeSourceInfo();
236 if (!TSI)
237 return {};
238
239 FunctionTypeLoc FTL =
240 TSI->getTypeLoc().IgnoreParens().getAs<FunctionTypeLoc>();
241 if (!FTL)
242 return {};
243
244 return FTL.getRParenLoc().getLocWithOffset(1);
245}
246
247void MakeMemberFunctionConstCheck::check(
248 const MatchFinder::MatchResult &Result) {
249 const auto *Definition = Result.Nodes.getNodeAs<CXXMethodDecl>("x");
250
251 auto Declaration = Definition->getCanonicalDecl();
252
253 auto Diag = diag(Definition->getLocation(), "method %0 can be made const")
254 << Definition
255 << FixItHint::CreateInsertion(getConstInsertionPoint(Definition),
256 " const");
257 if (Declaration != Definition) {
258 Diag << FixItHint::CreateInsertion(getConstInsertionPoint(Declaration),
259 " const");
260 }
261}
262
263} // namespace readability
264} // namespace tidy
265} // namespace clang