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
18using namespace clang::ast_matchers;
19
20namespace clang::tidy::bugprone {
21
22using BasesVector = llvm::SmallVector<const CXXRecordDecl *, 5>;
23
24static 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(Range: ThisClass.bases(), P: [=](const CXXBaseSpecifier &Base) {
30 auto *BaseDecl = Base.getType()->getAsCXXRecordDecl();
31 assert(BaseDecl);
32 return ParentCanonicalDecl == BaseDecl->getCanonicalDecl();
33 });
34}
35
36static 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(RD: 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(Base: &GrandParent))
53 Result.emplace_back(Args&: RecordDeclType);
54 }
55
56 return Result;
57}
58
59static 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, Policy: 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.
70static std::string getExprAsString(const clang::Expr &E,
71 clang::ASTContext &AC) {
72 std::string Text = tooling::fixit::getText(Node: E, Context: AC).str();
73 llvm::erase_if(C&: Text, P: [](char C) {
74 return llvm::isSpace(C: static_cast<unsigned char>(C));
75 });
76 return Text;
77}
78
79void ParentVirtualCallCheck::registerMatchers(MatchFinder *Finder) {
80 Finder->addMatcher(
81 NodeMatch: traverse(
82 TK: TK_AsIs,
83 InnerMatcher: cxxMemberCallExpr(
84 callee(InnerMatcher: memberExpr(hasDescendant(implicitCastExpr(
85 hasImplicitDestinationType(InnerMatcher: pointsTo(
86 InnerMatcher: type(anything()).bind(ID: "castToType"))),
87 hasSourceExpression(InnerMatcher: cxxThisExpr(hasType(
88 InnerMatcher: type(anything()).bind(ID: "thisType")))))))
89 .bind(ID: "member")),
90 callee(InnerMatcher: cxxMethodDecl(isVirtual())))),
91 Action: this);
92}
93
94void ParentVirtualCallCheck::check(const MatchFinder::MatchResult &Result) {
95 const auto *Member = Result.Nodes.getNodeAs<MemberExpr>(ID: "member");
96 assert(Member);
97
98 if (!Member->getQualifier())
99 return;
100
101 const auto *MemberDecl = cast<CXXMethodDecl>(Val: Member->getMemberDecl());
102
103 const auto *ThisTypePtr = Result.Nodes.getNodeAs<PointerType>(ID: "thisType");
104 assert(ThisTypePtr);
105
106 const auto *ThisType = ThisTypePtr->getPointeeCXXRecordDecl();
107 assert(ThisType);
108
109 const auto *CastToTypePtr = Result.Nodes.getNodeAs<Type>(ID: "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(res: 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(Loc: Member->getQualifierLoc().getSourceRange().getBegin(),
134 Description: "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>(Val: Parents.front()))
143 Diag << FixItHint::CreateReplacement(
144 RemoveRange: Member->getQualifierLoc().getSourceRange(),
145 Code: getNameAsString(Parents.front()) + "::");
146}
147
148} // namespace clang::tidy::bugprone
149

source code of clang-tools-extra/clang-tidy/bugprone/ParentVirtualCallCheck.cpp