124130d66SMatthias Gehre //===--- MakeMemberFunctionConstCheck.cpp - clang-tidy --------------------===//
224130d66SMatthias Gehre //
324130d66SMatthias Gehre // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
424130d66SMatthias Gehre // See https://llvm.org/LICENSE.txt for license information.
524130d66SMatthias Gehre // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
624130d66SMatthias Gehre //
724130d66SMatthias Gehre //===----------------------------------------------------------------------===//
824130d66SMatthias Gehre
924130d66SMatthias Gehre #include "MakeMemberFunctionConstCheck.h"
1024130d66SMatthias Gehre #include "clang/AST/ASTContext.h"
118a81daaaSReid Kleckner #include "clang/AST/ParentMapContext.h"
1224130d66SMatthias Gehre #include "clang/AST/RecursiveASTVisitor.h"
1324130d66SMatthias Gehre #include "clang/ASTMatchers/ASTMatchFinder.h"
14860aefd0SNathan James #include "clang/Lex/Lexer.h"
1524130d66SMatthias Gehre
1624130d66SMatthias Gehre using namespace clang::ast_matchers;
1724130d66SMatthias Gehre
187d2ea6c4SCarlos Galvez namespace clang::tidy::readability {
1924130d66SMatthias Gehre
AST_MATCHER(CXXMethodDecl,isStatic)20*11a411a4SPiotr Zegar AST_MATCHER(CXXMethodDecl, isStatic) { return Node.isStatic(); }
21*11a411a4SPiotr Zegar
AST_MATCHER(CXXMethodDecl,hasTrivialBody)22*11a411a4SPiotr Zegar AST_MATCHER(CXXMethodDecl, hasTrivialBody) { return Node.hasTrivialBody(); }
23*11a411a4SPiotr Zegar
AST_MATCHER(CXXRecordDecl,hasAnyDependentBases)24*11a411a4SPiotr Zegar AST_MATCHER(CXXRecordDecl, hasAnyDependentBases) {
25*11a411a4SPiotr Zegar return Node.hasAnyDependentBases();
26*11a411a4SPiotr Zegar }
27*11a411a4SPiotr Zegar
AST_MATCHER(CXXMethodDecl,isTemplate)28*11a411a4SPiotr Zegar AST_MATCHER(CXXMethodDecl, isTemplate) {
29*11a411a4SPiotr Zegar return Node.getTemplatedKind() != FunctionDecl::TK_NonTemplate;
30*11a411a4SPiotr Zegar }
31*11a411a4SPiotr Zegar
AST_MATCHER(CXXMethodDecl,isDependentContext)32*11a411a4SPiotr Zegar AST_MATCHER(CXXMethodDecl, isDependentContext) {
33*11a411a4SPiotr Zegar return Node.isDependentContext();
34*11a411a4SPiotr Zegar }
35*11a411a4SPiotr Zegar
AST_MATCHER(CXXMethodDecl,isInsideMacroDefinition)36*11a411a4SPiotr Zegar AST_MATCHER(CXXMethodDecl, isInsideMacroDefinition) {
37*11a411a4SPiotr Zegar const ASTContext &Ctxt = Finder->getASTContext();
38*11a411a4SPiotr Zegar return clang::Lexer::makeFileCharRange(
39*11a411a4SPiotr Zegar clang::CharSourceRange::getCharRange(
40*11a411a4SPiotr Zegar Node.getTypeSourceInfo()->getTypeLoc().getSourceRange()),
41*11a411a4SPiotr Zegar Ctxt.getSourceManager(), Ctxt.getLangOpts())
42*11a411a4SPiotr Zegar .isInvalid();
43*11a411a4SPiotr Zegar }
44*11a411a4SPiotr Zegar
AST_MATCHER_P(CXXMethodDecl,hasCanonicalDecl,ast_matchers::internal::Matcher<CXXMethodDecl>,InnerMatcher)45*11a411a4SPiotr Zegar AST_MATCHER_P(CXXMethodDecl, hasCanonicalDecl,
46*11a411a4SPiotr Zegar ast_matchers::internal::Matcher<CXXMethodDecl>, InnerMatcher) {
47*11a411a4SPiotr Zegar return InnerMatcher.matches(*Node.getCanonicalDecl(), Finder, Builder);
48*11a411a4SPiotr Zegar }
49*11a411a4SPiotr Zegar
5024130d66SMatthias Gehre enum UsageKind { Unused, Const, NonConst };
5124130d66SMatthias Gehre
5224130d66SMatthias Gehre class FindUsageOfThis : public RecursiveASTVisitor<FindUsageOfThis> {
5324130d66SMatthias Gehre ASTContext &Ctxt;
5424130d66SMatthias Gehre
5524130d66SMatthias Gehre public:
FindUsageOfThis(ASTContext & Ctxt)5624130d66SMatthias Gehre FindUsageOfThis(ASTContext &Ctxt) : Ctxt(Ctxt) {}
5724130d66SMatthias Gehre UsageKind Usage = Unused;
5824130d66SMatthias Gehre
getParent(const Expr * E)5924130d66SMatthias Gehre template <class T> const T *getParent(const Expr *E) {
608a81daaaSReid Kleckner DynTypedNodeList Parents = Ctxt.getParents(*E);
6124130d66SMatthias Gehre if (Parents.size() != 1)
6224130d66SMatthias Gehre return nullptr;
6324130d66SMatthias Gehre
6424130d66SMatthias Gehre return Parents.begin()->get<T>();
6524130d66SMatthias Gehre }
6624130d66SMatthias Gehre
getParentExprIgnoreParens(const Expr * E)67fc354d37SEvgeny Shulgin const Expr *getParentExprIgnoreParens(const Expr *E) {
68fc354d37SEvgeny Shulgin const Expr *Parent = getParent<Expr>(E);
69fc354d37SEvgeny Shulgin while (isa_and_nonnull<ParenExpr>(Parent))
70fc354d37SEvgeny Shulgin Parent = getParent<Expr>(Parent);
71fc354d37SEvgeny Shulgin return Parent;
72fc354d37SEvgeny Shulgin }
73fc354d37SEvgeny Shulgin
VisitUnresolvedMemberExpr(const UnresolvedMemberExpr *)7424130d66SMatthias Gehre bool VisitUnresolvedMemberExpr(const UnresolvedMemberExpr *) {
7524130d66SMatthias Gehre // An UnresolvedMemberExpr might resolve to a non-const non-static
7624130d66SMatthias Gehre // member function.
7724130d66SMatthias Gehre Usage = NonConst;
7824130d66SMatthias Gehre return false; // Stop traversal.
7924130d66SMatthias Gehre }
8024130d66SMatthias Gehre
VisitCXXConstCastExpr(const CXXConstCastExpr *)8124130d66SMatthias Gehre bool VisitCXXConstCastExpr(const CXXConstCastExpr *) {
8224130d66SMatthias Gehre // Workaround to support the pattern
8324130d66SMatthias Gehre // class C {
8424130d66SMatthias Gehre // const S *get() const;
8524130d66SMatthias Gehre // S* get() {
8624130d66SMatthias Gehre // return const_cast<S*>(const_cast<const C*>(this)->get());
8724130d66SMatthias Gehre // }
8824130d66SMatthias Gehre // };
8924130d66SMatthias Gehre // Here, we don't want to make the second 'get' const even though
9024130d66SMatthias Gehre // it only calls a const member function on this.
9124130d66SMatthias Gehre Usage = NonConst;
9224130d66SMatthias Gehre return false; // Stop traversal.
9324130d66SMatthias Gehre }
9424130d66SMatthias Gehre
9524130d66SMatthias Gehre // Our AST is
9624130d66SMatthias Gehre // `-ImplicitCastExpr
9724130d66SMatthias Gehre // (possibly `-UnaryOperator Deref)
9824130d66SMatthias Gehre // `-CXXThisExpr 'S *' this
visitUser(const ImplicitCastExpr * Cast)99ab2d3ce4SAlexander Kornienko bool visitUser(const ImplicitCastExpr *Cast) {
10024130d66SMatthias Gehre if (Cast->getCastKind() != CK_NoOp)
10124130d66SMatthias Gehre return false; // Stop traversal.
10224130d66SMatthias Gehre
10324130d66SMatthias Gehre // Only allow NoOp cast to 'const S' or 'const S *'.
10424130d66SMatthias Gehre QualType QT = Cast->getType();
10524130d66SMatthias Gehre if (QT->isPointerType())
10624130d66SMatthias Gehre QT = QT->getPointeeType();
10724130d66SMatthias Gehre
10824130d66SMatthias Gehre if (!QT.isConstQualified())
10924130d66SMatthias Gehre return false; // Stop traversal.
11024130d66SMatthias Gehre
11124130d66SMatthias Gehre const auto *Parent = getParent<Stmt>(Cast);
11224130d66SMatthias Gehre if (!Parent)
11324130d66SMatthias Gehre return false; // Stop traversal.
11424130d66SMatthias Gehre
11524130d66SMatthias Gehre if (isa<ReturnStmt>(Parent))
11624130d66SMatthias Gehre return true; // return (const S*)this;
11724130d66SMatthias Gehre
11824130d66SMatthias Gehre if (isa<CallExpr>(Parent))
11924130d66SMatthias Gehre return true; // use((const S*)this);
12024130d66SMatthias Gehre
12124130d66SMatthias Gehre // ((const S*)this)->Member
12224130d66SMatthias Gehre if (const auto *Member = dyn_cast<MemberExpr>(Parent))
123ab2d3ce4SAlexander Kornienko return visitUser(Member, /*OnConstObject=*/true);
12424130d66SMatthias Gehre
12524130d66SMatthias Gehre return false; // Stop traversal.
12624130d66SMatthias Gehre }
12724130d66SMatthias Gehre
12824130d66SMatthias Gehre // If OnConstObject is true, then this is a MemberExpr using
12924130d66SMatthias Gehre // a constant this, i.e. 'const S' or 'const S *'.
visitUser(const MemberExpr * Member,bool OnConstObject)130ab2d3ce4SAlexander Kornienko bool visitUser(const MemberExpr *Member, bool OnConstObject) {
13124130d66SMatthias Gehre if (Member->isBoundMemberFunction(Ctxt)) {
13224130d66SMatthias Gehre if (!OnConstObject || Member->getFoundDecl().getAccess() != AS_public) {
13324130d66SMatthias Gehre // Non-public non-static member functions might not preserve the
134dd5571d5SKazuaki Ishizaki // logical constness. E.g. in
13524130d66SMatthias Gehre // class C {
13624130d66SMatthias Gehre // int &data() const;
13724130d66SMatthias Gehre // public:
13824130d66SMatthias Gehre // int &get() { return data(); }
13924130d66SMatthias Gehre // };
14024130d66SMatthias Gehre // get() uses a private const method, but must not be made const
14124130d66SMatthias Gehre // itself.
14224130d66SMatthias Gehre return false; // Stop traversal.
14324130d66SMatthias Gehre }
14424130d66SMatthias Gehre // Using a public non-static const member function.
14524130d66SMatthias Gehre return true;
14624130d66SMatthias Gehre }
14724130d66SMatthias Gehre
148fc354d37SEvgeny Shulgin const auto *Parent = getParentExprIgnoreParens(Member);
14924130d66SMatthias Gehre
15024130d66SMatthias Gehre if (const auto *Cast = dyn_cast_or_null<ImplicitCastExpr>(Parent)) {
15124130d66SMatthias Gehre // A read access to a member is safe when the member either
15224130d66SMatthias Gehre // 1) has builtin type (a 'const int' cannot be modified),
15324130d66SMatthias Gehre // 2) or it's a public member (the pointee of a public 'int * const' can
15424130d66SMatthias Gehre // can be modified by any user of the class).
15524130d66SMatthias Gehre if (Member->getFoundDecl().getAccess() != AS_public &&
15624130d66SMatthias Gehre !Cast->getType()->isBuiltinType())
15724130d66SMatthias Gehre return false;
15824130d66SMatthias Gehre
15924130d66SMatthias Gehre if (Cast->getCastKind() == CK_LValueToRValue)
16024130d66SMatthias Gehre return true;
16124130d66SMatthias Gehre
16224130d66SMatthias Gehre if (Cast->getCastKind() == CK_NoOp && Cast->getType().isConstQualified())
16324130d66SMatthias Gehre return true;
16424130d66SMatthias Gehre }
16524130d66SMatthias Gehre
16624130d66SMatthias Gehre if (const auto *M = dyn_cast_or_null<MemberExpr>(Parent))
167ab2d3ce4SAlexander Kornienko return visitUser(M, /*OnConstObject=*/false);
16824130d66SMatthias Gehre
16924130d66SMatthias Gehre return false; // Stop traversal.
17024130d66SMatthias Gehre }
17124130d66SMatthias Gehre
VisitCXXThisExpr(const CXXThisExpr * E)17224130d66SMatthias Gehre bool VisitCXXThisExpr(const CXXThisExpr *E) {
17324130d66SMatthias Gehre Usage = Const;
17424130d66SMatthias Gehre
175fc354d37SEvgeny Shulgin const auto *Parent = getParentExprIgnoreParens(E);
17624130d66SMatthias Gehre
17724130d66SMatthias Gehre // Look through deref of this.
17824130d66SMatthias Gehre if (const auto *UnOp = dyn_cast_or_null<UnaryOperator>(Parent)) {
17924130d66SMatthias Gehre if (UnOp->getOpcode() == UO_Deref) {
180fc354d37SEvgeny Shulgin Parent = getParentExprIgnoreParens(UnOp);
18124130d66SMatthias Gehre }
18224130d66SMatthias Gehre }
18324130d66SMatthias Gehre
18424130d66SMatthias Gehre // It's okay to
18524130d66SMatthias Gehre // return (const S*)this;
18624130d66SMatthias Gehre // use((const S*)this);
18724130d66SMatthias Gehre // ((const S*)this)->f()
18824130d66SMatthias Gehre // when 'f' is a public member function.
18924130d66SMatthias Gehre if (const auto *Cast = dyn_cast_or_null<ImplicitCastExpr>(Parent)) {
190ab2d3ce4SAlexander Kornienko if (visitUser(Cast))
19124130d66SMatthias Gehre return true;
19224130d66SMatthias Gehre
19324130d66SMatthias Gehre // And it's also okay to
19424130d66SMatthias Gehre // (const T)(S->t)
19524130d66SMatthias Gehre // (LValueToRValue)(S->t)
19624130d66SMatthias Gehre // when 't' is either of builtin type or a public member.
19724130d66SMatthias Gehre } else if (const auto *Member = dyn_cast_or_null<MemberExpr>(Parent)) {
198ab2d3ce4SAlexander Kornienko if (visitUser(Member, /*OnConstObject=*/false))
19924130d66SMatthias Gehre return true;
20024130d66SMatthias Gehre }
20124130d66SMatthias Gehre
20224130d66SMatthias Gehre // Unknown user of this.
20324130d66SMatthias Gehre Usage = NonConst;
20424130d66SMatthias Gehre return false; // Stop traversal.
20524130d66SMatthias Gehre }
20624130d66SMatthias Gehre };
20724130d66SMatthias Gehre
AST_MATCHER(CXXMethodDecl,usesThisAsConst)20824130d66SMatthias Gehre AST_MATCHER(CXXMethodDecl, usesThisAsConst) {
20924130d66SMatthias Gehre FindUsageOfThis UsageOfThis(Finder->getASTContext());
21024130d66SMatthias Gehre
21124130d66SMatthias Gehre // TraverseStmt does not modify its argument.
21224130d66SMatthias Gehre UsageOfThis.TraverseStmt(const_cast<Stmt *>(Node.getBody()));
21324130d66SMatthias Gehre
21424130d66SMatthias Gehre return UsageOfThis.Usage == Const;
21524130d66SMatthias Gehre }
21624130d66SMatthias Gehre
registerMatchers(MatchFinder * Finder)21724130d66SMatthias Gehre void MakeMemberFunctionConstCheck::registerMatchers(MatchFinder *Finder) {
21824130d66SMatthias Gehre Finder->addMatcher(
219a72307c3SStephen Kelly traverse(
220027899daSAlexander Kornienko TK_AsIs,
22124130d66SMatthias Gehre cxxMethodDecl(
22224130d66SMatthias Gehre isDefinition(), isUserProvided(),
22324130d66SMatthias Gehre unless(anyOf(
224a72307c3SStephen Kelly isExpansionInSystemHeader(), isVirtual(), isConst(),
225*11a411a4SPiotr Zegar isStatic(), hasTrivialBody(), cxxConstructorDecl(),
226*11a411a4SPiotr Zegar cxxDestructorDecl(), isTemplate(), isDependentContext(),
227*11a411a4SPiotr Zegar ofClass(anyOf(isLambda(),
228*11a411a4SPiotr Zegar hasAnyDependentBases()) // Method might become
229a72307c3SStephen Kelly // virtual depending on
230a72307c3SStephen Kelly // template base class.
23124130d66SMatthias Gehre ),
232*11a411a4SPiotr Zegar isInsideMacroDefinition(),
233*11a411a4SPiotr Zegar hasCanonicalDecl(isInsideMacroDefinition()))),
23424130d66SMatthias Gehre usesThisAsConst())
235a72307c3SStephen Kelly .bind("x")),
23624130d66SMatthias Gehre this);
23724130d66SMatthias Gehre }
23824130d66SMatthias Gehre
getConstInsertionPoint(const CXXMethodDecl * M)23924130d66SMatthias Gehre static SourceLocation getConstInsertionPoint(const CXXMethodDecl *M) {
24024130d66SMatthias Gehre TypeSourceInfo *TSI = M->getTypeSourceInfo();
24124130d66SMatthias Gehre if (!TSI)
24224130d66SMatthias Gehre return {};
24324130d66SMatthias Gehre
244fc2a9ad1SPiotr Zegar auto FTL = TSI->getTypeLoc().IgnoreParens().getAs<FunctionTypeLoc>();
24524130d66SMatthias Gehre if (!FTL)
24624130d66SMatthias Gehre return {};
24724130d66SMatthias Gehre
24824130d66SMatthias Gehre return FTL.getRParenLoc().getLocWithOffset(1);
24924130d66SMatthias Gehre }
25024130d66SMatthias Gehre
check(const MatchFinder::MatchResult & Result)25124130d66SMatthias Gehre void MakeMemberFunctionConstCheck::check(
25224130d66SMatthias Gehre const MatchFinder::MatchResult &Result) {
25324130d66SMatthias Gehre const auto *Definition = Result.Nodes.getNodeAs<CXXMethodDecl>("x");
25424130d66SMatthias Gehre
255ab2d3ce4SAlexander Kornienko const auto *Declaration = Definition->getCanonicalDecl();
25624130d66SMatthias Gehre
25724130d66SMatthias Gehre auto Diag = diag(Definition->getLocation(), "method %0 can be made const")
25824130d66SMatthias Gehre << Definition
25924130d66SMatthias Gehre << FixItHint::CreateInsertion(getConstInsertionPoint(Definition),
26024130d66SMatthias Gehre " const");
26124130d66SMatthias Gehre if (Declaration != Definition) {
26224130d66SMatthias Gehre Diag << FixItHint::CreateInsertion(getConstInsertionPoint(Declaration),
26324130d66SMatthias Gehre " const");
26424130d66SMatthias Gehre }
26524130d66SMatthias Gehre }
26624130d66SMatthias Gehre
2677d2ea6c4SCarlos Galvez } // namespace clang::tidy::readability
268