llvm-project

Форк
0
/
ParentVirtualCallCheck.cpp 
148 строк · 5.4 Кб
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(ThisClass.bases(), [=](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(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(&GrandParent))
53
      Result.emplace_back(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, 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(E, AC).str();
73
  llvm::erase_if(Text, [](char C) {
74
    return llvm::isSpace(static_cast<unsigned char>(C));
75
  });
76
  return Text;
77
}
78

79
void ParentVirtualCallCheck::registerMatchers(MatchFinder *Finder) {
80
  Finder->addMatcher(
81
      traverse(
82
          TK_AsIs,
83
          cxxMemberCallExpr(
84
              callee(memberExpr(hasDescendant(implicitCastExpr(
85
                                    hasImplicitDestinationType(pointsTo(
86
                                        type(anything()).bind("castToType"))),
87
                                    hasSourceExpression(cxxThisExpr(hasType(
88
                                        type(anything()).bind("thisType")))))))
89
                         .bind("member")),
90
              callee(cxxMethodDecl(isVirtual())))),
91
      this);
92
}
93

94
void ParentVirtualCallCheck::check(const MatchFinder::MatchResult &Result) {
95
  const auto *Member = Result.Nodes.getNodeAs<MemberExpr>("member");
96
  assert(Member);
97

98
  if (!Member->getQualifier())
99
    return;
100

101
  const auto *MemberDecl = cast<CXXMethodDecl>(Member->getMemberDecl());
102

103
  const auto *ThisTypePtr = Result.Nodes.getNodeAs<PointerType>("thisType");
104
  assert(ThisTypePtr);
105

106
  const auto *ThisType = ThisTypePtr->getPointeeCXXRecordDecl();
107
  assert(ThisType);
108

109
  const auto *CastToTypePtr = Result.Nodes.getNodeAs<Type>("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(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(Member->getQualifierLoc().getSourceRange().getBegin(),
134
                   "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>(Parents.front()))
143
    Diag << FixItHint::CreateReplacement(
144
        Member->getQualifierLoc().getSourceRange(),
145
        getNameAsString(Parents.front()) + "::");
146
}
147

148
} // namespace clang::tidy::bugprone
149

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.