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