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