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 <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(Range: ThisClass.bases(), P: [=](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(RD: 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(Base: &GrandParent))
52 Result.emplace_back(Args&: 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, Policy: 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(Node: E, Context: AC).str();
72 llvm::erase_if(C&: Text, P: [](char C) {
73 return llvm::isSpace(C: static_cast<unsigned char>(C));
74 });
75 return Text;
76}
77
78void ParentVirtualCallCheck::registerMatchers(MatchFinder *Finder) {
79 Finder->addMatcher(
80 NodeMatch: traverse(
81 TK: TK_AsIs,
82 InnerMatcher: cxxMemberCallExpr(
83 callee(InnerMatcher: memberExpr(hasDescendant(implicitCastExpr(
84 hasImplicitDestinationType(InnerMatcher: pointsTo(
85 InnerMatcher: type(anything()).bind(ID: "castToType"))),
86 hasSourceExpression(InnerMatcher: cxxThisExpr(hasType(
87 InnerMatcher: type(anything()).bind(ID: "thisType")))))))
88 .bind(ID: "member")),
89 callee(InnerMatcher: cxxMethodDecl(isVirtual())))),
90 Action: this);
91}
92
93void ParentVirtualCallCheck::check(const MatchFinder::MatchResult &Result) {
94 const auto *Member = Result.Nodes.getNodeAs<MemberExpr>(ID: "member");
95 assert(Member);
96
97 if (!Member->getQualifier())
98 return;
99
100 const auto *MemberDecl = cast<CXXMethodDecl>(Val: Member->getMemberDecl());
101
102 const auto *ThisTypePtr = Result.Nodes.getNodeAs<PointerType>(ID: "thisType");
103 assert(ThisTypePtr);
104
105 const auto *ThisType = ThisTypePtr->getPointeeCXXRecordDecl();
106 assert(ThisType);
107
108 const auto *CastToTypePtr = Result.Nodes.getNodeAs<Type>(ID: "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(res: 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(Loc: Member->getQualifierLoc().getSourceRange().getBegin(),
133 Description: "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>(Val: Parents.front()))
142 Diag << FixItHint::CreateReplacement(
143 RemoveRange: Member->getQualifierLoc().getSourceRange(),
144 Code: getNameAsString(Parents.front()) + "::");
145}
146
147} // namespace clang::tidy::bugprone
148

Provided by KDAB

Privacy Policy
Improve your Profiling and Debugging skills
Find out more

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