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