aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/libs/clang16/tools/extra/clang-tidy/readability/MakeMemberFunctionConstCheck.cpp
diff options
context:
space:
mode:
authorthegeorg <thegeorg@yandex-team.com>2024-03-13 13:58:24 +0300
committerthegeorg <thegeorg@yandex-team.com>2024-03-13 14:11:53 +0300
commit11a895b7e15d1c5a1f52706396b82e3f9db953cb (patch)
treefabc6d883b0f946151f61ae7865cee9f529a1fdd /contrib/libs/clang16/tools/extra/clang-tidy/readability/MakeMemberFunctionConstCheck.cpp
parent9685917341315774aad5733b1793b1e533a88bbb (diff)
downloadydb-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.cpp268
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