[clang-tidy] add check cppcoreguidelines-special-member-functions

Summary:
Check for classes that violate the rule of five and zero as specified in CppCoreGuidelines:

"If a class defines or deletes a default operation then it should define or delete them all."

https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#c21-if-you-define-or-delete-any-default-operation-define-or-delete-them-all.

Reviewers: alexfh, sbenza, aaron.ballman

Subscribers: Prazek, Eugene.Zelenko, cfe-commits, ericLemanissier, nemanjai

Projects: #clang-tools-extra

Differential Revision: https://reviews.llvm.org/D22513

llvm-svn: 277262
diff --git a/clang-tools-extra/clang-tidy/cppcoreguidelines/CMakeLists.txt b/clang-tools-extra/clang-tidy/cppcoreguidelines/CMakeLists.txt
index 77f0646..62d9a8a 100644
--- a/clang-tools-extra/clang-tidy/cppcoreguidelines/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/cppcoreguidelines/CMakeLists.txt
@@ -13,6 +13,7 @@
   ProTypeStaticCastDowncastCheck.cpp
   ProTypeUnionAccessCheck.cpp
   ProTypeVarargCheck.cpp
+  SpecialMemberFunctionsCheck.cpp
   SlicingCheck.cpp
 
   LINK_LIBS
diff --git a/clang-tools-extra/clang-tidy/cppcoreguidelines/CppCoreGuidelinesTidyModule.cpp b/clang-tools-extra/clang-tidy/cppcoreguidelines/CppCoreGuidelinesTidyModule.cpp
index 608e2ee..dc9a7f3 100644
--- a/clang-tools-extra/clang-tidy/cppcoreguidelines/CppCoreGuidelinesTidyModule.cpp
+++ b/clang-tools-extra/clang-tidy/cppcoreguidelines/CppCoreGuidelinesTidyModule.cpp
@@ -22,6 +22,7 @@
 #include "ProTypeStaticCastDowncastCheck.h"
 #include "ProTypeUnionAccessCheck.h"
 #include "ProTypeVarargCheck.h"
+#include "SpecialMemberFunctionsCheck.h"
 #include "SlicingCheck.h"
 
 namespace clang {
@@ -54,6 +55,8 @@
         "cppcoreguidelines-pro-type-union-access");
     CheckFactories.registerCheck<ProTypeVarargCheck>(
         "cppcoreguidelines-pro-type-vararg");
