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