clang-tools  10.0.0git
MakeMemberFunctionConstCheck.cpp
Go to the documentation of this file.
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 
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 
20 AST_MATCHER(CXXMethodDecl, isStatic) { return Node.isStatic(); }
21 
22 AST_MATCHER(CXXMethodDecl, hasTrivialBody) { return Node.hasTrivialBody(); }
23 
24 AST_MATCHER(CXXRecordDecl, hasAnyDependentBases) {
25  return Node.hasAnyDependentBases();
26 }
27 
28 AST_MATCHER(CXXMethodDecl, isTemplate) {
29  return Node.getTemplatedKind() != FunctionDecl::TK_NonTemplate;
30 }
31 
32 AST_MATCHER(CXXMethodDecl, isDependentContext) {
33  return Node.isDependentContext();
34 }
35 
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 
45 AST_MATCHER_P(CXXMethodDecl, hasCanonicalDecl,
46  ast_matchers::internal::Matcher<CXXMethodDecl>, InnerMatcher) {
47  return InnerMatcher.matches(*Node.getCanonicalDecl(), Finder, Builder);
48 }
49 
51 
52 class FindUsageOfThis : public RecursiveASTVisitor<FindUsageOfThis> {
53  ASTContext &Ctxt;
54 
55 public:
56  FindUsageOfThis(ASTContext &Ctxt) : Ctxt(Ctxt) {}
58 
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 
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 
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
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 *'.
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 
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 
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 
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 
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 
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
const Node * Parent
bool VisitUnresolvedMemberExpr(const UnresolvedMemberExpr *)
bool VisitUser(const MemberExpr *Member, bool OnConstObject)
static SourceLocation getConstInsertionPoint(const CXXMethodDecl *M)
CodeCompletionBuilder Builder
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
const char Usage[]
const Expr * E
AST_MATCHER_P(CXXMethodDecl, hasCanonicalDecl, ast_matchers::internal::Matcher< CXXMethodDecl >, InnerMatcher)