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