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