Index: clang-tidy/misc/MoveConstructorInitCheck.h =================================================================== --- clang-tidy/misc/MoveConstructorInitCheck.h +++ clang-tidy/misc/MoveConstructorInitCheck.h @@ -18,12 +18,21 @@ /// The check flags user-defined move constructors that have a ctor-initializer /// initializing a member or base class through a copy constructor instead of a /// move constructor. +/// It also flags constructor arguments that are passed by value, have a +/// non-deleted move-constructor and are assigned to a class field by copy +/// construction. class MoveConstructorInitCheck : public ClangTidyCheck { public: MoveConstructorInitCheck(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 + handleMoveConstructor(const ast_matchers::MatchFinder::MatchResult &Result); + void + handleParamNotMoved(const ast_matchers::MatchFinder::MatchResult &Result); }; } // namespace tidy Index: clang-tidy/misc/MoveConstructorInitCheck.cpp =================================================================== --- clang-tidy/misc/MoveConstructorInitCheck.cpp +++ clang-tidy/misc/MoveConstructorInitCheck.cpp @@ -16,6 +16,48 @@ namespace clang { namespace tidy { +namespace { + +bool classHasTrivialCopyAndDestroy(QualType Type) { + auto *Record = Type->getAsCXXRecordDecl(); + return Record && Record->hasDefinition() && + !Record->hasNonTrivialCopyConstructor() && + !Record->hasNonTrivialDestructor(); +} + +AST_MATCHER(QualType, isExpensiveToCopy) { + // We can't reason about dependent types. Ignore them. + // If there's a template instantiation that results in what we consider + // excessive copying, we'll warn on them. + if (Node->isDependentType()) { + return false; + } + // Ignore trivially copyable types. + if (Node->isScalarType() || + Node.isTriviallyCopyableType(Finder->getASTContext()) || + classHasTrivialCopyAndDestroy(Node)) { + return false; + } + return true; +} + +int parmVarDeclRefExprOccurences(const ParmVarDecl &MovableParam, + const CXXConstructorDecl &ConstructorDecl, + ASTContext &Context) { + int Occurrences = 0; + auto AllDeclRefs = + findAll(declRefExpr(to(parmVarDecl(equalsNode(&MovableParam))))); + auto Matches = match(AllDeclRefs, *ConstructorDecl.getBody(), Context); + Occurrences += Matches.size(); + for (const auto* Initializer : ConstructorDecl.inits()) { + Matches = match(AllDeclRefs, *Initializer->getInit(), Context); + Occurrences += Matches.size(); + } + return Occurrences; +} + +} // namespace + void MoveConstructorInitCheck::registerMatchers(MatchFinder *Finder) { // Only register the matchers for C++11; the functionality currently does not // provide any benefit to other languages, despite being benign. @@ -28,14 +70,60 @@ hasAnyConstructorInitializer( ctorInitializer(withInitializer(constructExpr(hasDeclaration( constructorDecl(isCopyConstructor()).bind("ctor") - )))).bind("init") + )))).bind("move-init") ) )), this); + + auto NonConstValueMovableAndExpensiveToCopy = + qualType(allOf(unless(pointerType()), unless(isConstQualified()), + hasDeclaration(recordDecl(hasMethod(constructorDecl( + isMoveConstructor(), unless(isDeleted()))))), + isExpensiveToCopy())); + Finder->addMatcher( + constructorDecl( + allOf( + unless(isMoveConstructor()), + hasAnyConstructorInitializer(withInitializer(constructExpr( + hasDeclaration(constructorDecl(isCopyConstructor())), + hasArgument( + 0, declRefExpr( + to(parmVarDecl( + hasType( + NonConstValueMovableAndExpensiveToCopy)) + .bind("movable-param"))) + .bind("init-arg"))))))) + .bind("ctor-decl"), + this); } void MoveConstructorInitCheck::check(const MatchFinder::MatchResult &Result) { + if (Result.Nodes.getNodeAs("move-init") != nullptr) { + handleMoveConstructor(Result); + } + if (Result.Nodes.getNodeAs("movable-param") != nullptr) { + handleParamNotMoved(Result); + } +} + +void MoveConstructorInitCheck::handleParamNotMoved( + const MatchFinder::MatchResult &Result) { + const auto *MovableParam = + Result.Nodes.getNodeAs("movable-param"); + const auto *ConstructorDecl = + Result.Nodes.getNodeAs("ctor-decl"); + const auto *InitArg = Result.Nodes.getNodeAs("init-arg"); + // If the parameter is referenced more than once it is not safe to move it. + if (parmVarDeclRefExprOccurences(*MovableParam, *ConstructorDecl, + *Result.Context) > 1) { + return; + } + diag(InitArg->getLocStart(), "value parameter can be moved to avoid copy."); +} + +void MoveConstructorInitCheck::handleMoveConstructor( + const MatchFinder::MatchResult &Result) { const auto *CopyCtor = Result.Nodes.getNodeAs("ctor"); - const auto *Initializer = Result.Nodes.getNodeAs("init"); + const auto *Initializer = Result.Nodes.getNodeAs("move-init"); // Do not diagnose if the expression used to perform the initialization is a // trivially-copyable type. @@ -79,4 +167,3 @@ } // namespace tidy } // namespace clang - Index: test/clang-tidy/misc-move-constructor-init.cpp =================================================================== --- test/clang-tidy/misc-move-constructor-init.cpp +++ test/clang-tidy/misc-move-constructor-init.cpp @@ -76,3 +76,60 @@ B Mem; N(N &&RHS) : Mem(move(RHS.Mem)) {} }; + + +struct Movable { + Movable(Movable&&) = default; + Movable(const Movable&) = default; + Movable& operator =(const Movable&) = default; + ~Movable() {} +}; + +struct TriviallyCopyable { + TriviallyCopyable() = default; + TriviallyCopyable(TriviallyCopyable&&) = default; + TriviallyCopyable(const TriviallyCopyable&) = default; +}; + +struct Positive { + Positive(Movable M) : M_(M) {} + // CHECK-MESSAGES: [[@LINE-1]]:28: warning: value parameter can be moved to avoid copy. [misc-move-constructor-init] + Movable M_; +}; + +struct NegativeMultipleInitializerReferences { + NegativeMultipleInitializerReferences(Movable M) : M_(M), n_(M) {} + Movable M_; + Movable n_; +}; + +struct NegativeReferencedInConstructorBody { + NegativeReferencedInConstructorBody(Movable M) : M_(M) { + M_ = M; + } + Movable M_; +}; + +struct NegativeParamTriviallyCopyable { + NegativeParamTriviallyCopyable(TriviallyCopyable T) : T_(T) {} + TriviallyCopyable T_; +}; + +struct NegativeNotPassedByValue { + NegativeNotPassedByValue(const Movable& M) : M_(M) {} + NegativeNotPassedByValue(const Movable M) : M_(M) {} + NegativeNotPassedByValue(Movable& M) : M_(M) {} + NegativeNotPassedByValue(Movable* M) : M_(*M) {} + NegativeNotPassedByValue(const Movable* M) : M_(*M) {} + Movable M_; +}; + +struct Immovable { + Immovable(const Immovable&) = default; + Immovable(Immovable&&) = delete; +}; + +struct NegativeImmovableParameter { + NegativeImmovableParameter(Immovable I) : I_(I) {} + Immovable I_; +};