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