Index: clang-tidy/misc/CMakeLists.txt =================================================================== --- clang-tidy/misc/CMakeLists.txt +++ clang-tidy/misc/CMakeLists.txt @@ -29,6 +29,7 @@ UnusedParametersCheck.cpp UnusedRAIICheck.cpp UniqueptrResetReleaseCheck.cpp + DeprecatedSpecialMemberGenerationCheck.cpp VirtualNearMissCheck.cpp LINK_LIBS Index: clang-tidy/misc/DeprecatedSpecialMemberGenerationCheck.h =================================================================== --- /dev/null +++ clang-tidy/misc/DeprecatedSpecialMemberGenerationCheck.h @@ -0,0 +1,54 @@ +//===--- DeprecatedSpecialMemberGenerationCheck.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_MISC_DEPRECATED_SPECIAL_MEMBER_GENERATION_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_DEPRECATED_SPECIAL_MEMBER_GENERATION_H + +#include "../ClangTidy.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// Finds classes where deprecated compiler-generation of special member +/// functions will generate a copy constructor or copy assignment operator. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/misc-deprecated-special-member-generation.html +class DeprecatedSpecialMemberGenerationCheck : public ClangTidyCheck { +public: + DeprecatedSpecialMemberGenerationCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + enum class SpecialFunctionKind { + CopyConstructor, + CopyAssignment, + Destructor + }; + + static std::string + buildFixIt(const CXXMethodDecl &MatchedDecl, StringRef ClassName, + DeprecatedSpecialMemberGenerationCheck::SpecialFunctionKind S); + static StringRef getDiagnosticFormat( + DeprecatedSpecialMemberGenerationCheck::SpecialFunctionKind S); + void + addDiagnosticAndFixIt(const ast_matchers::MatchFinder::MatchResult &Result, + const CXXMethodDecl &MatchedDecl, + SpecialFunctionKind S); +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_DEPRECATED_SPECIAL_MEMBER_GENERATION_H Index: clang-tidy/misc/DeprecatedSpecialMemberGenerationCheck.cpp =================================================================== --- /dev/null +++ clang-tidy/misc/DeprecatedSpecialMemberGenerationCheck.cpp @@ -0,0 +1,124 @@ +//===--- DeprecatedSpecialMemberGenerationCheck.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 "DeprecatedSpecialMemberGenerationCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +void DeprecatedSpecialMemberGenerationCheck::registerMatchers( + MatchFinder *Finder) { + + if (!getLangOpts().CPlusPlus) + return; + + Finder->addMatcher( + cxxRecordDecl(isDefinition(), + has(cxxDestructorDecl(unless(isImplicit())).bind("dtor")), + unless(anyOf(has(cxxConstructorDecl(isCopyConstructor(), + unless(isImplicit()))), + has(cxxMethodDecl(isCopyAssignmentOperator(), + unless(isImplicit())))))), + this); + + Finder->addMatcher( + cxxRecordDecl(isDefinition(), has(cxxConstructorDecl(isCopyConstructor(), + unless(isImplicit())) + .bind("copy-ctor")), + unless(has(cxxMethodDecl(isCopyAssignmentOperator())))), + this); + + Finder->addMatcher( + cxxRecordDecl( + isDefinition(), + has(cxxMethodDecl(isCopyAssignmentOperator(), unless(isImplicit())) + .bind("copy assignment")), + unless(has(cxxConstructorDecl(isCopyConstructor())))), + this); +} + +StringRef DeprecatedSpecialMemberGenerationCheck::getDiagnosticFormat( + DeprecatedSpecialMemberGenerationCheck::SpecialFunctionKind S) { + switch (S) { + case SpecialFunctionKind::Destructor: + return "class %0 defines a destructor but not a copy constructor or copy " + "assignment operator"; + case SpecialFunctionKind::CopyConstructor: + return "class %0 defines a copy constructor but not a copy assignment " + "operator"; + case SpecialFunctionKind::CopyAssignment: + return "class %0 defines a copy assignment operator but not a " + "copy constructor"; + } +}; + +std::string DeprecatedSpecialMemberGenerationCheck::buildFixIt( + const CXXMethodDecl &MatchedDecl, StringRef ClassName, + DeprecatedSpecialMemberGenerationCheck::SpecialFunctionKind S) { + + StringRef DeleteOrDefault = + (!isa(&MatchedDecl) && MatchedDecl.isDefaulted()) + ? "default" + : "delete"; + switch (S) { + case SpecialFunctionKind::Destructor: + return (llvm::Twine("\n") + ClassName + "(const " + ClassName + " &) = " + + DeleteOrDefault + ";\n" + ClassName + " & operator=(const " + + ClassName + " &) = " + DeleteOrDefault + ";\n") + .str(); + case SpecialFunctionKind::CopyConstructor: + return (llvm::Twine("\n") + ClassName + " & operator=(const " + ClassName + + " &) = " + DeleteOrDefault + ";\n") + .str(); + case SpecialFunctionKind::CopyAssignment: + return (llvm::Twine("") + ClassName + "(const " + ClassName + " &) = " + + DeleteOrDefault + ";\n") + .str(); + } +}; + +void DeprecatedSpecialMemberGenerationCheck::addDiagnosticAndFixIt( + const MatchFinder::MatchResult &Result, const CXXMethodDecl &MatchedDecl, + SpecialFunctionKind S) { + const CXXRecordDecl *Class = MatchedDecl.getParent(); + + DiagnosticBuilder Diag = + diag(MatchedDecl.getLocation(), getDiagnosticFormat(S)) << Class; + + if (!getLangOpts().CPlusPlus11) + return; + + Diag << FixItHint::CreateInsertion( + MatchedDecl.getLocStart(), buildFixIt(MatchedDecl, Class->getName(), S)); +} + +void DeprecatedSpecialMemberGenerationCheck::check( + const MatchFinder::MatchResult &Result) { + if (const auto *MatchedDecl = + Result.Nodes.getNodeAs("dtor")) + addDiagnosticAndFixIt(Result, *MatchedDecl, + SpecialFunctionKind::Destructor); + else if (const auto *MatchedDecl = + Result.Nodes.getNodeAs("copy-ctor")) + addDiagnosticAndFixIt(Result, *MatchedDecl, + SpecialFunctionKind::CopyConstructor); + else if (const auto *MatchedDecl = + Result.Nodes.getNodeAs("copy assignment")) + addDiagnosticAndFixIt(Result, *MatchedDecl, + SpecialFunctionKind::CopyAssignment); +} + +} // namespace misc +} // namespace tidy +} // namespace clang Index: clang-tidy/misc/MiscTidyModule.cpp =================================================================== --- clang-tidy/misc/MiscTidyModule.cpp +++ clang-tidy/misc/MiscTidyModule.cpp @@ -37,6 +37,7 @@ #include "UnusedAliasDeclsCheck.h" #include "UnusedParametersCheck.h" #include "UnusedRAIICheck.h" +#include "DeprecatedSpecialMemberGenerationCheck.h" #include "VirtualNearMissCheck.h" namespace clang { @@ -97,6 +98,8 @@ CheckFactories.registerCheck( "misc-unused-parameters"); CheckFactories.registerCheck("misc-unused-raii"); + CheckFactories.registerCheck( + "misc-deprecated-special-member-generation"); CheckFactories.registerCheck( "misc-virtual-near-miss"); } Index: docs/clang-tidy/checks/list.rst =================================================================== --- docs/clang-tidy/checks/list.rst +++ docs/clang-tidy/checks/list.rst @@ -46,6 +46,7 @@ misc-argument-comment misc-assert-side-effect misc-assign-operator-signature + misc-deprecated-special-member-generation misc-bool-pointer-implicit-conversion misc-definitions-in-headers misc-inaccurate-erase Index: docs/clang-tidy/checks/misc-deprecated-special-member-generation.rst =================================================================== --- /dev/null +++ docs/clang-tidy/checks/misc-deprecated-special-member-generation.rst @@ -0,0 +1,40 @@ +.. title:: clang-tidy - misc-deprecated-special-member-generation + +misc-deprecated-special-member-generation +========================================= + +Modern compilers generate copy constructors and copy assignment operators in +cases where generation of special functions is deprecated by the standard. + +C++14 standard [class.copy] paragraph 7: + +"If the class definition does not explicitly declare a copy constructor, one is +declared implicitly. If the class definition declares a move constructor or +move assignment operator, the implicitly declared copy constructor is defined +as deleted; otherwise, it is defined as defaulted (8.4). The latter case is +deprecated if the class has a user-declared copy assignment operator or a +user-declared destructor." + +C++14 standard [class.copy] paragraph 18: + +"If the class definition does not explicitly declare a copy assignment +operator, one is declared implicitly. If the class definition declares a move +constructor or move assignment operator, the implicitly declared copy +assignment operator is defined as deleted; otherwise, it is defined as +defaulted (8.4). The latter case is deprecated if the class has a user-declared +copy constructor or a user-declared destructor." + +This check finds classes where deprecated compiler-generation of special member +functions may generate a copy constructor or copy assignment operator. + +If the destructor is declared, missing copy constructors or copy assignment +operators are declared '=delete'. + +If a copy constructor or assignment operator is declared '=default' and no +destructor is declared, the missing copy constructor or assignment operator is +declared '=default'. + +Otherwise, if a copy constructor or assignment operator is declared and no +destructor is declared, the missing copy constructor or assignment operator is +declared '=delete'. + Index: test/clang-tidy/misc-deprecated-special-member-generation.cpp =================================================================== --- /dev/null +++ test/clang-tidy/misc-deprecated-special-member-generation.cpp @@ -0,0 +1,122 @@ +// RUN: %check_clang_tidy %s misc-deprecated-special-member-generation %t + +// +// User defined copy constructors +// +class DeclaresCopyConstructor { + DeclaresCopyConstructor(const DeclaresCopyConstructor &); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: class 'DeclaresCopyConstructor' defines a copy constructor but not a copy assignment operator [misc-deprecated-special-member-generation] +}; + +// CHECK-FIXES: DeclaresCopyConstructor & operator=(const DeclaresCopyConstructor &) = delete; + +class DefinesDefaultedCopyConstructor { + DefinesDefaultedCopyConstructor(const DefinesDefaultedCopyConstructor &) = default; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: class 'DefinesDefaultedCopyConstructor' defines a copy constructor but not a copy assignment operator [misc-deprecated-special-member-generation] +}; + +// CHECK-FIXES: DefinesDefaultedCopyConstructor & operator=(const DefinesDefaultedCopyConstructor &) = default; + +class DefinesDeletedCopyConstructor { + DefinesDeletedCopyConstructor(const DefinesDeletedCopyConstructor &) = delete; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: class 'DefinesDeletedCopyConstructor' defines a copy constructor but not a copy assignment operator [misc-deprecated-special-member-generation] +}; + +// CHECK-FIXES: DefinesDeletedCopyConstructor & operator=(const DefinesDeletedCopyConstructor &) = delete; + + +// +// User defined copy assignment +// +class DeclaresCopyAssignment { + DeclaresCopyAssignment & operator=(const DeclaresCopyAssignment &); + // CHECK-MESSAGES: :[[@LINE-1]]:28: warning: class 'DeclaresCopyAssignment' defines a copy assignment operator but not a copy constructor [misc-deprecated-special-member-generation] +}; + +// CHECK-FIXES: DeclaresCopyAssignment(const DeclaresCopyAssignment &) = delete; + +class DefinesDefaultedCopyAssignment { + DefinesDefaultedCopyAssignment & operator=(const DefinesDefaultedCopyAssignment &) = default; + // CHECK-MESSAGES: :[[@LINE-1]]:36: warning: class 'DefinesDefaultedCopyAssignment' defines a copy assignment operator but not a copy constructor [misc-deprecated-special-member-generation] +}; + +// CHECK-FIXES: DefinesDefaultedCopyAssignment(const DefinesDefaultedCopyAssignment &) = default; + +class DefinesDeletedCopyAssignment { + DefinesDeletedCopyAssignment & operator=(const DefinesDeletedCopyAssignment &) = delete; + // CHECK-MESSAGES: :[[@LINE-1]]:34: warning: class 'DefinesDeletedCopyAssignment' defines a copy assignment operator but not a copy constructor [misc-deprecated-special-member-generation] +}; + +// CHECK-FIXES: DefinesDeletedCopyAssignment(const DefinesDeletedCopyAssignment &) = delete; + +class DeclaresDestructor { + ~DeclaresDestructor(); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: class 'DeclaresDestructor' defines a destructor but not a copy constructor or copy assignment operator [misc-deprecated-special-member-generation] +}; +// CHECK-FIXES: DeclaresDestructor(const DeclaresDestructor &) = delete; +// CHECK-FIXES: DeclaresDestructor & operator=(const DeclaresDestructor &) = delete; + +class DefinesDestructor { + ~DefinesDestructor() {} + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: class 'DefinesDestructor' defines a destructor but not a copy constructor or copy assignment operator [misc-deprecated-special-member-generation] +}; +// CHECK-FIXES: DefinesDestructor(const DefinesDestructor &) = delete; +// CHECK-FIXES: DefinesDestructor & operator=(const DefinesDestructor &) = delete; + + +class DefinesDefaultedDestructor { + ~DefinesDefaultedDestructor() = default; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: class 'DefinesDefaultedDestructor' defines a destructor but not a copy constructor or copy assignment operator [misc-deprecated-special-member-generation] +}; +// CHECK-FIXES: DefinesDefaultedDestructor(const DefinesDefaultedDestructor &) = delete; +// CHECK-FIXES: DefinesDefaultedDestructor & operator=(const DefinesDefaultedDestructor &) = delete; + + +class DefinesDeletedDestructor { + ~DefinesDeletedDestructor() = delete; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: class 'DefinesDeletedDestructor' defines a destructor but not a copy constructor or copy assignment operator [misc-deprecated-special-member-generation] +}; +// CHECK-FIXES: DefinesDeletedDestructor(const DefinesDeletedDestructor &) = delete; +// CHECK-FIXES: DefinesDeletedDestructor & operator=(const DefinesDeletedDestructor &) = delete; + +template +class ClassTemplateDeclaresDestructor { + ~ClassTemplateDeclaresDestructor(); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: class 'ClassTemplateDeclaresDestructor' defines a destructor but not a copy constructor or copy assignment operator [misc-deprecated-special-member-generation] +}; +// CHECK-FIXES: ClassTemplateDeclaresDestructor(const ClassTemplateDeclaresDestructor &) = delete; +// CHECK-FIXES: ClassTemplateDeclaresDestructor & operator=(const ClassTemplateDeclaresDestructor &) = delete; + +template <> +class ClassTemplateDeclaresDestructor { + ~ClassTemplateDeclaresDestructor(); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: class 'ClassTemplateDeclaresDestructor' defines a destructor but not a copy constructor or copy assignment operator [misc-deprecated-special-member-generation] +}; +// CHECK-FIXES: ClassTemplateDeclaresDestructor(const ClassTemplateDeclaresDestructor &) = delete; +// CHECK-FIXES: ClassTemplateDeclaresDestructor & operator=(const ClassTemplateDeclaresDestructor &) = delete; + +#define DEFAULTED_DESTRUCTOR(X) ~X() = default + +class MacroDefaultsDestructor { + DEFAULTED_DESTRUCTOR(MacroDefaultsDestructor); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: class 'MacroDefaultsDestructor' defines a destructor but not a copy constructor or copy assignment operator [misc-deprecated-special-member-generation] +}; +// CHECK-FIXES: MacroDefaultsDestructor(const MacroDefaultsDestructor &) = delete; +// CHECK-FIXES: MacroDefaultsDestructor & operator=(const MacroDefaultsDestructor &) = delete; + +#define VIRTUAL_BASE_CLASS(X) class X { virtual ~X() = default; } + +VIRTUAL_BASE_CLASS(VirtualBase); +// CHECK-MESSAGES: :[[@LINE-1]]:1: warning: class 'VirtualBase' defines a destructor but not a copy constructor or copy assignment operator [misc-deprecated-special-member-generation] + +class DeclaresAllThree { + ~DeclaresAllThree(); + DeclaresAllThree(const DeclaresAllThree&); + DeclaresAllThree & operator=(const DeclaresAllThree&); +}; + +class DeclaresCopyAndAssignment { + DeclaresCopyAndAssignment(const DeclaresCopyAndAssignment&); + DeclaresCopyAndAssignment & operator=(const DeclaresCopyAndAssignment&); +}; +