7daf742dadac11b811f7016f306ad3f221c8480e
[lldb.git] / clang-tools-extra / clang-tidy / bugprone / ParentVirtualCallCheck.cpp
1 //===--- ParentVirtualCallCheck.cpp - clang-tidy---------------------------===//
2 //
3 //                     The LLVM Compiler Infrastructure
4 //
5 // This file is distributed under the University of Illinois Open Source
6 // License. See LICENSE.TXT for details.
7 //
8 //===----------------------------------------------------------------------===//
9
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"
16 #include <algorithm>
17 #include <cctype>
18
19 using namespace clang::ast_matchers;
20
21 namespace clang {
22 namespace tidy {
23 namespace bugprone {
24
25 using BasesVector = llvm::SmallVector<const CXXRecordDecl *, 5>;
26
27 static bool isParentOf(const CXXRecordDecl &Parent,
28                        const CXXRecordDecl &ThisClass) {
29   if (Parent.getCanonicalDecl() == ThisClass.getCanonicalDecl())
30     return true;
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 Parent.getCanonicalDecl() == 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 =
53         ActualMemberDecl->getThisType(ActualMemberDecl->getASTContext())
54             .getTypePtr();
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);
59   }
60
61   return Result;
62 }
63
64 static std::string getNameAsString(const NamedDecl *Decl) {
65   std::string QualName;
66   llvm::raw_string_ostream OS(QualName);
67   PrintingPolicy PP(Decl->getASTContext().getPrintingPolicy());
68   PP.SuppressUnwrittenScope = true;
69   Decl->printQualifiedName(OS, PP);
70   return OS.str();
71 }
72
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();
78   Text.erase(
79       llvm::remove_if(
80           Text,
81           [](char C) { return std::isspace(static_cast<unsigned char>(C)); }),
82       Text.end());
83   return Text;
84 }
85
86 void ParentVirtualCallCheck::registerMatchers(MatchFinder *Finder) {
87   Finder->addMatcher(
88       cxxMemberCallExpr(
89           callee(memberExpr(hasDescendant(implicitCastExpr(
90                                 hasImplicitDestinationType(pointsTo(
91                                     type(anything()).bind("castToType"))),
92                                 hasSourceExpression(cxxThisExpr(hasType(
93                                     type(anything()).bind("thisType")))))))
94                      .bind("member")),
95           callee(cxxMethodDecl(isVirtual()))),
96       this);
97 }
98
99 void ParentVirtualCallCheck::check(const MatchFinder::MatchResult &Result) {
100   const auto *Member = Result.Nodes.getNodeAs<MemberExpr>("member");
101   assert(Member);
102
103   if (!Member->getQualifier())
104     return;
105
106   const auto *MemberDecl = cast<CXXMethodDecl>(Member->getMemberDecl());
107
108   const auto *ThisTypePtr = Result.Nodes.getNodeAs<PointerType>("thisType");
109   assert(ThisTypePtr);
110
111   const auto *ThisType = ThisTypePtr->getPointeeCXXRecordDecl();
112   assert(ThisType);
113
114   const auto *CastToTypePtr = Result.Nodes.getNodeAs<Type>("castToType");
115   assert(CastToTypePtr);
116
117   const auto *CastToType = CastToTypePtr->getAsCXXRecordDecl();
118   assert(CastToType);
119
120   if (isParentOf(*CastToType, *ThisType))
121     return;
122
123   const BasesVector Parents =
124       getParentsByGrandParent(*CastToType, *ThisType, *MemberDecl);
125
126   if (Parents.empty())
127     return;
128
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("'");
135   }
136
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;
143
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()) + "::");
151 }
152
153 } // namespace bugprone
154 } // namespace tidy
155 } // namespace clang