Skip to content

[clang-tidy] Add check performance-lost-std-move #139525

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions clang-tools-extra/clang-tidy/performance/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ add_clang_library(clangTidyPerformanceModule STATIC
InefficientAlgorithmCheck.cpp
InefficientStringConcatenationCheck.cpp
InefficientVectorOperationCheck.cpp
LostStdMoveCheck.cpp
MoveConstArgCheck.cpp
MoveConstructorInitCheck.cpp
NoAutomaticMoveCheck.cpp
Expand Down
122 changes: 122 additions & 0 deletions clang-tools-extra/clang-tidy/performance/LostStdMoveCheck.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
//===--- LostStdMoveCheck.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 "LostStdMoveCheck.h"
#include "../utils/DeclRefExprUtils.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"

using namespace clang::ast_matchers;

namespace clang::tidy::performance {

using utils::decl_ref_expr::allDeclRefExprs;

AST_MATCHER(CXXRecordDecl, hasTrivialMoveConstructor) {
return Node.hasDefinition() && Node.hasTrivialMoveConstructor();
}

void LostStdMoveCheck::registerMatchers(MatchFinder *Finder) {
auto returnParent =
hasParent(expr(hasParent(cxxConstructExpr(hasParent(returnStmt())))));

auto outermostExpr = expr(unless(hasParent(expr())));
auto leafStatement =
stmt(outermostExpr, unless(hasDescendant(outermostExpr)));

Finder->addMatcher(
declRefExpr(
// not "return x;"
unless(returnParent),

unless(hasType(namedDecl(hasName("::std::string_view")))),

// non-trivial type
hasType(hasCanonicalType(hasDeclaration(cxxRecordDecl()))),

// non-trivial X(X&&)
unless(hasType(hasCanonicalType(
hasDeclaration(cxxRecordDecl(hasTrivialMoveConstructor()))))),

// Not in a cycle
unless(hasAncestor(forStmt())), unless(hasAncestor(doStmt())),
unless(hasAncestor(whileStmt())),

// only non-X&
unless(hasDeclaration(
varDecl(hasType(qualType(lValueReferenceType()))))),

hasAncestor(leafStatement.bind("leaf_statement")),

hasDeclaration(
varDecl(hasAncestor(functionDecl().bind("func"))).bind("decl")),

hasParent(expr(hasParent(cxxConstructExpr())).bind("use_parent")))
.bind("use"),
this);
}

const Expr *LostStdMoveCheck::getLastVarUsage(const VarDecl &Var,
const Decl &Func,
ASTContext &Context) {
llvm::SmallPtrSet<const DeclRefExpr *, 16> Exprs = allDeclRefExprs(Var, Func, Context);

const Expr *LastExpr = nullptr;
for (const auto &Expr : Exprs) {
Copy link
Contributor

@vbvictor vbvictor May 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use explicit type instead of auto if it isn't written explicitly in code line.

if (!LastExpr)
LastExpr = Expr;

if (LastExpr->getBeginLoc() < Expr->getBeginLoc())
LastExpr = Expr;
}

return LastExpr;
}

template <typename Node>
void extractNodesByIdTo(ArrayRef<BoundNodes> Matches, StringRef ID,
llvm::SmallPtrSet<const Node *, 16> &Nodes) {
for (const auto &Match : Matches)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

Nodes.insert(Match.getNodeAs<Node>(ID));
}

void LostStdMoveCheck::check(const MatchFinder::MatchResult &Result) {
const auto *MatchedDecl = Result.Nodes.getNodeAs<VarDecl>("decl");
const auto *MatchedFunc = Result.Nodes.getNodeAs<FunctionDecl>("func");
const auto *MatchedUse = Result.Nodes.getNodeAs<Expr>("use");
const auto *MatchedUseCall = Result.Nodes.getNodeAs<CallExpr>("use_parent");
const auto *MatchedLeafStatement =
Result.Nodes.getNodeAs<Stmt>("leaf_statement");

if (MatchedUseCall)
return;

const Expr *LastUsage =
getLastVarUsage(*MatchedDecl, *MatchedFunc, *Result.Context);
if (LastUsage == nullptr)
return;

if (LastUsage->getBeginLoc() > MatchedUse->getBeginLoc()) {
// "use" is not the last reference to x
return;
}

// Calculate X usage count in the statement
llvm::SmallPtrSet<const DeclRefExpr *, 16> DeclRefs;
auto Matches = match(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

findAll(declRefExpr(to(varDecl(equalsNode(MatchedDecl)))).bind("ref")),
*MatchedLeafStatement, *Result.Context);
extractNodesByIdTo(Matches, "ref", DeclRefs);
if (DeclRefs.size() > 1) {
// Unspecified order of evaluation, e.g. f(x, x)
return;
}

diag(LastUsage->getBeginLoc(), "Could be std::move()");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use lovercase for diag messages

Suggested change
diag(LastUsage->getBeginLoc(), "Could be std::move()");
diag(LastUsage->getBeginLoc(), "could be 'std::move'");

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Try to provide fixits with the insertion of std::move

}

} // namespace clang::tidy::performance
37 changes: 37 additions & 0 deletions clang-tools-extra/clang-tidy/performance/LostStdMoveCheck.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
//===--- LostStdMoveCheck.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_LOSTSTDMOVECHECK_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_LOSTSTDMOVECHECK_H

#include "../ClangTidyCheck.h"

namespace clang::tidy::performance {

/// Search for lost std::move().
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be synced with release notes and check docs

///
/// For the user-facing documentation see:
/// http://clang.llvm.org/extra/clang-tidy/checks/performance/lost-std-move.html
class LostStdMoveCheck : public ClangTidyCheck {
public:
LostStdMoveCheck(StringRef Name, ClangTidyContext *Context)
: ClangTidyCheck(Name, Context) {}
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
return LangOpts.CPlusPlus;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return LangOpts.CPlusPlus;
return LangOpts.CPlusPlus11;

}

private:
const Expr *getLastVarUsage(const VarDecl &Var, const Decl &Func,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be converted to static function and move to .cpp file if no fields of check class are needed

ASTContext &Context);
};

} // namespace clang::tidy::performance

