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