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 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-static 78 // member function. 79 Usage = NonConst; 80 return false; // Stop traversal. 81 } 82 83 bool VisitCXXConstCastExpr(const CXXConstCastExpr *) { 84 // Workaround to support the pattern 85 // 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 though 92 // it only calls a const member function on this. 93 Usage = NonConst; 94 return false; // Stop traversal. 95 } 96 97 // Our AST is 98 // `-ImplicitCastExpr 99 // (possibly `-UnaryOperator Deref) 100 // `-CXXThisExpr 'S *' this 101 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)->Member 124 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 using 131 // 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 the 136 // logical constness. E.g. in 137 // 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 const 143 // 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 either 154 // 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' can 156 // 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 to 187 // 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 to 196 // (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 210 AST_MATCHER(CXXMethodDecl, usesThisAsConst) { 211 FindUsageOfThis UsageOfThis(Finder->getASTContext()); 212 213 // TraverseStmt does not modify its argument. 214 UsageOfThis.TraverseStmt(const_cast<Stmt *>(Node.getBody())); 215 216 return UsageOfThis.Usage == Const; 217 } 218 219 void MakeMemberFunctionConstCheck::registerMatchers(MatchFinder *Finder) { 220 Finder->addMatcher( 221 traverse( 222 TK_AsIs, 223 cxxMethodDecl( 224 isDefinition(), isUserProvided(), 225 unless(anyOf( 226 isExpansionInSystemHeader(), isVirtual(), isConst(), 227 isStatic(), hasTrivialBody(), cxxConstructorDecl(), 228 cxxDestructorDecl(), isTemplate(), isDependentContext(), 229 ofClass(anyOf(isLambda(), 230 hasAnyDependentBases()) // Method might become 231 // virtual depending on 232 // template base class. 233 ), 234 isInsideMacroDefinition(), 235 hasCanonicalDecl(isInsideMacroDefinition()))), 236 usesThisAsConst()) 237 .bind("x")), 238 this); 239 } 240 241 static SourceLocation getConstInsertionPoint(const CXXMethodDecl *M) { 242 TypeSourceInfo *TSI = M->getTypeSourceInfo(); 243 if (!TSI) 244 return {}; 245 246 FunctionTypeLoc FTL = 247 TSI->getTypeLoc().IgnoreParens().getAs<FunctionTypeLoc>(); 248 if (!FTL) 249 return {}; 250 251 return FTL.getRParenLoc().getLocWithOffset(1); 252 } 253 254 void MakeMemberFunctionConstCheck::check( 255 const MatchFinder::MatchResult &Result) { 256 const auto *Definition = Result.Nodes.getNodeAs<CXXMethodDecl>("x"); 257 258 const auto *Declaration = Definition->getCanonicalDecl(); 259 260 auto Diag = diag(Definition->getLocation(), "method %0 can be made const") 261 << Definition 262 << FixItHint::CreateInsertion(getConstInsertionPoint(Definition), 263 " const"); 264 if (Declaration != Definition) { 265 Diag << FixItHint::CreateInsertion(getConstInsertionPoint(Declaration), 266 " const"); 267 } 268 } 269 270 } // namespace readability 271 } // namespace tidy 272 } // namespace clang 273