#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_LOSTSTDMOVECHECK_H
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include "InefficientAlgorithmCheck.h"
#include "InefficientStringConcatenationCheck.h"
#include "InefficientVectorOperationCheck.h"
#include "LostStdMoveCheck.h"
#include "MoveConstArgCheck.h"
#include "MoveConstructorInitCheck.h"
#include "NoAutomaticMoveCheck.h"
Expand Down Expand Up @@ -49,6 +50,7 @@ class PerformanceModule : public ClangTidyModule {
"performance-inefficient-string-concatenation");
CheckFactories.registerCheck<InefficientVectorOperationCheck>(
"performance-inefficient-vector-operation");
CheckFactories.registerCheck<LostStdMoveCheck>("performance-lost-std-move");
CheckFactories.registerCheck<MoveConstArgCheck>(
"performance-move-const-arg");
CheckFactories.registerCheck<MoveConstructorInitCheck>(
Expand Down
5 changes: 5 additions & 0 deletions clang-tools-extra/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,11 @@ New checks
Finds unintended character output from ``unsigned char`` and ``signed char``
to an ``ostream``.

- New :doc:`performance-lost-std-move
<clang-tidy/checks/performance/lost-std-move>` check.

Warns if copy constructor is used instead of ``std::move()``.

- New :doc:`readability-ambiguous-smartptr-reset-call
<clang-tidy/checks/readability/ambiguous-smartptr-reset-call>` check.

Expand Down
1 change: 1 addition & 0 deletions clang-tools-extra/docs/clang-tidy/checks/list.rst
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,7 @@ Clang-Tidy Checks
:doc:`performance-inefficient-algorithm <performance/inefficient-algorithm>`, "Yes"
:doc:`performance-inefficient-string-concatenation <performance/inefficient-string-concatenation>`,
:doc:`performance-inefficient-vector-operation <performance/inefficient-vector-operation>`, "Yes"
:doc:`performance-lost-std-move <performance/lost-std-move>`, "Yes"
:doc:`performance-move-const-arg <performance/move-const-arg>`, "Yes"
:doc:`performance-move-constructor-init <performance/move-constructor-init>`,
:doc:`performance-no-automatic-move <performance/no-automatic-move>`,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
.. title:: clang-tidy - performance-lost-std-move

performance-lost-std-move
=========================

Warns if copy constructor is used instead of ``std::move()``.
Copy link
Contributor

@vbvictor vbvictor May 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This need a little more description when the check will warn, e.g when this will fire, with what types?
Look for reference at https://reviews.llvm.org/D137205

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit suggestion: try make the description similar to already existing new checks in ReleaseNotes.rst.
Description should start with "Finds ..."


.. code-block:: c++

void f(X);

void g(X x) {
f(x); // warning: Could be std::move() [performance-lost-std-move]
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Give examples when check will not warn

Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// RUN: %check_clang_tidy %s performance-lost-std-move %t

namespace std {

template<typename T>
class shared_ptr {
public:
T& operator*() { return reinterpret_cast<T&>(*this); }
shared_ptr() {}
shared_ptr(const shared_ptr<T>&) {}
};

template<typename T>
T&& move(T&)
{
}

} // namespace std

int f(std::shared_ptr<int>);

void f_arg(std::shared_ptr<int> ptr)
{
if (*ptr)
f(ptr);
// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: Could be std::move() [performance-lost-std-move]
}

void f_rvalue_ref(std::shared_ptr<int>&& ptr)
{
if (*ptr)
f(ptr);
// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: Could be std::move() [performance-lost-std-move]
}

using SharedPtr = std::shared_ptr<int>;
void f_using(SharedPtr ptr)
{
if (*ptr)
f(ptr);
// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: Could be std::move() [performance-lost-std-move]
}

void f_local()
{
std::shared_ptr<int> ptr;
if (*ptr)
f(ptr);
// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: Could be std::move() [performance-lost-std-move]
}

void f_move()
{
std::shared_ptr<int> ptr;
if (*ptr)
f(std::move(ptr));
}

void f_ref(std::shared_ptr<int> &ptr)
{
if (*ptr)
f(ptr);
}

std::shared_ptr<int> f_return()
{
std::shared_ptr<int> ptr;
return ptr;
}

void f_still_used(std::shared_ptr<int> ptr)
{
if (*ptr)
f(ptr);

*ptr = 1;
*ptr = *ptr;
}

void f_cycle1()
{
std::shared_ptr<int> ptr;
for(;;)
f(ptr);
}

void f_cycle2()
{
std::shared_ptr<int> ptr;
for(int i=0; i<5; i++)
f(ptr);
}

void f_cycle3()
{
std::shared_ptr<int> ptr;
while (*ptr) {
f(ptr);
}
}

void f_cycle4()
{
std::shared_ptr<int> ptr;
do {
f(ptr);
} while (*ptr);
}

int f_multiple_usages()
{
std::shared_ptr<int> ptr;
return f(ptr) + f(ptr);
}
Loading