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