1 //===--- ParentVirtualCallCheck.cpp - clang-tidy---------------------------===//
3 // The LLVM Compiler Infrastructure
5 // This file is distributed under the University of Illinois Open Source
6 // License. See LICENSE.TXT for details.
8 //===----------------------------------------------------------------------===//
10 #include "ParentVirtualCallCheck.h"
11 #include "clang/AST/ASTContext.h"
12 #include "clang/ASTMatchers/ASTMatchFinder.h"
13 #include "clang/Tooling/FixIt.h"
14 #include "llvm/ADT/STLExtras.h"
15 #include "llvm/ADT/SmallVector.h"
19 using namespace clang::ast_matchers;
25 using BasesVector = llvm::SmallVector<const CXXRecordDecl *, 5>;
27 static bool isParentOf(const CXXRecordDecl &Parent,
28 const CXXRecordDecl &ThisClass) {
29 if (Parent.getCanonicalDecl() == ThisClass.getCanonicalDecl())
31 return ThisClass.bases_end() !=
32 llvm::find_if(ThisClass.bases(), [=](const CXXBaseSpecifier &Base) {
33 auto *BaseDecl = Base.getType()->getAsCXXRecordDecl();
35 return Parent.getCanonicalDecl() == BaseDecl->getCanonicalDecl();
39 static BasesVector getParentsByGrandParent(const CXXRecordDecl &GrandParent,
40 const CXXRecordDecl &ThisClass,
41 const CXXMethodDecl &MemberDecl) {
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)
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.
53 ActualMemberDecl->getThisType(ActualMemberDecl->getASTContext())
55 const CXXRecordDecl *RecordDeclType = TypePtr->getPointeeCXXRecordDecl();
56 assert(RecordDeclType && "TypePtr is not a pointer to CXXRecordDecl!");
57 if (RecordDeclType->getCanonicalDecl()->isDerivedFrom(&GrandParent))
58 Result.emplace_back(RecordDeclType);
64 static std::string getNameAsString(const NamedDecl *Decl) {
66 llvm::raw_string_ostream OS(QualName);
67 PrintingPolicy PP(Decl->getASTContext().getPrintingPolicy());
68 PP.SuppressUnwrittenScope = true;
69 Decl->printQualifiedName(OS, PP);
73 // Returns E as written in the source code. Used to handle 'using' and
74 // 'typedef'ed names of grand-parent classes.
75 static std::string getExprAsString(const clang::Expr &E,
76 clang::ASTContext &AC) {
77 std::string Text = tooling::fixit::getText(E, AC).str();
81 [](char C) { return std::isspace(static_cast<unsigned char>(C)); }),
86 void ParentVirtualCallCheck::registerMatchers(MatchFinder *Finder) {
89 callee(memberExpr(hasDescendant(implicitCastExpr(
90 hasImplicitDestinationType(pointsTo(
91 type(anything()).bind("castToType"))),
92 hasSourceExpression(cxxThisExpr(hasType(
93 type(anything()).bind("thisType")))))))
95 callee(cxxMethodDecl(isVirtual()))),
99 void ParentVirtualCallCheck::check(const MatchFinder::MatchResult &Result) {
100 const auto *Member = Result.Nodes.getNodeAs<MemberExpr>("member");
103 if (!Member->getQualifier())
106 const auto *MemberDecl = cast<CXXMethodDecl>(Member->getMemberDecl());
108 const auto *ThisTypePtr = Result.Nodes.getNodeAs<PointerType>("thisType");
111 const auto *ThisType = ThisTypePtr->getPointeeCXXRecordDecl();
114 const auto *CastToTypePtr = Result.Nodes.getNodeAs<Type>("castToType");
115 assert(CastToTypePtr);
117 const auto *CastToType = CastToTypePtr->getAsCXXRecordDecl();
120 if (isParentOf(*CastToType, *ThisType))
123 const BasesVector Parents =
124 getParentsByGrandParent(*CastToType, *ThisType, *MemberDecl);
129 std::string ParentsStr;
130 ParentsStr.reserve(30 * Parents.size());
131 for (const CXXRecordDecl *Parent : Parents) {
132 if (!ParentsStr.empty())
133 ParentsStr.append(" or ");
134 ParentsStr.append("'").append(getNameAsString(Parent)).append("'");
137 assert(Member->getQualifierLoc().getSourceRange().getBegin().isValid());
138 auto Diag = diag(Member->getQualifierLoc().getSourceRange().getBegin(),
139 "qualified name '%0' refers to a member overridden "
140 "in subclass%1; did you mean %2?")
141 << getExprAsString(*Member, *Result.Context)
142 << (Parents.size() > 1 ? "es" : "") << ParentsStr;
144 // Propose a fix if there's only one parent class...
145 if (Parents.size() == 1 &&
146 // ...unless parent class is templated
147 !isa<ClassTemplateSpecializationDecl>(Parents.front()))
148 Diag << FixItHint::CreateReplacement(
149 Member->getQualifierLoc().getSourceRange(),
150 getNameAsString(Parents.front()) + "::");
153 } // namespace bugprone