clang-tools  15.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/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 
22 AST_MATCHER(CXXMethodDecl, isStatic) { return Node.isStatic(); }
23 
24 AST_MATCHER(CXXMethodDecl, hasTrivialBody) { return Node.hasTrivialBody(); }
25 
26 AST_MATCHER(CXXRecordDecl, hasAnyDependentBases) {
27  return Node.hasAnyDependentBases();
28 }
29 
30 AST_MATCHER(CXXMethodDecl, isTemplate) {
31  return Node.getTemplatedKind() != FunctionDecl::TK_NonTemplate;
32 }
33 
34 AST_MATCHER(CXXMethodDecl, isDependentContext) {
35  return Node.isDependentContext();
36 }
37 
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 
47 AST_MATCHER_P(CXXMethodDecl, hasCanonicalDecl,
48  ast_matchers::internal::Matcher<CXXMethodDecl>, InnerMatcher) {
49  return InnerMatcher.matches(*Node.getCanonicalDecl(), Finder, Builder);
50 }
51 
53 
54 class FindUsageOfThis : public RecursiveASTVisitor<FindUsageOfThis> {
55  ASTContext &Ctxt;
56 
57 public:
58  FindUsageOfThis(ASTContext &Ctxt) : Ctxt(Ctxt) {}
60 
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 
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 
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 
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
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 *'.
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 
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 
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 
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 
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 
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
E
const Expr * E
Definition: AvoidBindCheck.cpp:88
Usage
const char Usage[]
Definition: ClangReorderFields.cpp:50
clang::clangd::check
bool check(llvm::StringRef File, llvm::Optional< Range > LineRange, const ThreadsafeFS &TFS, const ClangdLSPServer::Options &Opts, bool EnableCodeCompletion)
Definition: Check.cpp:273
clang::tidy::readability::FindUsageOfThis::Usage
UsageKind Usage
Definition: MakeMemberFunctionConstCheck.cpp:59
clang::tidy::readability::FindUsageOfThis::VisitCXXConstCastExpr
bool VisitCXXConstCastExpr(const CXXConstCastExpr *)
Definition: MakeMemberFunctionConstCheck.cpp:83
clang::tidy::readability::FindUsageOfThis::visitUser
bool visitUser(const MemberExpr *Member, bool OnConstObject)
Definition: MakeMemberFunctionConstCheck.cpp:132
clang::tidy::readability::FindUsageOfThis::VisitUnresolvedMemberExpr
bool VisitUnresolvedMemberExpr(const UnresolvedMemberExpr *)
Definition: MakeMemberFunctionConstCheck.cpp:76
clang::ast_matchers
Definition: AbseilMatcher.h:14
clang::tidy::readability::getConstInsertionPoint
static SourceLocation getConstInsertionPoint(const CXXMethodDecl *M)
Definition: MakeMemberFunctionConstCheck.cpp:241
M
const google::protobuf::Message & M
Definition: Server.cpp:309
clang::tidy::readability::FindUsageOfThis::FindUsageOfThis
FindUsageOfThis(ASTContext &Ctxt)
Definition: MakeMemberFunctionConstCheck.cpp:58
Builder
CodeCompletionBuilder Builder
Definition: CodeCompletionStringsTests.cpp:36
clang::tidy::readability::Unused
@ Unused
Definition: MakeMemberFunctionConstCheck.cpp:52
clang::tidy::readability::FindUsageOfThis
Definition: MakeMemberFunctionConstCheck.cpp:54
clang::ast_matchers::AST_MATCHER
AST_MATCHER(Expr, isMacroID)
Definition: PreferIsaOrDynCastInConditionalsCheck.cpp:19
Parent
const Node * Parent
Definition: ExtractFunction.cpp:157
clang::tidy::readability::Const
@ Const
Definition: MakeMemberFunctionConstCheck.cpp:52
clang
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
Definition: ApplyReplacements.h:27
clang::tidy::readability::FindUsageOfThis::VisitCXXThisExpr
bool VisitCXXThisExpr(const CXXThisExpr *E)
Definition: MakeMemberFunctionConstCheck.cpp:174
MakeMemberFunctionConstCheck.h
clang::tidy::readability::NonConst
@ NonConst
Definition: MakeMemberFunctionConstCheck.cpp:52
clang::tidy::readability::UsageKind
UsageKind
Definition: MakeMemberFunctionConstCheck.cpp:52
clang::tidy::bugprone::AST_MATCHER_P
AST_MATCHER_P(FunctionDecl, parameterCountGE, unsigned, N)
Matches functions that have at least the specified amount of parameters.
Definition: EasilySwappableParametersCheck.cpp:1888
clang::tidy::readability::FindUsageOfThis::visitUser
bool visitUser(const ImplicitCastExpr *Cast)
Definition: MakeMemberFunctionConstCheck.cpp:101