summaryrefslogtreecommitdiffstats
path: root/clang-tidy/misc/NewDeleteOverloadsCheck.cpp
blob: a96d1904392c5dfb3874a057b48eb88393b80c7c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
//===--- NewDeleteOverloadsCheck.cpp - clang-tidy--------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "NewDeleteOverloadsCheck.h"
#include "clang/AST/ASTContext.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"

using namespace clang::ast_matchers;

namespace clang {
namespace tidy {
namespace misc {

namespace {

AST_MATCHER(FunctionDecl, isPlacementOverload) {
  bool New;
  switch (Node.getOverloadedOperator()) {
  default:
    return false;
  case OO_New:
  case OO_Array_New:
    New = true;
    break;
  case OO_Delete:
  case OO_Array_Delete:
    New = false;
    break;
  }

  // Variadic functions are always placement functions.
  if (Node.isVariadic())
    return true;

  // Placement new is easy: it always has more than one parameter (the first
  // parameter is always the size). If it's an overload of delete or delete[]
  // that has only one parameter, it's never a placement delete.
  if (New)
    return Node.getNumParams() > 1;
  if (Node.getNumParams() == 1)
    return false;

  // Placement delete is a little more challenging. They always have more than
  // one parameter with the first parameter being a pointer. However, the
  // second parameter can be a size_t for sized deallocation, and that is never
  // a placement delete operator.
  if (Node.getNumParams() <= 1 || Node.getNumParams() > 2)
    return true;

  const auto *FPT = Node.getType()->castAs<FunctionProtoType>();
  ASTContext &Ctx = Node.getASTContext();
  if (Ctx.getLangOpts().SizedDeallocation &&
      Ctx.hasSameType(FPT->getParamType(1), Ctx.getSizeType()))
    return false;

  return true;
}

OverloadedOperatorKind getCorrespondingOverload(const FunctionDecl *FD) {
  switch (FD->getOverloadedOperator()) {
  default:
    break;
  case OO_New:
    return OO_Delete;
  case OO_Delete:
    return OO_New;
  case OO_Array_New:
    return OO_Array_Delete;
  case OO_Array_Delete:
    return OO_Array_New;
  }
  llvm_unreachable("Not an overloaded allocation operator");
}

const char *getOperatorName(OverloadedOperatorKind K) {
  switch (K) {
  default:
    break;
  case OO_New:
    return "operator new";
  case OO_Delete:
    return "operator delete";
  case OO_Array_New:
    return "operator new[]";
  case OO_Array_Delete:
    return "operator delete[]";
  }
  llvm_unreachable("Not an overloaded allocation operator");
}

bool areCorrespondingOverloads(const FunctionDecl *LHS,
                               const FunctionDecl *RHS) {
  return RHS->getOverloadedOperator() == getCorrespondingOverload(LHS);
}

bool hasCorrespondingOverloadInBaseClass(const CXXMethodDecl *MD,
                                         const CXXRecordDecl *RD = nullptr) {
  if (RD) {
    // Check the methods in the given class and accessible to derived classes.
    for (const auto *BMD : RD->methods())
      if (BMD->isOverloadedOperator() && BMD->getAccess() != AS_private &&
          areCorrespondingOverloads(MD, BMD))
        return true;
  } else {
    // Get the parent class of the method; we do not need to care about checking
    // the methods in this class as the caller has already done that by looking
    // at the declaration contexts.
    RD = MD->getParent();
  }

  for (const auto &BS : RD->bases()) {
    // We can't say much about a dependent base class, but to avoid false
    // positives assume it can have a corresponding overload.
    if (BS.getType()->isDependentType())
      return true;
    if (const auto *BaseRD = BS.getType()->getAsCXXRecordDecl())
      if (hasCorrespondingOverloadInBaseClass(MD, BaseRD))
        return true;
  }

  return false;
}

} // anonymous namespace

void NewDeleteOverloadsCheck::registerMatchers(MatchFinder *Finder) {
  if (!getLangOpts().CPlusPlus)
    return;

  // Match all operator new and operator delete overloads (including the array
  // forms). Do not match implicit operators, placement operators, or
  // deleted/private operators.
  //
  // Technically, trivially-defined operator delete seems like a reasonable
  // thing to also skip. e.g., void operator delete(void *) {}
  // However, I think it's more reasonable to warn in this case as the user
  // should really be writing that as a deleted function.
  Finder->addMatcher(
      functionDecl(unless(anyOf(isImplicit(), isPlacementOverload(),
                                isDeleted(), cxxMethodDecl(isPrivate()))),
                   anyOf(hasOverloadedOperatorName("new"),
                         hasOverloadedOperatorName("new[]"),
                         hasOverloadedOperatorName("delete"),
                         hasOverloadedOperatorName("delete[]")))
          .bind("func"),
      this);
}

void NewDeleteOverloadsCheck::check(const MatchFinder::MatchResult &Result) {
  // Add any matches we locate to the list of things to be checked at the
  // end of the translation unit.
  const auto *FD = Result.Nodes.getNodeAs<FunctionDecl>("func");
  const CXXRecordDecl *RD = nullptr;
  if (const auto *MD = dyn_cast<CXXMethodDecl>(FD))
    RD = MD->getParent();
  Overloads[RD].push_back(FD);
}

void NewDeleteOverloadsCheck::onEndOfTranslationUnit() {
  // Walk over the list of declarations we've found to see if there is a
  // corresponding overload at the same declaration context or within a base
  // class. If there is not, add the element to the list of declarations to
  // diagnose.
  SmallVector<const FunctionDecl *, 4> Diagnose;
  for (const auto &RP : Overloads) {
    // We don't care about the CXXRecordDecl key in the map; we use it as a way
    // to shard the overloads by declaration context to reduce the algorithmic
    // complexity when searching for corresponding free store functions.
    for (const auto *Overload : RP.second) {
      const auto *Match =
          std::find_if(RP.second.begin(), RP.second.end(),
                       [&Overload](const FunctionDecl *FD) {
                         if (FD == Overload)
                           return false;
                         // If the declaration contexts don't match, we don't
                         // need to check any further.
                         if (FD->getDeclContext() != Overload->getDeclContext())
                           return false;

                         // Since the declaration contexts match, see whether
                         // the current element is the corresponding operator.
                         if (!areCorrespondingOverloads(Overload, FD))
                           return false;

                         return true;
                       });

      if (Match == RP.second.end()) {
        // Check to see if there is a corresponding overload in a base class
        // context. If there isn't, or if the overload is not a class member
        // function, then we should diagnose.
        const auto *MD = dyn_cast<CXXMethodDecl>(Overload);
        if (!MD || !hasCorrespondingOverloadInBaseClass(MD))
          Diagnose.push_back(Overload);
      }
    }
  }

  for (const auto *FD : Diagnose)
    diag(FD->getLocation(), "declaration of %0 has no matching declaration "
                            "of '%1' at the same scope")
        << FD << getOperatorName(getCorrespondingOverload(FD));
}

} // namespace misc
} // namespace tidy
} // namespace clang