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::tidy::bugprone { |
21 | |
22 | using BasesVector = llvm::SmallVector<const CXXRecordDecl *, 5>; |
23 | |
24 | static 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 | |
36 | static 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 | |
59 | static 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. |
70 | static 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 | |
79 | void 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 | |
94 | void 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 | |