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