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