1 //===--- MakeMemberFunctionConstCheck.cpp - clang-tidy --------------------===//
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
7 //===----------------------------------------------------------------------===//
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"
16 using namespace clang::ast_matchers
;
18 namespace clang::tidy::readability
{
20 AST_MATCHER(CXXMethodDecl
, isStatic
) { return Node
.isStatic(); }
22 AST_MATCHER(CXXMethodDecl
, hasTrivialBody
) { return Node
.hasTrivialBody(); }
24 AST_MATCHER(CXXRecordDecl
, hasAnyDependentBases
) {
25 return Node
.hasAnyDependentBases();
28 AST_MATCHER(CXXMethodDecl
, isTemplate
) {
29 return Node
.getTemplatedKind() != FunctionDecl::TK_NonTemplate
;
32 AST_MATCHER(CXXMethodDecl
, isDependentContext
) {
33 return Node
.isDependentContext();
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())
45 AST_MATCHER_P(CXXMethodDecl
, hasCanonicalDecl
,
46 ast_matchers::internal::Matcher
<CXXMethodDecl
>, InnerMatcher
) {
47 return InnerMatcher
.matches(*Node
.getCanonicalDecl(), Finder
, Builder
);
50 enum UsageKind
{ Unused
, Const
, NonConst
};
52 class FindUsageOfThis
: public RecursiveASTVisitor
<FindUsageOfThis
> {
56 FindUsageOfThis(ASTContext
&Ctxt
) : Ctxt(Ctxt
) {}
57 UsageKind Usage
= Unused
;
59 template <class T
> const T
*getParent(const Expr
*E
) {
60 DynTypedNodeList Parents
= Ctxt
.getParents(*E
);
61 if (Parents
.size() != 1)
64 return Parents
.begin()->get
<T
>();
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
);
74 bool VisitUnresolvedMemberExpr(const UnresolvedMemberExpr
*) {
75 // An UnresolvedMemberExpr might resolve to a non-const non-static
78 return false; // Stop traversal.
81 bool VisitCXXConstCastExpr(const CXXConstCastExpr
*) {
82 // Workaround to support the pattern
84 // const S *get() const;
86 // return const_cast<S*>(const_cast<const C*>(this)->get());
89 // Here, we don't want to make the second 'get' const even though
90 // it only calls a const member function on this.
92 return false; // Stop traversal.
97 // (possibly `-UnaryOperator Deref)
98 // `-CXXThisExpr 'S *' this
99 bool visitUser(const ImplicitCastExpr
*Cast
) {
100 if (Cast
->getCastKind() != CK_NoOp
)
101 return false; // Stop traversal.
103 // Only allow NoOp cast to 'const S' or 'const S *'.
104 QualType QT
= Cast
->getType();
105 if (QT
->isPointerType())
106 QT
= QT
->getPointeeType();
108 if (!QT
.isConstQualified())
109 return false; // Stop traversal.
111 const auto *Parent
= getParent
<Stmt
>(Cast
);
113 return false; // Stop traversal.
115 if (isa
<ReturnStmt
>(Parent
))
116 return true; // return (const S*)this;
118 if (isa
<CallExpr
>(Parent
))
119 return true; // use((const S*)this);
121 // ((const S*)this)->Member
122 if (const auto *Member
= dyn_cast
<MemberExpr
>(Parent
))
123 return visitUser(Member
, /*OnConstObject=*/true);
125 return false; // Stop traversal.
128 // If OnConstObject is true, then this is a MemberExpr using
129 // a constant this, i.e. 'const S' or 'const S *'.
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
136 // int &data() const;
138 // int &get() { return data(); }
140 // get() uses a private const method, but must not be made const
142 return false; // Stop traversal.
144 // Using a public non-static const member function.
148 const auto *Parent
= getParentExprIgnoreParens(Member
);
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())
159 if (Cast
->getCastKind() == CK_LValueToRValue
)
162 if (Cast
->getCastKind() == CK_NoOp
&& Cast
->getType().isConstQualified())
166 if (const auto *M
= dyn_cast_or_null
<MemberExpr
>(Parent
))
167 return visitUser(M
, /*OnConstObject=*/false);
169 return false; // Stop traversal.
172 bool VisitCXXThisExpr(const CXXThisExpr
*E
) {
175 const auto *Parent
= getParentExprIgnoreParens(E
);
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
);
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
)) {
193 // And it's also okay to
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))
202 // Unknown user of this.
204 return false; // Stop traversal.
208 AST_MATCHER(CXXMethodDecl
, usesThisAsConst
) {
209 FindUsageOfThis
UsageOfThis(Finder
->getASTContext());
211 // TraverseStmt does not modify its argument.
212 UsageOfThis
.TraverseStmt(const_cast<Stmt
*>(Node
.getBody()));
214 return UsageOfThis
.Usage
== Const
;
217 void MakeMemberFunctionConstCheck::registerMatchers(MatchFinder
*Finder
) {
222 isDefinition(), isUserProvided(),
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.
232 isInsideMacroDefinition(),
233 hasCanonicalDecl(isInsideMacroDefinition()))),
239 static SourceLocation
getConstInsertionPoint(const CXXMethodDecl
*M
) {
240 TypeSourceInfo
*TSI
= M
->getTypeSourceInfo();
244 auto FTL
= TSI
->getTypeLoc().IgnoreParens().getAs
<FunctionTypeLoc
>();
248 return FTL
.getRParenLoc().getLocWithOffset(1);
251 void MakeMemberFunctionConstCheck::check(
252 const MatchFinder::MatchResult
&Result
) {
253 const auto *Definition
= Result
.Nodes
.getNodeAs
<CXXMethodDecl
>("x");
255 const auto *Declaration
= Definition
->getCanonicalDecl();
257 auto Diag
= diag(Definition
->getLocation(), "method %0 can be made const")
259 << FixItHint::CreateInsertion(getConstInsertionPoint(Definition
),
261 if (Declaration
!= Definition
) {
262 Diag
<< FixItHint::CreateInsertion(getConstInsertionPoint(Declaration
),
267 } // namespace clang::tidy::readability