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