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