+    CheckFactories.registerCheck<SpecialMemberFunctionsCheck>(
+        "cppcoreguidelines-special-member-functions");
     CheckFactories.registerCheck<SlicingCheck>(
         "cppcoreguidelines-slicing");
     CheckFactories.registerCheck<misc::UnconventionalAssignOperatorCheck>(
diff --git a/clang-tools-extra/clang-tidy/cppcoreguidelines/SpecialMemberFunctionsCheck.cpp b/clang-tools-extra/clang-tidy/cppcoreguidelines/SpecialMemberFunctionsCheck.cpp
new file mode 100644
index 0000000..9a0ffa9
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/cppcoreguidelines/SpecialMemberFunctionsCheck.cpp
@@ -0,0 +1,133 @@
+//===--- SpecialMemberFunctionsCheck.cpp - clang-tidy----------------------===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "SpecialMemberFunctionsCheck.h"
+
+#include "clang/AST/ASTContext.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "llvm/ADT/DenseMapInfo.h"
+#include "llvm/ADT/StringExtras.h"
+
+#define DEBUG_TYPE "clang-tidy"
+
+using namespace clang::ast_matchers;
+
+namespace clang {
+namespace tidy {
+namespace cppcoreguidelines {
+
+void SpecialMemberFunctionsCheck::registerMatchers(MatchFinder *Finder) {
+  if (!getLangOpts().CPlusPlus)
+    return;
+  Finder->addMatcher(
+      cxxRecordDecl(
+          eachOf(
+              has(cxxDestructorDecl(unless(isImplicit())).bind("dtor")),
+              has(cxxConstructorDecl(isCopyConstructor(), unless(isImplicit()))
+                      .bind("copy-ctor")),
+              has(cxxMethodDecl(isCopyAssignmentOperator(),
+                                unless(isImplicit()))
+                      .bind("copy-assign")),
+              has(cxxConstructorDecl(isMoveConstructor(), unless(isImplicit()))
+                      .bind("move-ctor")),
+              has(cxxMethodDecl(isMoveAssignmentOperator(),
+                                unless(isImplicit()))
+                      .bind("move-assign"))))
+          .bind("class-def"),
+      this);
+}
+
+llvm::StringRef SpecialMemberFunctionsCheck::toString(
+    SpecialMemberFunctionsCheck::SpecialMemberFunctionKind K) {
+  switch (K) {
+  case SpecialMemberFunctionKind::Destructor:
+    return "a destructor";
+  case SpecialMemberFunctionKind::CopyConstructor:
+    return "a copy constructor";
+  case SpecialMemberFunctionKind::CopyAssignment:
+    return "a copy assignment operator";
+  case SpecialMemberFunctionKind::MoveConstructor:
+    return "a move constructor";
+  case SpecialMemberFunctionKind::MoveAssignment:
+    return "a move assignment operator";
+  }
+  llvm_unreachable("Unhandled SpecialMemberFunctionKind");
+}
+
+std::string SpecialMemberFunctionsCheck::join(
+    llvm::ArrayRef<SpecialMemberFunctionKind> SMFS, llvm::StringRef AndOr) {
+
+  assert(!SMFS.empty() &&
+         "List of defined or undefined members should never be empty.");
+  std::string Buffer;
+  llvm::raw_string_ostream Stream(Buffer);
+
+  Stream << toString(SMFS[0]);
+  size_t LastIndex = SMFS.size() - 1;
+  for (size_t i = 1; i < LastIndex; ++i) {
+    Stream << ", " << toString(SMFS[i]);
+  }
+  if (LastIndex != 0) {
+    Stream << AndOr << toString(SMFS[LastIndex]);
+  }
+  return Stream.str();
+}
+
+void SpecialMemberFunctionsCheck::check(
+    const MatchFinder::MatchResult &Result) {
+  const CXXRecordDecl *MatchedDecl =
+      Result.Nodes.getNodeAs<CXXRecordDecl>("class-def");
+  if (!MatchedDecl)
+    return;
+
+  ClassDefId ID(MatchedDecl->getLocation(), MatchedDecl->getName());
+
+  std::initializer_list<std::pair<std::string, SpecialMemberFunctionKind>>
+      Matchers = {{"dtor", SpecialMemberFunctionKind::Destructor},
+                  {"copy-ctor", SpecialMemberFunctionKind::CopyConstructor},
+                  {"copy-assign", SpecialMemberFunctionKind::CopyAssignment},
+                  {"move-ctor", SpecialMemberFunctionKind::MoveConstructor},
+                  {"move-assign", SpecialMemberFunctionKind::MoveAssignment}};
+
+  for (const auto &KV : Matchers)
+    if (Result.Nodes.getNodeAs<CXXMethodDecl>(KV.first))
+      ClassWithSpecialMembers[ID].push_back(KV.second);
+}
+
+void SpecialMemberFunctionsCheck::onEndOfTranslationUnit() {
+  llvm::SmallVector<SpecialMemberFunctionKind, 5> AllSpecialMembers = {
+      SpecialMemberFunctionKind::Destructor,
+      SpecialMemberFunctionKind::CopyConstructor,
+      SpecialMemberFunctionKind::CopyAssignment};
+
+  if (getLangOpts().CPlusPlus11) {
+    AllSpecialMembers.push_back(SpecialMemberFunctionKind::MoveConstructor);
+    AllSpecialMembers.push_back(SpecialMemberFunctionKind::MoveAssignment);
+  }
+
+  for (const auto &C : ClassWithSpecialMembers) {
+    ArrayRef<SpecialMemberFunctionKind> DefinedSpecialMembers = C.second;
+
+    if (DefinedSpecialMembers.size() == AllSpecialMembers.size())
+      continue;
+
+    llvm::SmallVector<SpecialMemberFunctionKind, 5> UndefinedSpecialMembers;
+    std::set_difference(AllSpecialMembers.begin(), AllSpecialMembers.end(),
+                        DefinedSpecialMembers.begin(),
+                        DefinedSpecialMembers.end(),
+                        std::back_inserter(UndefinedSpecialMembers));
+
+    diag(C.first.first, "class '%0' defines %1 but does not define %2")
+        << C.first.second << join(DefinedSpecialMembers, " and ")
+        << join(UndefinedSpecialMembers, " or ");
+  }
+}
+} // namespace cppcoreguidelines
+} // namespace tidy
+} // namespace clang
diff --git a/clang-tools-extra/clang-tidy/cppcoreguidelines/SpecialMemberFunctionsCheck.h b/clang-tools-extra/clang-tidy/cppcoreguidelines/SpecialMemberFunctionsCheck.h
new file mode 100644
index 0000000..6218535
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/cppcoreguidelines/SpecialMemberFunctionsCheck.h
@@ -0,0 +1,101 @@
+//===--- SpecialMemberFunctionsCheck.h - clang-tidy-------------------*- C++ -*-===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_SPECIAL_MEMBER_FUNCTIONS_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_SPECIAL_MEMBER_FUNCTIONS_H
+
+#include "../ClangTidy.h"
+
+#include "llvm/ADT/DenseMapInfo.h"
+
+namespace clang {
+namespace tidy {
+namespace cppcoreguidelines {
+
+/// Checks for classes where some, but not all, of the special member functions
+/// are defined.
+///
+/// For the user-facing documentation see:
+/// http://clang.llvm.org/extra/clang-tidy/checks/cppcoreguidelines-special-member-functions.html
+class SpecialMemberFunctionsCheck : public ClangTidyCheck {
+public:
+  SpecialMemberFunctionsCheck(StringRef Name, ClangTidyContext *Context)
+      : ClangTidyCheck(Name, Context) {}
+  void registerMatchers(ast_matchers::MatchFinder *Finder) override;
+  void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
+  void onEndOfTranslationUnit() override;
+  
+  enum class SpecialMemberFunctionKind {
+    Destructor,
+    CopyConstructor,
+    CopyAssignment,
+    MoveConstructor,
+    MoveAssignment
+  };
+
+  using ClassDefId = std::pair<SourceLocation, std::string>;
+
+  using ClassDefiningSpecialMembersMap = llvm::DenseMap<ClassDefId, llvm::SmallVector<SpecialMemberFunctionKind, 5>>;
+
+private:
+  
+  static llvm::StringRef toString(SpecialMemberFunctionKind K);
+
+  static std::string join(llvm::ArrayRef<SpecialMemberFunctionKind> SMFS,
+                          llvm::StringRef AndOr);
+  
+  ClassDefiningSpecialMembersMap ClassWithSpecialMembers;
+};
+
+} // namespace cppcoreguidelines
+} // namespace tidy
+} // namespace clang
+
+namespace llvm {
+/// Specialisation of DenseMapInfo to allow ClassDefId objects in DenseMaps
+/// FIXME: Move this to the corresponding cpp file as is done for
+/// clang-tidy/readability/IdentifierNamingCheck.cpp. 
+template <>
+struct DenseMapInfo<
+    clang::tidy::cppcoreguidelines::SpecialMemberFunctionsCheck::ClassDefId> {
+  using ClassDefId =
+    clang::tidy::cppcoreguidelines::SpecialMemberFunctionsCheck::ClassDefId;
+
+  static inline ClassDefId getEmptyKey() {
+    return ClassDefId(
+        clang::SourceLocation::getFromRawEncoding(static_cast<unsigned>(-1)),
+        "EMPTY");
+  }
+
+  static inline ClassDefId getTombstoneKey() {
+    return ClassDefId(
+        clang::SourceLocation::getFromRawEncoding(static_cast<unsigned>(-2)),
+        "TOMBSTONE");
+  }
+
+  static unsigned getHashValue(ClassDefId Val) {
+    assert(Val != getEmptyKey() && "Cannot hash the empty key!");
+    assert(Val != getTombstoneKey() && "Cannot hash the tombstone key!");
+
+    std::hash<ClassDefId::second_type> SecondHash;
+    return Val.first.getRawEncoding() + SecondHash(Val.second);
+  }
+
+  static bool isEqual(ClassDefId LHS, ClassDefId RHS) {
+    if (RHS == getEmptyKey())
+      return LHS == getEmptyKey();
+    if (RHS == getTombstoneKey())
+      return LHS == getTombstoneKey();
+    return LHS == RHS;
+  }
+};
+
+} // namespace llvm
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_SPECIAL_MEMBER_FUNCTIONS_H