mbid created this revision. Herald added a subscriber: carlosgalvezp. Herald added a reviewer: njames93. Herald added a project: All. mbid requested review of this revision. Herald added projects: clang, clang-tools-extra. Herald added a subscriber: cfe-commits.
Repository: rG LLVM Github Monorepo https://reviews.llvm.org/D138031 Files: clang-tools-extra/clang-tidy/performance/CMakeLists.txt clang-tools-extra/clang-tidy/performance/MissingMoveConstructorCheck.cpp clang-tools-extra/clang-tidy/performance/MissingMoveConstructorCheck.h clang-tools-extra/clang-tidy/performance/PerformanceTidyModule.cpp clang-tools-extra/docs/ReleaseNotes.rst clang-tools-extra/docs/clang-tidy/checks/list.rst clang-tools-extra/docs/clang-tidy/checks/performance/missing-move-constructor.rst clang-tools-extra/test/clang-tidy/checkers/performance/missing-move-constructor.cpp clang/include/clang/Lex/Token.h
Index: clang/include/clang/Lex/Token.h =================================================================== --- clang/include/clang/Lex/Token.h +++ clang/include/clang/Lex/Token.h @@ -96,9 +96,7 @@ /// "if (Tok.is(tok::l_brace)) {...}". bool is(tok::TokenKind K) const { return Kind == K; } bool isNot(tok::TokenKind K) const { return Kind != K; } - bool isOneOf(tok::TokenKind K1, tok::TokenKind K2) const { - return is(K1) || is(K2); - } + bool isOneOf() const { return false; } template <typename... Ts> bool isOneOf(tok::TokenKind K1, Ts... Ks) const { return is(K1) || isOneOf(Ks...); } Index: clang-tools-extra/test/clang-tidy/checkers/performance/missing-move-constructor.cpp =================================================================== --- /dev/null +++ clang-tools-extra/test/clang-tidy/checkers/performance/missing-move-constructor.cpp @@ -0,0 +1,99 @@ +// RUN: %check_clang_tidy %s performance-missing-move-constructor %t + +class OffendingDtor { +public: + ~OffendingDtor() = default; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: explicit destructor prevents generation of implicit move constructor and move assignment operator [performance-missing-move-constructor] +}; + +class OffendingCopyCtor { +public: + OffendingCopyCtor(const OffendingCopyCtor &) = default; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: explicit copy constructor prevents generation of implicit move constructor and move assignment operator [performance-missing-move-constructor] +}; + +class OffendingMoveCtor { +public: + OffendingMoveCtor(OffendingMoveCtor &&) = default; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: explicit move constructor prevents generation of implicit move assignment operator [performance-missing-move-constructor] + + // Copy assignment operator is not implicitly defined because of the explicit + // move constructor. + OffendingMoveCtor &operator=(const OffendingMoveCtor &) = default; +}; + +class OffendingCopyAssign { +public: + OffendingCopyAssign &operator=(const OffendingCopyAssign &) = default; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: explicit copy assignment operator prevents generation of implicit move constructor and move assignment operator [performance-missing-move-constructor] +}; + +class OffendingMoveAssign { +public: + // Copy constructor is not implicitly defined because of the explicit move + // assignment operator. This means that the warning appears here, because the + // copy constructor has higher priority in where we print than the move + // assignment operator. + OffendingMoveAssign(const OffendingMoveAssign &) = default; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: explicit copy constructor prevents generation of implicit move constructor [performance-missing-move-constructor] + + OffendingMoveAssign &operator=(OffendingMoveAssign &&) = default; +}; + + +class InlineFix { +public: + InlineFix(const InlineFix &) = default; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: explicit copy constructor prevents generation of implicit move constructor and move assignment operator [performance-missing-move-constructor] + // CHECK-FIXES: InlineFix(InlineFix &&) = default; + + InlineFix& operator=(const InlineFix &) = default; + // CHECK-FIXES: InlineFix &operator=(InlineFix &&) = default; +}; + +namespace N { + +class OutOfLineFix { +public: + OutOfLineFix(const OutOfLineFix &); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: explicit copy constructor prevents generation of implicit move constructor and move assignment operator [performance-missing-move-constructor] + // CHECK-FIXES: OutOfLineFix(OutOfLineFix &&); + + OutOfLineFix& operator=(const OutOfLineFix &); + // CHECK-FIXES: OutOfLineFix &operator=(OutOfLineFix &&); +}; + +} + +N::OutOfLineFix::OutOfLineFix(const OutOfLineFix &) = default; +// CHECK-FIXES: N::OutOfLineFix::OutOfLineFix(OutOfLineFix &&) = default; + +N::OutOfLineFix &N::OutOfLineFix::operator=(const OutOfLineFix &) = default; +// CHECK-FIXES: N::OutOfLineFix &N::OutOfLineFix::operator=(OutOfLineFix &&) = default; + +class InlineFixWithoutConstructor { + // CHECK-FIXES: InlineFixWithoutConstructor(InlineFixWithoutConstructor &&) = default; + // CHECK-FIXES: InlineFixWithoutConstructor &operator=(InlineFixWithoutConstructor &&) = default; + ~InlineFixWithoutConstructor() = default; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: explicit destructor prevents generation of implicit move constructor and move assignment operator [performance-missing-move-constructor] +}; + +class InlineFixWithConstructorWithoutCopy { +public: + InlineFixWithConstructorWithoutCopy() {} + // CHECK-FIXES: InlineFixWithConstructorWithoutCopy(InlineFixWithConstructorWithoutCopy &&) = default; + // CHECK-FIXES: InlineFixWithConstructorWithoutCopy &operator=(InlineFixWithConstructorWithoutCopy &&) = default; + + ~InlineFixWithConstructorWithoutCopy() = default; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: explicit destructor prevents generation of implicit move constructor and move assignment operator [performance-missing-move-constructor] +}; + +class InlineFixWithDefaultedConstructorWithoutCopy { +public: + InlineFixWithDefaultedConstructorWithoutCopy() = default; + // CHECK-FIXES: InlineFixWithDefaultedConstructorWithoutCopy(InlineFixWithDefaultedConstructorWithoutCopy &&) = default; + // CHECK-FIXES: InlineFixWithDefaultedConstructorWithoutCopy &operator=(InlineFixWithDefaultedConstructorWithoutCopy &&) = default; + + ~InlineFixWithDefaultedConstructorWithoutCopy() = default; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: explicit destructor prevents generation of implicit move constructor and move assignment operator [performance-missing-move-constructor] +}; Index: clang-tools-extra/docs/clang-tidy/checks/performance/missing-move-constructor.rst =================================================================== --- /dev/null +++ clang-tools-extra/docs/clang-tidy/checks/performance/missing-move-constructor.rst @@ -0,0 +1,25 @@ +.. title:: clang-tidy - performance-missing-move-constructor + +performance-missing-move-constructor +==================================== + +Warns when a class has a copy constructor but not move constructor. + +Classes that have one of the following member methods do not have `implicit move constructors <https://en.cppreference.com/w/cpp/language/move_constructor>`: +- A copy constructor. +- An assignment operator. +- A destructor. + +This check warns about classes that are copyable but do not have an implicit +move constructor because one of the methods above is explicitly declared. +Similar rules apply to implicit move assignment operators, and the check warns +also if those are not generated. + +.. code-block:: c++ + class C { + // This explicit copy constructor prevents generation of the implicit + // move constructor. + C(const C &that) { + //... + } + }; Index: clang-tools-extra/docs/clang-tidy/checks/list.rst =================================================================== --- clang-tools-extra/docs/clang-tidy/checks/list.rst +++ clang-tools-extra/docs/clang-tidy/checks/list.rst @@ -310,6 +310,7 @@ `performance-inefficient-algorithm <performance/inefficient-algorithm.html>`_, "Yes" `performance-inefficient-string-concatenation <performance/inefficient-string-concatenation.html>`_, `performance-inefficient-vector-operation <performance/inefficient-vector-operation.html>`_, "Yes" + `performance-missing-move-constructor <performance/missing-move-constructor.html>`_, "Yes" `performance-move-const-arg <performance/move-const-arg.html>`_, "Yes" `performance-move-constructor-init <performance/move-constructor-init.html>`_, `performance-no-automatic-move <performance/no-automatic-move.html>`_, Index: clang-tools-extra/docs/ReleaseNotes.rst =================================================================== --- clang-tools-extra/docs/ReleaseNotes.rst +++ clang-tools-extra/docs/ReleaseNotes.rst @@ -115,6 +115,12 @@ Warns when using ``do-while`` loops. +- New :doc:`performance-missing-move-constructor + <clang-tidy/checks/performance/missing-move-constructor>` check. + + Warns when a class declares a copy constructor but no move constructor, and + similarly for assignment operators. + New check aliases ^^^^^^^^^^^^^^^^^ Index: clang-tools-extra/clang-tidy/performance/PerformanceTidyModule.cpp =================================================================== --- clang-tools-extra/clang-tidy/performance/PerformanceTidyModule.cpp +++ clang-tools-extra/clang-tidy/performance/PerformanceTidyModule.cpp @@ -15,6 +15,7 @@ #include "InefficientAlgorithmCheck.h" #include "InefficientStringConcatenationCheck.h" #include "InefficientVectorOperationCheck.h" +#include "MissingMoveConstructorCheck.h" #include "MoveConstArgCheck.h" #include "MoveConstructorInitCheck.h" #include "NoAutomaticMoveCheck.h" @@ -44,6 +45,8 @@ "performance-inefficient-string-concatenation"); CheckFactories.registerCheck<InefficientVectorOperationCheck>( "performance-inefficient-vector-operation"); + CheckFactories.registerCheck<MissingMoveConstructorCheck>( + "performance-missing-move-constructor"); CheckFactories.registerCheck<MoveConstArgCheck>( "performance-move-const-arg"); CheckFactories.registerCheck<MoveConstructorInitCheck>( Index: clang-tools-extra/clang-tidy/performance/MissingMoveConstructorCheck.h =================================================================== --- /dev/null +++ clang-tools-extra/clang-tidy/performance/MissingMoveConstructorCheck.h @@ -0,0 +1,35 @@ +//===--- MissingMoveConstructorCheck.h - clang-tidy -------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_MISSINGMOVECONSTRUCTORCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_MISSINGMOVECONSTRUCTORCHECK_H + +#include "../ClangTidyCheck.h" + +namespace clang { +namespace tidy { +namespace performance { + +class MissingMoveConstructorCheck : public ClangTidyCheck { +public: + MissingMoveConstructorCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + void checkConstructor(ASTContext &Context, + const CXXConstructorDecl &CopyCtor); + void checkAssignment(ASTContext &Context, const CXXMethodDecl &CopyAssign); +}; + +} // namespace performance +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_MISSINGMOVECONSTRUCTORCHECK_H Index: clang-tools-extra/clang-tidy/performance/MissingMoveConstructorCheck.cpp =================================================================== --- /dev/null +++ clang-tools-extra/clang-tidy/performance/MissingMoveConstructorCheck.cpp @@ -0,0 +1,598 @@ +//===--- MissingMoveConstructorCheck.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 "MissingMoveConstructorCheck.h" +#include "../utils/LexerUtils.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" +#include "llvm/Support/raw_ostream.h" + +#include <algorithm> +#include <optional> + +using namespace clang::ast_matchers; +using namespace clang::tidy::utils::lexer; + +namespace clang { +namespace tidy { +namespace performance { + +namespace { + +/// A structure that contains explicit declarations preventing implicit +/// generation of move contsructors or move assignment operators. +struct ImplicitMovePreventingDecls { + const CXXDestructorDecl *Destructor = nullptr; + + const CXXConstructorDecl *CopyConstructor = nullptr; + const CXXConstructorDecl *MoveConstructor = nullptr; + + const CXXMethodDecl *CopyAssignment = nullptr; + const CXXMethodDecl *MoveAssignment = nullptr; +}; + +/// Returns true iff `Decls` contains at least one field that is not nullptr. +bool isSome(const ImplicitMovePreventingDecls &Decls) { + return Decls.Destructor || Decls.CopyConstructor || Decls.MoveConstructor || + Decls.CopyAssignment || Decls.MoveAssignment; +} + +/// Finds all implicit move preventing declarations in the given `Record`. +/// +/// If there are no such declarations, all fields in the result structure are +/// null. +ImplicitMovePreventingDecls +findImplicitMovePreventingDecls(const CXXRecordDecl &Record) { + ImplicitMovePreventingDecls Result; + + // Check for a user-declared destructor. + const CXXDestructorDecl *const Dtor = Record.getDestructor(); + if (Dtor && !Dtor->isImplicit()) { + Result.Destructor = Dtor; + } + + // Check for move or copy constructors. If there are several of one of the + // two types, we select the last ones in declaration order. + for (const CXXConstructorDecl *Ctor : Record.ctors()) { + if (Ctor->isImplicit()) { + continue; + } + + if (Ctor->isCopyConstructor()) { + Result.CopyConstructor = Ctor; + } + + if (Ctor->isMoveConstructor()) { + Result.MoveConstructor = Ctor; + } + } + + // Check for move or copy constructors. If there are several of one of the + // two types, we select the last ones in declaration order. + for (const CXXMethodDecl *Method : Record.methods()) { + if (Method->isImplicit()) { + continue; + } + + if (Method->isCopyAssignmentOperator()) { + Result.CopyAssignment = Method; + } + + if (Method->isMoveAssignmentOperator()) { + Result.MoveAssignment = Method; + } + } + + return Result; +} + +/// A class misses a move constructor if it has a copy constructor but no move +/// constructor. +bool missesMoveConstructor(const CXXRecordDecl &Record) { + const auto CopyCtorIt = std::find_if( + Record.ctor_begin(), Record.ctor_end(), + [](CXXConstructorDecl *Ctor) { return Ctor->isCopyConstructor(); }); + const bool HasCopyCtor = + CopyCtorIt != Record.ctor_end() || Record.needsImplicitCopyConstructor(); + + const auto MoveCtorIt = std::find_if( + Record.ctor_begin(), Record.ctor_end(), + [](CXXConstructorDecl *Ctor) { return Ctor->isMoveConstructor(); }); + const bool HasMoveCtor = MoveCtorIt != Record.ctor_end(); + + return HasCopyCtor && !HasMoveCtor; +} + +/// A class misses a move assignment operator if it has a copy assignment +/// operator but no move assignment operator. +bool missesMoveAssignmentOperator(const CXXRecordDecl &Record) { + const auto CopyAssignIt = std::find_if( + Record.method_begin(), Record.method_end(), + [](CXXMethodDecl *Method) { return Method->isCopyAssignmentOperator(); }); + const bool HasCopyAssign = CopyAssignIt != Record.method_end() || + Record.needsImplicitCopyAssignment(); + + const auto MoveAssignIt = std::find_if( + Record.method_begin(), Record.method_end(), + [](CXXMethodDecl *Method) { return Method->isMoveAssignmentOperator(); }); + const bool HasMoveAssign = MoveAssignIt != Record.method_end(); + + return HasCopyAssign && !HasMoveAssign; +} + +std::pair<SourceLocation, std::string> +diagnosticMessage(const ImplicitMovePreventingDecls &OffendingDecls, + const bool MissesMoveConstructor, + const bool MissesMoveAssignmentOperator) { + SourceLocation Loc; + std::string MessageString; + llvm::raw_string_ostream Message(MessageString); + + Message << "explicit"; + + if (OffendingDecls.Destructor != nullptr) { + Message << " destructor"; + Loc = OffendingDecls.Destructor->getBeginLoc(); + } else if (OffendingDecls.CopyConstructor != nullptr) { + Message << " copy constructor"; + Loc = OffendingDecls.CopyConstructor->getBeginLoc(); + } else if (OffendingDecls.MoveConstructor != nullptr) { + Message << " move constructor"; + Loc = OffendingDecls.MoveConstructor->getBeginLoc(); + } else if (OffendingDecls.CopyAssignment != nullptr) { + Message << " copy assignment operator"; + Loc = OffendingDecls.CopyAssignment->getBeginLoc(); + } else if (OffendingDecls.MoveAssignment != nullptr) { + Message << " move assignment operator"; + Loc = OffendingDecls.MoveAssignment->getBeginLoc(); + } else { + llvm_unreachable("no offending declaration"); + } + + Message << " prevents generation of implicit"; + if (MissesMoveConstructor && MissesMoveAssignmentOperator) { + Message << " move constructor and move assignment operator"; + } else if (!MissesMoveConstructor && MissesMoveAssignmentOperator) { + Message << " move assignment operator"; + } else if (MissesMoveConstructor && !MissesMoveAssignmentOperator) { + Message << " move constructor"; + } else { + llvm_unreachable("nothing missing"); + } + + return {Loc, std::move(MessageString)}; +} + +enum class NewlinePosition { + before, + after, +}; + +/// An ostream that is meant to print a single line into an std::string. Will +/// insert a newline character before or after the line depending on a +/// `NewlinePosition`. +class LineRawStringOstream { +public: + LineRawStringOstream(NewlinePosition NewlinePos) : NewlinePos(NewlinePos) { + if (NewlinePos == NewlinePosition::before) { + *this << "\n"; + } + } + + // No copy/move: We have fields referencing each other. + LineRawStringOstream(const LineRawStringOstream &) = delete; + LineRawStringOstream &operator=(const LineRawStringOstream &) = delete; + + template <class T> LineRawStringOstream &operator<<(T &&Arg) { + Stream << std::forward<T>(Arg); + return *this; + } + + /// Retrieve the line as a string. + std::string str() && { + if (NewlinePos == NewlinePosition::after) { + *this << "\n"; + } + return std::move(String); + } + +private: + NewlinePosition NewlinePos; + std::string String; + llvm::raw_string_ostream Stream{String}; +}; + +/// Information about where a method declaration-definition pair should be +/// emitted. +struct MethodInsertInfo { + // If DeclarationLocation and DefinitionLocation agree, then the method + // should be defined inline. Otherwise, separate declaration and definition + // should be emitted. + SourceLocation DeclarationLocation; + SourceLocation DefinitionLocation; + + // Fields describing whether a newline should be added before the emitted + // code or after. + NewlinePosition NewlinePos; + + // Unqualified name of the record/class this method belongs to. + std::string RecordName; + // A qualified version of RecordName that can be use to refer to the type at + // DefinitionLocation. + std::string QualifiedRecordName; +}; + +void GenerateMoveConstructor(DiagnosticBuilder &Diagnostic, + const MethodInsertInfo &InsertInfo) { + if (InsertInfo.DeclarationLocation == InsertInfo.DefinitionLocation) { + LineRawStringOstream MoveCtor{InsertInfo.NewlinePos}; + MoveCtor << InsertInfo.RecordName << "(" << InsertInfo.RecordName + << " &&) = default;"; + Diagnostic << FixItHint::CreateInsertion(InsertInfo.DeclarationLocation, + std::move(MoveCtor).str()); + } else { + LineRawStringOstream MoveCtorDecl(InsertInfo.NewlinePos); + MoveCtorDecl << InsertInfo.RecordName << "(" << InsertInfo.RecordName + << " &&);"; + Diagnostic << FixItHint::CreateInsertion(InsertInfo.DeclarationLocation, + std::move(MoveCtorDecl).str()); + + LineRawStringOstream MoveCtorDef(InsertInfo.NewlinePos); + MoveCtorDef << InsertInfo.QualifiedRecordName + << "::" << InsertInfo.RecordName << "(" << InsertInfo.RecordName + << " &&) = default;"; + Diagnostic << FixItHint::CreateInsertion(InsertInfo.DefinitionLocation, + std::move(MoveCtorDef).str()); + } +} + +void GenerateMoveAssignment(DiagnosticBuilder &Diagnostic, + const MethodInsertInfo &InsertInfo) { + if (InsertInfo.DeclarationLocation == InsertInfo.DefinitionLocation) { + LineRawStringOstream MoveAssign{InsertInfo.NewlinePos}; + MoveAssign << InsertInfo.RecordName << " &operator=(" + << InsertInfo.RecordName << " &&) = default;"; + Diagnostic << FixItHint::CreateInsertion(InsertInfo.DeclarationLocation, + std::move(MoveAssign).str()); + } else { + LineRawStringOstream MoveAssignDecl{InsertInfo.NewlinePos}; + MoveAssignDecl << InsertInfo.RecordName << " &operator=(" + << InsertInfo.RecordName << " &&);"; + + LineRawStringOstream MoveAssignDef{InsertInfo.NewlinePos}; + MoveAssignDef << InsertInfo.QualifiedRecordName << " &" + << InsertInfo.QualifiedRecordName << "::operator=(" + << InsertInfo.RecordName << " &&) = default;"; + + Diagnostic << FixItHint::CreateInsertion(InsertInfo.DeclarationLocation, + std::move(MoveAssignDecl).str()); + Diagnostic << FixItHint::CreateInsertion(InsertInfo.DefinitionLocation, + std::move(MoveAssignDef).str()); + } +} + +/// Retrieve the way an out-of-line method declaration refers to the type it +/// belongs to. +std::optional<std::string> +QualifiedOutOfLineRecordName(const LangOptions &LangOpts, + const CXXMethodDecl &MethodDecl) { + std::string ResultString; + { + llvm::raw_string_ostream Result{ResultString}; + MethodDecl.getQualifier()->print(Result, PrintingPolicy(LangOpts)); + } + + // ResultString should contain the suffix "::". We need to remove this + // because we want the string refering to the type itself. + if (StringRef(ResultString).substr(ResultString.size() - 2) != "::") { + return std::nullopt; + } + ResultString.resize(ResultString.size() - 2); + + return ResultString; +} + +/// Retrieve info for insertion after a specified method. +std::optional<MethodInsertInfo> +MethodInsertInfoAfterMethod(const CXXMethodDecl &Method, SourceManager &SrcMgr, + const LangOptions &LangOpts) { + const CXXRecordDecl *Record = Method.getParent(); + if (!Record) { + return std::nullopt; + } + + const CXXMethodDecl *Def = cast<CXXMethodDecl>(Method.getDefinition()); + const CXXMethodDecl *Decl = Method.getCanonicalDecl(); + if (!Def || !Decl) { + return std::nullopt; + } + + SourceLocation DefinitionLocation; + if (const Stmt *Body = Def->getBody()) { + DefinitionLocation = Body->getEndLoc().getLocWithOffset(1); + } else if (Def->isDefaulted() || Def->isDeleted()) { + DefinitionLocation = + findNextAnyTokenKind(Def->getEndLoc(), SrcMgr, LangOpts, tok::semi) + .getLocWithOffset(1); + } else { + return std::nullopt; + } + + const SourceLocation DeclarationLocation = + Def == Decl + ? DefinitionLocation + : findNextAnyTokenKind(Decl->getEndLoc(), SrcMgr, LangOpts, tok::semi) + .getLocWithOffset(1); + + std::string QualifiedRecordName; + if (Def == Decl) { + QualifiedRecordName = Record->getName().str(); + } else { + std::optional<std::string> QualName = + QualifiedOutOfLineRecordName(LangOpts, *Def); + if (!QualName.has_value()) { + return std::nullopt; + } + QualifiedRecordName = std::move(*QualName); + } + + return MethodInsertInfo{ + DeclarationLocation, + DefinitionLocation, + NewlinePosition::before, + Record->getName().str(), + std::move(QualifiedRecordName), + }; +} + +/// Retrieve info for insertion before a specified method. +std::optional<MethodInsertInfo> +MethodInsertInfoBeforeMethod(const CXXMethodDecl &Method, SourceManager &SrcMgr, + const LangOptions &LangOpts) { + const CXXRecordDecl *Record = Method.getParent(); + if (!Record) { + return std::nullopt; + } + + const CXXMethodDecl *Def = cast<CXXMethodDecl>(Method.getDefinition()); + const CXXMethodDecl *Decl = Method.getCanonicalDecl(); + if (!Def || !Decl) { + return std::nullopt; + } + + const SourceLocation DefinitionLocation = Def->getBeginLoc(); + const SourceLocation DeclarationLocation = Decl->getBeginLoc(); + + std::string QualifiedRecordName; + if (Def == Decl) { + QualifiedRecordName = Record->getName().str(); + } else { + std::optional<std::string> QualName = + QualifiedOutOfLineRecordName(LangOpts, *Def); + if (!QualName.has_value()) { + return std::nullopt; + } + QualifiedRecordName = std::move(*QualName); + } + + return MethodInsertInfo{ + DeclarationLocation, + DefinitionLocation, + NewlinePosition::after, + Record->getName().str(), + std::move(QualifiedRecordName), + }; +} + +const CXXConstructorDecl *FindLastConstructor(const CXXRecordDecl &Record, + SourceManager &SrcMgr) { + const CXXConstructorDecl *LastCtor = nullptr; + for (CXXConstructorDecl *Ctor : Record.ctors()) { + if (!LastCtor || SrcMgr.isBeforeInTranslationUnit(LastCtor->getBeginLoc(), + Ctor->getBeginLoc())) { + LastCtor = Ctor; + } + } + return LastCtor; +} + +const CXXMethodDecl *FindFirstAssignmentOperator(const CXXRecordDecl &Record, + SourceManager &SrcMgr) { + const CXXMethodDecl *FirstAssign = nullptr; + for (CXXMethodDecl *Method : Record.methods()) { + if (!Method->isCopyAssignmentOperator() && + !Method->isCopyAssignmentOperator()) { + continue; + } + + if (!FirstAssign || + SrcMgr.isBeforeInTranslationUnit(Method->getBeginLoc(), + FirstAssign->getBeginLoc())) { + FirstAssign = Method; + } + } + + return FirstAssign; +} + +/// Find a general location where a move constructor or move assignment +/// operator should be defined/declared. This follows the google style guide: +/// After constructors, before assignment operators, before destructors. +/// +/// This method should be called in case there is no obvious place to put the +/// move method (i.e. there is no user-declared copying version). +std::optional<MethodInsertInfo> +FindMoveMethodLocation(const LangOptions &LangOpts, SourceManager &SrcMgr, + const CXXRecordDecl &Record) { + const CXXConstructorDecl *LastCtor = FindLastConstructor(Record, SrcMgr); + if (LastCtor) { + return MethodInsertInfoAfterMethod(*LastCtor, SrcMgr, LangOpts); + } + + const CXXMethodDecl *LastAssign = FindFirstAssignmentOperator(Record, SrcMgr); + if (LastCtor) { + return MethodInsertInfoBeforeMethod(*LastAssign, SrcMgr, LangOpts); + } + + const CXXDestructorDecl *Destructor = Record.getDestructor(); + if (Destructor) { + return MethodInsertInfoBeforeMethod(*Destructor, SrcMgr, LangOpts); + } + + return std::nullopt; +} + +/// Try to emit FixIt hints that fix a missing move constructor. +void FixMoveConstructor(DiagnosticBuilder &Diagnostic, + const CXXRecordDecl &Record, SourceManager &SrcMgr, + const LangOptions &LangOpts) { + if (Record.needsImplicitCopyConstructor()) { + std::optional<MethodInsertInfo> InsertInfo = + FindMoveMethodLocation(LangOpts, SrcMgr, Record); + if (InsertInfo.has_value()) { + GenerateMoveConstructor(Diagnostic, *InsertInfo); + } + return; + } + + // Find definition and declaration of the copy constructor. The new move + // constructor should be defined/declared next to the copy constructor. If we + // can't find declaration or definition of the copy constructor, we can't + // suggest a fix. + const auto CopyCtorIt = std::find_if( + Record.ctor_begin(), Record.ctor_end(), + [](CXXConstructorDecl *Ctor) { return Ctor->isCopyConstructor(); }); + if (CopyCtorIt == Record.ctor_end()) { + return; + } + const CXXConstructorDecl *CopyCtor = *CopyCtorIt; + + const FunctionDecl *CopyCtorDef = CopyCtor->getDefinition(); + if (!CopyCtorDef || !CopyCtorDef->isDefaulted()) { + // We only know how to generate default move constructors. If a class needs + // a custom copy constructor, then it likely needs a custom move + // constructor, so we're not trying to automatically fix this case. + return; + } + + std::optional<MethodInsertInfo> InsertInfo = + MethodInsertInfoAfterMethod(*CopyCtor, SrcMgr, LangOpts); + if (InsertInfo) { + GenerateMoveConstructor(Diagnostic, *InsertInfo); + } +} + +/// Try to emit FixIt hints that fix a missing move assignment operator. +void FixMoveAssignment(DiagnosticBuilder &Diagnostic, + const CXXRecordDecl &Record, SourceManager &SrcMgr, + const LangOptions &LangOpts) { + // Move assignments are generated analogously to move constructors. + + if (Record.needsImplicitCopyAssignment()) { + std::optional<MethodInsertInfo> InsertInfo = + FindMoveMethodLocation(LangOpts, SrcMgr, Record); + if (InsertInfo.has_value()) { + GenerateMoveAssignment(Diagnostic, *InsertInfo); + } + return; + } + + const auto CopyAssignIt = std::find_if( + Record.method_begin(), Record.method_end(), + [](CXXMethodDecl *Method) { return Method->isCopyAssignmentOperator(); }); + if (CopyAssignIt == Record.method_end()) { + return; + } + const CXXMethodDecl *CopyAssign = *CopyAssignIt; + + const FunctionDecl *CopyAssignDef = CopyAssign->getDefinition(); + if (!CopyAssignDef || !CopyAssignDef->isDefaulted()) { + return; + } + + std::optional<MethodInsertInfo> InsertInfo = + MethodInsertInfoAfterMethod(*CopyAssign, SrcMgr, LangOpts); + if (InsertInfo) { + GenerateMoveAssignment(Diagnostic, *InsertInfo); + } +} + +} // namespace + +void MissingMoveConstructorCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher( + traverse(TK_IgnoreUnlessSpelledInSource, + cxxConstructorDecl(isCopyConstructor(), isDefinition()) + .bind("CopyCtor")), + this); + + Finder->addMatcher( + traverse(TK_IgnoreUnlessSpelledInSource, + cxxMethodDecl(isCopyAssignmentOperator(), isDefinition()) + .bind("CopyAssign")), + this); + + Finder->addMatcher(traverse(TK_IgnoreUnlessSpelledInSource, + cxxDestructorDecl(isDefinition()).bind("Dtor")), + this); +} + +void MissingMoveConstructorCheck::check( + const MatchFinder::MatchResult &Result) { + ASTContext &Context = *Result.Context; + + const CXXRecordDecl *Record = nullptr; + + if (auto *CopyCtor = Result.Nodes.getNodeAs<CXXConstructorDecl>("CopyCtor")) { + Record = CopyCtor->getParent(); + } else if (auto *CopyAssign = + Result.Nodes.getNodeAs<CXXMethodDecl>("CopyAssign")) { + Record = CopyAssign->getParent(); + } else if (auto *Dtor = Result.Nodes.getNodeAs<CXXDestructorDecl>("Dtor")) { + Record = Dtor->getParent(); + } else { + llvm_unreachable("Impossible match"); + } + assert(Record); + + const ImplicitMovePreventingDecls OffendingDecls = + findImplicitMovePreventingDecls(*Record); + if (!isSome(OffendingDecls)) { + return; + } + // At this point, we know that the implicit move constructor and the implicit + // move assignment operator are not generated for `Record`. + + const bool MissesMoveCtor = missesMoveConstructor(*Record); + const bool MissesMoveAssign = missesMoveAssignmentOperator(*Record); + + if (!MissesMoveCtor && !MissesMoveAssign) { + return; + } + + auto [Loc, Message] = diagnosticMessage( + OffendingDecls, /* MissesMoveConstructor = */ MissesMoveCtor, + /* MissesMoveAssignmentOperator = */ MissesMoveAssign); + + auto Diag = diag(Loc, std::move(Message)); + + const LangOptions &LangOpts = Context.getLangOpts(); + SourceManager &SrcMgr = Context.getSourceManager(); + + if (MissesMoveCtor) { + FixMoveConstructor(Diag, *Record, SrcMgr, LangOpts); + } + if (MissesMoveAssign) { + FixMoveAssignment(Diag, *Record, SrcMgr, LangOpts); + } +} + +} // namespace performance +} // namespace tidy +} // namespace clang Index: clang-tools-extra/clang-tidy/performance/CMakeLists.txt =================================================================== --- clang-tools-extra/clang-tidy/performance/CMakeLists.txt +++ clang-tools-extra/clang-tidy/performance/CMakeLists.txt @@ -10,6 +10,7 @@ InefficientAlgorithmCheck.cpp InefficientStringConcatenationCheck.cpp InefficientVectorOperationCheck.cpp + MissingMoveConstructorCheck.cpp MoveConstArgCheck.cpp MoveConstructorInitCheck.cpp NoAutomaticMoveCheck.cpp
_______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits