brintos

brintos / llvm-project-archived public Read only

0
0
Text · 8.6 KiB · ddc92ef Raw
272 lines · cpp
1//===----------------------------------------------------------------------===//2//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-exception6//7//===----------------------------------------------------------------------===//8 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"15 16using namespace clang::ast_matchers;17 18namespace clang::tidy::readability {19 20namespace {21 22AST_MATCHER(CXXMethodDecl, isStatic) { return Node.isStatic(); }23 24AST_MATCHER(CXXMethodDecl, hasTrivialBody) { return Node.hasTrivialBody(); }25 26AST_MATCHER(CXXRecordDecl, hasAnyDependentBases) {27  return Node.hasAnyDependentBases();28}29 30AST_MATCHER(CXXMethodDecl, isTemplate) {31  return Node.getTemplatedKind() != FunctionDecl::TK_NonTemplate;32}33 34AST_MATCHER(CXXMethodDecl, isDependentContext) {35  return Node.isDependentContext();36}37 38AST_MATCHER(CXXMethodDecl, isInsideMacroDefinition) {39  const ASTContext &Ctxt = Finder->getASTContext();40  return clang::Lexer::makeFileCharRange(41             clang::CharSourceRange::getCharRange(42                 Node.getTypeSourceInfo()->getTypeLoc().getSourceRange()),43             Ctxt.getSourceManager(), Ctxt.getLangOpts())44      .isInvalid();45}46 47AST_MATCHER_P(CXXMethodDecl, hasCanonicalDecl,48              ast_matchers::internal::Matcher<CXXMethodDecl>, InnerMatcher) {49  return InnerMatcher.matches(*Node.getCanonicalDecl(), Finder, Builder);50}51 52enum UsageKind { Unused, Const, NonConst };53 54class FindUsageOfThis : public RecursiveASTVisitor<FindUsageOfThis> {55  ASTContext &Ctxt;56 57public:58  FindUsageOfThis(ASTContext &Ctxt) : Ctxt(Ctxt) {}59  UsageKind Usage = Unused;60 61  template <class T> const T *getParent(const Expr *E) {62    const DynTypedNodeList Parents = Ctxt.getParents(*E);63    if (Parents.size() != 1)64      return nullptr;65 66    return Parents.begin()->get<T>();67  }68 69  const Expr *getParentExprIgnoreParens(const Expr *E) {70    const Expr *Parent = getParent<Expr>(E);71    while (isa_and_nonnull<ParenExpr>(Parent))72      Parent = getParent<Expr>(Parent);73    return Parent;74  }75 76  bool VisitUnresolvedMemberExpr(const UnresolvedMemberExpr *) {77    // An UnresolvedMemberExpr might resolve to a non-const non-static78    // member function.79    Usage = NonConst;80    return false; // Stop traversal.81  }82 83  bool VisitCXXConstCastExpr(const CXXConstCastExpr *) {84    // Workaround to support the pattern85    // class C {86    //   const S *get() const;87    //   S* get() {88    //     return const_cast<S*>(const_cast<const C*>(this)->get());89    //   }90    // };91    // Here, we don't want to make the second 'get' const even though92    // it only calls a const member function on this.93    Usage = NonConst;94    return false; // Stop traversal.95  }96 97  // Our AST is98  //  `-ImplicitCastExpr99  //  (possibly `-UnaryOperator Deref)100  //        `-CXXThisExpr 'S *' this101  bool visitUser(const ImplicitCastExpr *Cast) {102    if (Cast->getCastKind() != CK_NoOp)103      return false; // Stop traversal.104 105    // Only allow NoOp cast to 'const S' or 'const S *'.106    QualType QT = Cast->getType();107    if (QT->isPointerType())108      QT = QT->getPointeeType();109 110    if (!QT.isConstQualified())111      return false; // Stop traversal.112 113    const auto *Parent = getParent<Stmt>(Cast);114    if (!Parent)115      return false; // Stop traversal.116 117    if (isa<ReturnStmt>(Parent))118      return true; // return (const S*)this;119 120    if (isa<CallExpr>(Parent))121      return true; // use((const S*)this);122 123    // ((const S*)this)->Member124    if (const auto *Member = dyn_cast<MemberExpr>(Parent))125      return visitUser(Member, /*OnConstObject=*/true);126 127    return false; // Stop traversal.128  }129 130  // If OnConstObject is true, then this is a MemberExpr using131  // a constant this, i.e. 'const S' or 'const S *'.132  bool visitUser(const MemberExpr *Member, bool OnConstObject) {133    if (Member->isBoundMemberFunction(Ctxt)) {134      if (!OnConstObject || Member->getFoundDecl().getAccess() != AS_public) {135        // Non-public non-static member functions might not preserve the136        // logical constness. E.g. in137        // class C {138        //   int &data() const;139        // public:140        //   int &get() { return data(); }141        // };142        // get() uses a private const method, but must not be made const143        // itself.144        return false; // Stop traversal.145      }146      // Using a public non-static const member function.147      return true;148    }149 150    const auto *Parent = getParentExprIgnoreParens(Member);151 152    if (const auto *Cast = dyn_cast_or_null<ImplicitCastExpr>(Parent)) {153      // A read access to a member is safe when the member either154      // 1) has builtin type (a 'const int' cannot be modified),155      // 2) or it's a public member (the pointee of a public 'int * const' can156      // can be modified by any user of the class).157      if (Member->getFoundDecl().getAccess() != AS_public &&158          !Cast->getType()->isBuiltinType())159        return false;160 161      if (Cast->getCastKind() == CK_LValueToRValue)162        return true;163 164      if (Cast->getCastKind() == CK_NoOp && Cast->getType().isConstQualified())165        return true;166    }167 168    if (const auto *M = dyn_cast_or_null<MemberExpr>(Parent))169      return visitUser(M, /*OnConstObject=*/false);170 171    return false; // Stop traversal.172  }173 174  bool VisitCXXThisExpr(const CXXThisExpr *E) {175    Usage = Const;176 177    const auto *Parent = getParentExprIgnoreParens(E);178 179    // Look through deref of this.180    if (const auto *UnOp = dyn_cast_or_null<UnaryOperator>(Parent)) {181      if (UnOp->getOpcode() == UO_Deref) {182        Parent = getParentExprIgnoreParens(UnOp);183      }184    }185 186    // It's okay to187    //  return (const S*)this;188    //  use((const S*)this);189    //  ((const S*)this)->f()190    // when 'f' is a public member function.191    if (const auto *Cast = dyn_cast_or_null<ImplicitCastExpr>(Parent)) {192      if (visitUser(Cast))193        return true;194 195      // And it's also okay to196      //   (const T)(S->t)197      //   (LValueToRValue)(S->t)198      // when 't' is either of builtin type or a public member.199    } else if (const auto *Member = dyn_cast_or_null<MemberExpr>(Parent)) {200      if (visitUser(Member, /*OnConstObject=*/false))201        return true;202    }203 204    // Unknown user of this.205    Usage = NonConst;206    return false; // Stop traversal.207  }208};209 210AST_MATCHER(CXXMethodDecl, usesThisAsConst) {211  FindUsageOfThis UsageOfThis(Finder->getASTContext());212 213  // TraverseStmt does not modify its argument.214  UsageOfThis.TraverseStmt(Node.getBody());215 216  return UsageOfThis.Usage == Const;217}218 219} // namespace220 221void MakeMemberFunctionConstCheck::registerMatchers(MatchFinder *Finder) {222  Finder->addMatcher(223      traverse(224          TK_AsIs,225          cxxMethodDecl(226              isDefinition(), isUserProvided(),227              unless(anyOf(228                  isExpansionInSystemHeader(), isVirtual(), isConst(),229                  isStatic(), hasTrivialBody(), cxxConstructorDecl(),230                  cxxDestructorDecl(), isTemplate(), isDependentContext(),231                  ofClass(anyOf(isLambda(),232                                hasAnyDependentBases()) // Method might become233                                                        // virtual depending on234                                                        // template base class.235                          ),236                  isInsideMacroDefinition(),237                  hasCanonicalDecl(isInsideMacroDefinition()))),238              usesThisAsConst())239              .bind("x")),240      this);241}242 243static SourceLocation getConstInsertionPoint(const CXXMethodDecl *M) {244  const TypeSourceInfo *TSI = M->getTypeSourceInfo();245  if (!TSI)246    return {};247 248  auto FTL = TSI->getTypeLoc().IgnoreParens().getAs<FunctionTypeLoc>();249  if (!FTL)250    return {};251 252  return FTL.getRParenLoc().getLocWithOffset(1);253}254 255void MakeMemberFunctionConstCheck::check(256    const MatchFinder::MatchResult &Result) {257  const auto *Definition = Result.Nodes.getNodeAs<CXXMethodDecl>("x");258 259  const auto *Declaration = Definition->getCanonicalDecl();260 261  auto Diag = diag(Definition->getLocation(), "method %0 can be made const")262              << Definition263              << FixItHint::CreateInsertion(getConstInsertionPoint(Definition),264                                            " const");265  if (Declaration != Definition) {266    Diag << FixItHint::CreateInsertion(getConstInsertionPoint(Declaration),267                                       " const");268  }269}270 271} // namespace clang::tidy::readability272