diff options
author | thegeorg <thegeorg@yandex-team.com> | 2024-03-13 13:58:24 +0300 |
---|---|---|
committer | thegeorg <thegeorg@yandex-team.com> | 2024-03-13 14:11:53 +0300 |
commit | 11a895b7e15d1c5a1f52706396b82e3f9db953cb (patch) | |
tree | fabc6d883b0f946151f61ae7865cee9f529a1fdd /contrib/libs/clang16/tools/extra/clang-tidy/readability/MakeMemberFunctionConstCheck.cpp | |
parent | 9685917341315774aad5733b1793b1e533a88bbb (diff) | |
download | ydb-11a895b7e15d1c5a1f52706396b82e3f9db953cb.tar.gz |
Export clang-format16 via ydblib project
6e6be3a95868fde888d801b7590af4044049563f
Diffstat (limited to 'contrib/libs/clang16/tools/extra/clang-tidy/readability/MakeMemberFunctionConstCheck.cpp')
-rw-r--r-- | contrib/libs/clang16/tools/extra/clang-tidy/readability/MakeMemberFunctionConstCheck.cpp | 268 |
1 files changed, 268 insertions, 0 deletions
diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/readability/MakeMemberFunctionConstCheck.cpp b/contrib/libs/clang16/tools/extra/clang-tidy/readability/MakeMemberFunctionConstCheck.cpp new file mode 100644 index 0000000000..2d6a469252 --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/readability/MakeMemberFunctionConstCheck.cpp @@ -0,0 +1,268 @@ +//===--- 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::tidy::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>(); + } + + const Expr *getParentExprIgnoreParens(const Expr *E) { + const Expr *Parent = getParent<Expr>(E); + while (isa_and_nonnull<ParenExpr>(Parent)) + Parent = getParent<Expr>(Parent); + return Parent; + } + + 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 = getParentExprIgnoreParens(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 = getParentExprIgnoreParens(E); + + // Look through deref of this. + if (const auto *UnOp = dyn_cast_or_null<UnaryOperator>(Parent)) { + if (UnOp->getOpcode() == UO_Deref) { + Parent = getParentExprIgnoreParens(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 clang::tidy::readability |