reference, declarationdefinition
definition → references, declarations, derived classes, virtual overrides
reference to multiple definitions → definitions
unreferenced
    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