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 {
19 namespace tidy {
20 namespace readability {
21 
AST_MATCHER(CXXMethodDecl,isStatic)22 AST_MATCHER(CXXMethodDecl, isStatic) { return Node.isStatic(); }
23 
AST_MATCHER(CXXMethodDecl,hasTrivialBody)24 AST_MATCHER(CXXMethodDecl, hasTrivialBody) { return Node.hasTrivialBody(); }
25 
AST_MATCHER(CXXRecordDecl,hasAnyDependentBases)26 AST_MATCHER(CXXRecordDecl, hasAnyDependentBases) {
27   return Node.hasAnyDependentBases();
28 }
29 
AST_MATCHER(CXXMethodDecl,isTemplate)30 AST_MATCHER(CXXMethodDecl, isTemplate) {
31   return Node.getTemplatedKind() != FunctionDecl::TK_NonTemplate;
32 }
33 
AST_MATCHER(CXXMethodDecl,isDependentContext)34 AST_MATCHER(CXXMethodDecl, isDependentContext) {
35   return Node.isDependentContext();
36 }
37 
AST_MATCHER(CXXMethodDecl,isInsideMacroDefinition)38 AST_MATCHER(CXXMethodDecl, isInsideMacroDefinition) {
39   const ASTContext &Ctxt = Finder->getASTContext();
40   return clang::Lexer::makeFileCharRange(
41              clang::CharSourceRange::getCharRange(
42                  Node.getTypeSourceInfo()->getTypeLoc().getSourceRange()),
43              Ctxt.getSourceManager(), Ctxt.getLangOpts())
44       .isInvalid();
45 }
46 
AST_MATCHER_P(CXXMethodDecl,hasCanonicalDecl,ast_matchers::internal::Matcher<CXXMethodDecl>,InnerMatcher)47 AST_MATCHER_P(CXXMethodDecl, hasCanonicalDecl,
48               ast_matchers::internal::Matcher<CXXMethodDecl>, InnerMatcher) {
49   return InnerMatcher.matches(*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:
FindUsageOfThis(ASTContext & Ctxt)58   FindUsageOfThis(ASTContext &Ctxt) : Ctxt(Ctxt) {}
59   UsageKind Usage = Unused;
60 
getParent(const Expr * E)61   template <class T> const T *getParent(const Expr *E) {
62     DynTypedNodeList Parents = Ctxt.getParents(*E);
63     if (Parents.size() != 1)
64       return nullptr;
65 
66     return Parents.begin()->get<T>();
67   }
68 
getParentExprIgnoreParens(const Expr * E)69   const Expr *getParentExprIgnoreParens(const Expr *E) {
70     const Expr *Parent = getParent<Expr>(E);
71     while (isa_and_nonnull<ParenExpr>(Parent))
72       Parent = getParent<Expr>(Parent);
73     return Parent;
74   }
75 
VisitUnresolvedMemberExpr(const UnresolvedMemberExpr *)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 
VisitCXXConstCastExpr(const CXXConstCastExpr *)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
visitUser(const ImplicitCastExpr * Cast)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 *'.
visitUser(const MemberExpr * Member,bool OnConstObject)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 
VisitCXXThisExpr(const CXXThisExpr * E)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(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 
AST_MATCHER(CXXMethodDecl,usesThisAsConst)210 AST_MATCHER(CXXMethodDecl, usesThisAsConst) {
211   FindUsageOfThis UsageOfThis(Finder->getASTContext());
212 
213   // TraverseStmt does not modify its argument.
214   UsageOfThis.TraverseStmt(const_cast<Stmt *>(Node.getBody()));
215 
216   return UsageOfThis.Usage == Const;
217 }
218 
registerMatchers(MatchFinder * Finder)219 void MakeMemberFunctionConstCheck::registerMatchers(MatchFinder *Finder) {
220   Finder->addMatcher(
221       traverse(
222           TK_AsIs,
223           cxxMethodDecl(
224               isDefinition(), isUserProvided(),
225               unless(anyOf(
226                   isExpansionInSystemHeader(), isVirtual(), isConst(),
227                   isStatic(), hasTrivialBody(), cxxConstructorDecl(),
228                   cxxDestructorDecl(), isTemplate(), isDependentContext(),
229                   ofClass(anyOf(isLambda(),
230                                 hasAnyDependentBases()) // Method might become
231                                                         // virtual depending on
232                                                         // template base class.
233                           ),
234                   isInsideMacroDefinition(),
235                   hasCanonicalDecl(isInsideMacroDefinition()))),
236               usesThisAsConst())
237               .bind("x")),
238       this);
239 }
240 
getConstInsertionPoint(const CXXMethodDecl * M)241 static SourceLocation getConstInsertionPoint(const CXXMethodDecl *M) {
242   TypeSourceInfo *TSI = M->getTypeSourceInfo();
243   if (!TSI)
244     return {};
245 
246   FunctionTypeLoc FTL =
247       TSI->getTypeLoc().IgnoreParens().getAs<FunctionTypeLoc>();
248   if (!FTL)
249     return {};
250 
251   return FTL.getRParenLoc().getLocWithOffset(1);
252 }
253 
check(const MatchFinder::MatchResult & Result)254 void MakeMemberFunctionConstCheck::check(
255     const MatchFinder::MatchResult &Result) {
256   const auto *Definition = Result.Nodes.getNodeAs<CXXMethodDecl>("x");
257 
258   const auto *Declaration = Definition->getCanonicalDecl();
259 
260   auto Diag = diag(Definition->getLocation(), "method %0 can be made const")
261               << Definition
262               << FixItHint::CreateInsertion(getConstInsertionPoint(Definition),
263                                             " const");
264   if (Declaration != Definition) {
265     Diag << FixItHint::CreateInsertion(getConstInsertionPoint(Declaration),
266                                        " const");
267   }
268 }
269 
270 } // namespace readability
271 } // namespace tidy
272 } // namespace clang
273