| //===--- MakeMemberFunctionConstCheck.cpp - clang-tidy --------------------===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "MakeMemberFunctionConstCheck.h" |
| #include "clang/AST/ASTContext.h" |
| #include "clang/AST/ParentMapContext.h" |
| #include "clang/AST/RecursiveASTVisitor.h" |
| #include "clang/ASTMatchers/ASTMatchFinder.h" |
| #include "clang/Lex/Lexer.h" |
| |
| using namespace clang::ast_matchers; |
| |
| namespace clang { |
| namespace tidy { |
| namespace readability { |
| |
| AST_MATCHER(CXXMethodDecl, isStatic) { return Node.isStatic(); } |
| |
| AST_MATCHER(CXXMethodDecl, hasTrivialBody) { return Node.hasTrivialBody(); } |
| |
| AST_MATCHER(CXXRecordDecl, hasAnyDependentBases) { |
| return Node.hasAnyDependentBases(); |
| } |
| |
| AST_MATCHER(CXXMethodDecl, isTemplate) { |
| return Node.getTemplatedKind() != FunctionDecl::TK_NonTemplate; |
| } |
| |
| AST_MATCHER(CXXMethodDecl, isDependentContext) { |
| return Node.isDependentContext(); |
| } |
| |
| AST_MATCHER(CXXMethodDecl, isInsideMacroDefinition) { |
| const ASTContext &Ctxt = Finder->getASTContext(); |
| return clang::Lexer::makeFileCharRange( |
| clang::CharSourceRange::getCharRange( |
| Node.getTypeSourceInfo()->getTypeLoc().getSourceRange()), |
| Ctxt.getSourceManager(), Ctxt.getLangOpts()) |
| .isInvalid(); |
| } |
| |
| AST_MATCHER_P(CXXMethodDecl, hasCanonicalDecl, |
| ast_matchers::internal::Matcher<CXXMethodDecl>, InnerMatcher) { |
| return InnerMatcher.matches(*Node.getCanonicalDecl(), Finder, Builder); |
| } |
| |
| enum UsageKind { Unused, Const, NonConst }; |
| |
| class FindUsageOfThis : public RecursiveASTVisitor<FindUsageOfThis> { |
| ASTContext &Ctxt; |
| |
| public: |
| FindUsageOfThis(ASTContext &Ctxt) : Ctxt(Ctxt) {} |
| UsageKind Usage = Unused; |
| |
| template <class T> const T *getParent(const Expr *E) { |
| DynTypedNodeList Parents = Ctxt.getParents(*E); |
| if (Parents.size() != 1) |
| return nullptr; |
| |
| return Parents.begin()->get<T>(); |
| } |
| |
| bool VisitUnresolvedMemberExpr(const UnresolvedMemberExpr *) { |
| // An UnresolvedMemberExpr might resolve to a non-const non-static |
| // member function. |
| Usage = NonConst; |
| return false; // Stop traversal. |
| } |
| |
| bool VisitCXXConstCastExpr(const CXXConstCastExpr *) { |
| // Workaround to support the pattern |
| // class C { |
| // const S *get() const; |
| // S* get() { |
| // return const_cast<S*>(const_cast<const C*>(this)->get()); |
| // } |
| // }; |
| // Here, we don't want to make the second 'get' const even though |
| // it only calls a const member function on this. |
| Usage = NonConst; |
| return false; // Stop traversal. |
| } |
| |
| // Our AST is |
| // `-ImplicitCastExpr |
| // (possibly `-UnaryOperator Deref) |
| // `-CXXThisExpr 'S *' this |
| bool visitUser(const ImplicitCastExpr *Cast) { |
| if (Cast->getCastKind() != CK_NoOp) |
| return false; // Stop traversal. |
| |
| // Only allow NoOp cast to 'const S' or 'const S *'. |
| QualType QT = Cast->getType(); |
| if (QT->isPointerType()) |
| QT = QT->getPointeeType(); |
| |
| if (!QT.isConstQualified()) |
| return false; // Stop traversal. |
| |
| const auto *Parent = getParent<Stmt>(Cast); |
| if (!Parent) |
| return false; // Stop traversal. |
| |
| if (isa<ReturnStmt>(Parent)) |
| return true; // return (const S*)this; |
| |
| if (isa<CallExpr>(Parent)) |
| return true; // use((const S*)this); |
| |
| // ((const S*)this)->Member |
| if (const auto *Member = dyn_cast<MemberExpr>(Parent)) |
| return visitUser(Member, /*OnConstObject=*/true); |
| |
| return false; // Stop traversal. |
| } |
| |
| // If OnConstObject is true, then this is a MemberExpr using |
| // a constant this, i.e. 'const S' or 'const S *'. |
| bool visitUser(const MemberExpr *Member, bool OnConstObject) { |
| if (Member->isBoundMemberFunction(Ctxt)) { |
| if (!OnConstObject || Member->getFoundDecl().getAccess() != AS_public) { |
| // Non-public non-static member functions might not preserve the |
| // logical constness. E.g. in |
| // class C { |
| // int &data() const; |
| // public: |
| // int &get() { return data(); } |
| // }; |
| // get() uses a private const method, but must not be made const |
| // itself. |
| return false; // Stop traversal. |
| } |
| // Using a public non-static const member function. |
| return true; |
| } |
| |
| const auto *Parent = getParent<Expr>(Member); |
| |
| if (const auto *Cast = dyn_cast_or_null<ImplicitCastExpr>(Parent)) { |
| // A read access to a member is safe when the member either |
| // 1) has builtin type (a 'const int' cannot be modified), |
| // 2) or it's a public member (the pointee of a public 'int * const' can |
| // can be modified by any user of the class). |
| if (Member->getFoundDecl().getAccess() != AS_public && |
| !Cast->getType()->isBuiltinType()) |
| return false; |
| |
| if (Cast->getCastKind() == CK_LValueToRValue) |
| return true; |
| |
| if (Cast->getCastKind() == CK_NoOp && Cast->getType().isConstQualified()) |
| return true; |
| } |
| |
| if (const auto *M = dyn_cast_or_null<MemberExpr>(Parent)) |
| return visitUser(M, /*OnConstObject=*/false); |
| |
| return false; // Stop traversal. |
| } |
| |
| bool VisitCXXThisExpr(const CXXThisExpr *E) { |
| Usage = Const; |
| |
| const auto *Parent = getParent<Expr>(E); |
| |
| // Look through deref of this. |
| if (const auto *UnOp = dyn_cast_or_null<UnaryOperator>(Parent)) { |
| if (UnOp->getOpcode() == UO_Deref) { |
| Parent = getParent<Expr>(UnOp); |
| } |
| } |
| |
| // It's okay to |
| // return (const S*)this; |
| // use((const S*)this); |
| // ((const S*)this)->f() |
| // when 'f' is a public member function. |
| if (const auto *Cast = dyn_cast_or_null<ImplicitCastExpr>(Parent)) { |
| if (visitUser(Cast)) |
| return true; |
| |
| // And it's also okay to |
| // (const T)(S->t) |
| // (LValueToRValue)(S->t) |
| // when 't' is either of builtin type or a public member. |
| } else if (const auto *Member = dyn_cast_or_null<MemberExpr>(Parent)) { |
| if (visitUser(Member, /*OnConstObject=*/false)) |
| return true; |
| } |
| |
| // Unknown user of this. |
| Usage = NonConst; |
| return false; // Stop traversal. |
| } |
| }; |
| |
| AST_MATCHER(CXXMethodDecl, usesThisAsConst) { |
| FindUsageOfThis UsageOfThis(Finder->getASTContext()); |
| |
| // TraverseStmt does not modify its argument. |
| UsageOfThis.TraverseStmt(const_cast<Stmt *>(Node.getBody())); |
| |
| return UsageOfThis.Usage == Const; |
| } |
| |
| void MakeMemberFunctionConstCheck::registerMatchers(MatchFinder *Finder) { |
| Finder->addMatcher( |
| traverse( |
| TK_AsIs, |
| cxxMethodDecl( |
| isDefinition(), isUserProvided(), |
| unless(anyOf( |
| isExpansionInSystemHeader(), isVirtual(), isConst(), |
| isStatic(), hasTrivialBody(), cxxConstructorDecl(), |
| cxxDestructorDecl(), isTemplate(), isDependentContext(), |
| ofClass(anyOf(isLambda(), |
| hasAnyDependentBases()) // Method might become |
| // virtual depending on |
| // template base class. |
| ), |
| isInsideMacroDefinition(), |
| hasCanonicalDecl(isInsideMacroDefinition()))), |
| usesThisAsConst()) |
| .bind("x")), |
| this); |
| } |
| |
| static SourceLocation getConstInsertionPoint(const CXXMethodDecl *M) { |
| TypeSourceInfo *TSI = M->getTypeSourceInfo(); |
| if (!TSI) |
| return {}; |
| |
| FunctionTypeLoc FTL = |
| TSI->getTypeLoc().IgnoreParens().getAs<FunctionTypeLoc>(); |
| if (!FTL) |
| return {}; |
| |
| return FTL.getRParenLoc().getLocWithOffset(1); |
| } |
| |
| void MakeMemberFunctionConstCheck::check( |
| const MatchFinder::MatchResult &Result) { |
| const auto *Definition = Result.Nodes.getNodeAs<CXXMethodDecl>("x"); |
| |
| const auto *Declaration = Definition->getCanonicalDecl(); |
| |
| auto Diag = diag(Definition->getLocation(), "method %0 can be made const") |
| << Definition |
| << FixItHint::CreateInsertion(getConstInsertionPoint(Definition), |
| " const"); |
| if (Declaration != Definition) { |
| Diag << FixItHint::CreateInsertion(getConstInsertionPoint(Declaration), |
| " const"); |
| } |
| } |
| |
| } // namespace readability |
| } // namespace tidy |
| } // namespace clang |