1 //===--- ParentVirtualCallCheck.cpp - clang-tidy---------------------------===//
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
7 //===----------------------------------------------------------------------===//
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"
18 using namespace clang::ast_matchers
;
20 namespace clang::tidy::bugprone
{
22 using BasesVector
= llvm::SmallVector
<const CXXRecordDecl
*, 5>;
24 static bool isParentOf(const CXXRecordDecl
&Parent
,
25 const CXXRecordDecl
&ThisClass
) {
26 if (Parent
.getCanonicalDecl() == ThisClass
.getCanonicalDecl())
28 const CXXRecordDecl
*ParentCanonicalDecl
= Parent
.getCanonicalDecl();
29 return llvm::any_of(ThisClass
.bases(), [=](const CXXBaseSpecifier
&Base
) {
30 auto *BaseDecl
= Base
.getType()->getAsCXXRecordDecl();
32 return ParentCanonicalDecl
== BaseDecl
->getCanonicalDecl();
36 static BasesVector
getParentsByGrandParent(const CXXRecordDecl
&GrandParent
,
37 const CXXRecordDecl
&ThisClass
,
38 const CXXMethodDecl
&MemberDecl
) {
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
)
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
);
59 static std::string
getNameAsString(const NamedDecl
*Decl
) {
61 llvm::raw_string_ostream
OS(QualName
);
62 PrintingPolicy
PP(Decl
->getASTContext().getPrintingPolicy());
63 PP
.SuppressUnwrittenScope
= true;
64 Decl
->printQualifiedName(OS
, PP
);
68 // Returns E as written in the source code. Used to handle 'using' and
69 // 'typedef'ed names of grand-parent classes.
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
));
79 void ParentVirtualCallCheck::registerMatchers(MatchFinder
*Finder
) {
84 callee(memberExpr(hasDescendant(implicitCastExpr(
85 hasImplicitDestinationType(pointsTo(
86 type(anything()).bind("castToType"))),
87 hasSourceExpression(cxxThisExpr(hasType(
88 type(anything()).bind("thisType")))))))
90 callee(cxxMethodDecl(isVirtual())))),
94 void ParentVirtualCallCheck::check(const MatchFinder::MatchResult
&Result
) {
95 const auto *Member
= Result
.Nodes
.getNodeAs
<MemberExpr
>("member");
98 if (!Member
->getQualifier())
101 const auto *MemberDecl
= cast
<CXXMethodDecl
>(Member
->getMemberDecl());
103 const auto *ThisTypePtr
= Result
.Nodes
.getNodeAs
<PointerType
>("thisType");
106 const auto *ThisType
= ThisTypePtr
->getPointeeCXXRecordDecl();
109 const auto *CastToTypePtr
= Result
.Nodes
.getNodeAs
<Type
>("castToType");
110 assert(CastToTypePtr
);
112 const auto *CastToType
= CastToTypePtr
->getAsCXXRecordDecl();
115 if (isParentOf(*CastToType
, *ThisType
))
118 const BasesVector Parents
=
119 getParentsByGrandParent(*CastToType
, *ThisType
, *MemberDecl
);
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("'");
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
;
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()) + "::");
148 } // namespace clang::tidy::bugprone