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