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