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 | |
17 | using namespace clang::ast_matchers; |
18 | |
19 | namespace clang::tidy::bugprone { |
20 | |
21 | using BasesVector = llvm::SmallVector<const CXXRecordDecl *, 5>; |
22 | |
23 | static 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 | |
35 | static 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 | |
58 | static 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. |
69 | static 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 | |
78 | void 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 | |
93 | void 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 | |