1 //===--- ParentVirtualCallCheck.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 "ParentVirtualCallCheck.h" 10 #include "clang/AST/ASTContext.h" 11 #include "clang/ASTMatchers/ASTMatchFinder.h" 12 #include "clang/Tooling/FixIt.h" 13 #include "llvm/ADT/STLExtras.h" 14 #include "llvm/ADT/SmallVector.h" 15 #include <algorithm> 16 #include <cctype> 17 18 using namespace clang::ast_matchers; 19 20 namespace clang { 21 namespace tidy { 22 namespace bugprone { 23 24 using BasesVector = llvm::SmallVector<const CXXRecordDecl *, 5>; 25 26 static bool isParentOf(const CXXRecordDecl &Parent, 27 const CXXRecordDecl &ThisClass) { 28 if (Parent.getCanonicalDecl() == ThisClass.getCanonicalDecl()) 29 return true; 30 const CXXRecordDecl *ParentCanonicalDecl = Parent.getCanonicalDecl(); 31 return ThisClass.bases_end() != 32 llvm::find_if(ThisClass.bases(), [=](const CXXBaseSpecifier &Base) { 33 auto *BaseDecl = Base.getType()->getAsCXXRecordDecl(); 34 assert(BaseDecl); 35 return ParentCanonicalDecl == BaseDecl->getCanonicalDecl(); 36 }); 37 } 38 39 static BasesVector getParentsByGrandParent(const CXXRecordDecl &GrandParent, 40 const CXXRecordDecl &ThisClass, 41 const CXXMethodDecl &MemberDecl) { 42 BasesVector Result; 43 for (const auto &Base : ThisClass.bases()) { 44 const auto *BaseDecl = Base.getType()->getAsCXXRecordDecl(); 45 const CXXMethodDecl *ActualMemberDecl = 46 MemberDecl.getCorrespondingMethodInClass(BaseDecl); 47 if (!ActualMemberDecl) 48 continue; 49 // TypePtr is the nearest base class to ThisClass between ThisClass and 50 // GrandParent, where MemberDecl is overridden. TypePtr is the class the 51 // check proposes to fix to. 52 const Type *TypePtr = ActualMemberDecl->getThisType().getTypePtr(); 53 const CXXRecordDecl *RecordDeclType = TypePtr->getPointeeCXXRecordDecl(); 54 assert(RecordDeclType && "TypePtr is not a pointer to CXXRecordDecl!"); 55 if (RecordDeclType->getCanonicalDecl()->isDerivedFrom(&GrandParent)) 56 Result.emplace_back(RecordDeclType); 57 } 58 59 return Result; 60 } 61 62 static std::string getNameAsString(const NamedDecl *Decl) { 63 std::string QualName; 64 llvm::raw_string_ostream OS(QualName); 65 PrintingPolicy PP(Decl->getASTContext().getPrintingPolicy()); 66 PP.SuppressUnwrittenScope = true; 67 Decl->printQualifiedName(OS, PP); 68 return OS.str(); 69 } 70 71 // Returns E as written in the source code. Used to handle 'using' and 72 // 'typedef'ed names of grand-parent classes. 73 static std::string getExprAsString(const clang::Expr &E, 74 clang::ASTContext &AC) { 75 std::string Text = tooling::fixit::getText(E, AC).str(); 76 Text.erase( 77 llvm::remove_if( 78 Text, 79 [](char C) { return std::isspace(static_cast<unsigned char>(C)); }), 80 Text.end()); 81 return Text; 82 } 83 84 void ParentVirtualCallCheck::registerMatchers(MatchFinder *Finder) { 85 Finder->addMatcher( 86 cxxMemberCallExpr( 87 callee(memberExpr(hasDescendant(implicitCastExpr( 88 hasImplicitDestinationType(pointsTo( 89 type(anything()).bind("castToType"))), 90 hasSourceExpression(cxxThisExpr(hasType( 91 type(anything()).bind("thisType"))))))) 92 .bind("member")), 93 callee(cxxMethodDecl(isVirtual()))), 94 this); 95 } 96 97 void ParentVirtualCallCheck::check(const MatchFinder::MatchResult &Result) { 98 const auto *Member = Result.Nodes.getNodeAs<MemberExpr>("member"); 99 assert(Member); 100 101 if (!Member->getQualifier()) 102 return; 103 104 const auto *MemberDecl = cast<CXXMethodDecl>(Member->getMemberDecl()); 105 106 const auto *ThisTypePtr = Result.Nodes.getNodeAs<PointerType>("thisType"); 107 assert(ThisTypePtr); 108 109 const auto *ThisType = ThisTypePtr->getPointeeCXXRecordDecl(); 110 assert(ThisType); 111 112 const auto *CastToTypePtr = Result.Nodes.getNodeAs<Type>("castToType"); 113 assert(CastToTypePtr); 114 115 const auto *CastToType = CastToTypePtr->getAsCXXRecordDecl(); 116 assert(CastToType); 117 118 if (isParentOf(*CastToType, *ThisType)) 119 return; 120 121 const BasesVector Parents = 122 getParentsByGrandParent(*CastToType, *ThisType, *MemberDecl); 123 124 if (Parents.empty()) 125 return; 126 127 std::string ParentsStr; 128 ParentsStr.reserve(30 * Parents.size()); 129 for (const CXXRecordDecl *Parent : Parents) { 130 if (!ParentsStr.empty()) 131 ParentsStr.append(" or "); 132 ParentsStr.append("'").append(getNameAsString(Parent)).append("'"); 133 } 134 135 assert(Member->getQualifierLoc().getSourceRange().getBegin().isValid()); 136 auto Diag = diag(Member->getQualifierLoc().getSourceRange().getBegin(), 137 "qualified name '%0' refers to a member overridden " 138 "in subclass%1; did you mean %2?") 139 << getExprAsString(*Member, *Result.Context) 140 << (Parents.size() > 1 ? "es" : "") << ParentsStr; 141 142 // Propose a fix if there's only one parent class... 143 if (Parents.size() == 1 && 144 // ...unless parent class is templated 145 !isa<ClassTemplateSpecializationDecl>(Parents.front())) 146 Diag << FixItHint::CreateReplacement( 147 Member->getQualifierLoc().getSourceRange(), 148 getNameAsString(Parents.front()) + "::"); 149 } 150 151 } // namespace bugprone 152 } // namespace tidy 153 } // namespace clang 154