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

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

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