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::tidy::readability { |
19 | |
20 | namespace { |
21 | |
22 | AST_MATCHER(CXXMethodDecl, isStatic) { return Node.isStatic(); } |
23 | |
24 | AST_MATCHER(CXXMethodDecl, hasTrivialBody) { return Node.hasTrivialBody(); } |
25 | |
26 | AST_MATCHER(CXXRecordDecl, hasAnyDependentBases) { |
27 | return Node.hasAnyDependentBases(); |
28 | } |
29 | |
30 | AST_MATCHER(CXXMethodDecl, isTemplate) { |
31 | return Node.getTemplatedKind() != FunctionDecl::TK_NonTemplate; |
32 | } |
33 | |
34 | AST_MATCHER(CXXMethodDecl, isDependentContext) { |
35 | return Node.isDependentContext(); |
36 | } |
37 | |
38 | AST_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 | |
47 | AST_MATCHER_P(CXXMethodDecl, hasCanonicalDecl, |
48 | ast_matchers::internal::Matcher<CXXMethodDecl>, InnerMatcher) { |
49 | return InnerMatcher.matches(Node: *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: |
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 | |
210 | AST_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 | |
221 | void 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 | |
243 | static 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 | |
255 | void 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 |
Definitions
- isStatic
- hasTrivialBody
- hasAnyDependentBases
- isTemplate
- isDependentContext
- isInsideMacroDefinition
- hasCanonicalDecl
- UsageKind
- FindUsageOfThis
- FindUsageOfThis
- getParent
- getParentExprIgnoreParens
- VisitUnresolvedMemberExpr
- VisitCXXConstCastExpr
- visitUser
- visitUser
- VisitCXXThisExpr
- usesThisAsConst
- registerMatchers
- getConstInsertionPoint
Learn to use CMake with our Intro Training
Find out more