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

source code of clang-tools-extra/clang-tidy/readability/MakeMemberFunctionConstCheck.cpp