Skip to content
Open
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
3abbce9
[clang-tidy] Add check performance-lost-std-move
segoon May 12, 2025
4a1a77b
f(x, x)
segoon May 12, 2025
f74066d
test
segoon May 12, 2025
edd4800
review fixes
segoon May 26, 2025
5359c69
auto -> Expr
segoon May 26, 2025
5bb6af6
FixIt
segoon May 26, 2025
592d79b
f((x))
segoon May 26, 2025
1710c2f
w
segoon May 31, 2025
b4824ef
Merge remote-tracking branch 'origin/main' into feature/lost-std-move
segoon May 31, 2025
60e4aca
thread_local, extern, static
segoon May 31, 2025
a27b1b3
better test
segoon May 31, 2025
6aacd41
fixes
segoon Jun 1, 2025
71cd708
[=] test
segoon Jun 1, 2025
402ba55
fix
segoon Jun 1, 2025
fdf01b6
docs
segoon Jun 1, 2025
24357a2
format
segoon Jun 1, 2025
b48425b
format
segoon Jun 1, 2025
986d6aa
Merge remote-tracking branch 'origin/main' into feature/lost-std-move
segoon Jun 1, 2025
5fb15e1
w
segoon Jun 23, 2025
4a1d653
Merge remote-tracking branch 'origin/main' into feature/lost-std-move
segoon Sep 15, 2025
0d612ec
80 symbols
segoon Sep 15, 2025
6f55a1c
``
segoon Sep 15, 2025
8963056
StrictMode
segoon Sep 15, 2025
55c4d16
test
segoon Sep 15, 2025
ee844fd
option
segoon Sep 15, 2025
2fd53f6
``
segoon Sep 15, 2025
54571f9
default
segoon Sep 16, 2025
d28087b
alphabetical order
segoon Sep 16, 2025
b212770
``
segoon Sep 16, 2025
aa46750
fix UB
segoon Sep 16, 2025
4ae1247
format
segoon Sep 16, 2025
62e4951
line 80
segoon Sep 16, 2025
e12c39c
rm \n
segoon Sep 16, 2025
97db45c
rm \n
segoon Sep 16, 2025
7b091cc
add test
segoon Sep 24, 2025
9816cdb
cxxConstructExpr()
segoon Sep 24, 2025
6b31812
tidy fixes
segoon Sep 24, 2025
e8b2d48
fixes
segoon Sep 24, 2025
777eacd
text changes
segoon Sep 30, 2025
67790b8
invert logic
segoon Sep 30, 2025
708317b
Merge remote-tracking branch 'origin/main' into feature/lost-std-move
segoon Sep 30, 2025
ed6d376
fix doc
segoon Sep 30, 2025
4f62ad9
fix
segoon Oct 2, 2025
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
180 changes: 180 additions & 0 deletions clang-tools-extra/clang-tidy/performance/LostStdMoveCheck.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
//===--- LostStdMoveCheck.cpp - clang-tidy --------------------------------===//
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
//===--- 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 "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/Lex/Lexer.h"

using namespace clang::ast_matchers;

namespace clang::tidy::performance {

template <typename Node>
void extractNodesByIdTo(ArrayRef<BoundNodes> Matches, StringRef ID,
llvm::SmallPtrSet<const Node*, 16>& Nodes) {
for (const BoundNodes& Match : Matches)
Nodes.insert(Match.getNodeAs<Node>(ID));
}

static llvm::SmallPtrSet<const DeclRefExpr*, 16> allDeclRefExprsHonourLambda(
const VarDecl& VarDecl, const Decl& Decl, ASTContext& Context) {
auto Matches = match(
decl(forEachDescendant(
declRefExpr(to(varDecl(equalsNode(&VarDecl))),

Copy link
Contributor

Choose a reason for hiding this comment

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

Excessive newline. Same in other places.

unless(hasAncestor(lambdaExpr(hasAnyCapture(lambdaCapture(
capturesVar(varDecl(equalsNode(&VarDecl))))))))

)
.bind("declRef"))),
Decl, Context);
llvm::SmallPtrSet<const DeclRefExpr*, 16> DeclRefs;
extractNodesByIdTo(Matches, "declRef", DeclRefs);
return DeclRefs;
}

static const Expr* getLastVarUsage(const VarDecl& Var, const Decl& Func,
ASTContext& Context) {
auto Exprs = allDeclRefExprsHonourLambda(Var, Func, Context);

const Expr* LastExpr = nullptr;
for (const clang::DeclRefExpr* Expr : Exprs) {
if (!LastExpr) LastExpr = Expr;

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

return LastExpr;
}

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);

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

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think that such newlines improve code readability. Same in other places.

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())),

// Not in a body of lambda
unless(hasAncestor(compoundStmt(hasAncestor(lambdaExpr())))),

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

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

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

anyOf(

// f(x)
hasParent(expr(hasParent(cxxConstructExpr())).bind("use_parent")),

// f((x))
hasParent(parenExpr(hasParent(
expr(hasParent(cxxConstructExpr())).bind("use_parent"))))

)

)
.bind("use"),
this);
}

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 (!MatchedDecl->hasLocalStorage()) return;
Copy link
Contributor

Choose a reason for hiding this comment

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

Should be two lines as next statement.


if (MatchedUseCall) {
return;
}

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

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

if (LastUsage &&
LastUsage->getSourceRange() != MatchedUse->getSourceRange()) {
return;
}

// Calculate X usage count in the statement
llvm::SmallPtrSet<const DeclRefExpr*, 16> DeclRefs;
ArrayRef<BoundNodes> Matches = match(
findAll(declRefExpr(

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

Same below.

to(varDecl(equalsNode(MatchedDecl))),

unless(hasAncestor(lambdaExpr(hasAnyCapture(lambdaCapture(
capturesVar(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;
}

const SourceManager& Source = Result.Context->getSourceManager();
const auto Range =
CharSourceRange::getTokenRange(MatchedUse->getSourceRange());
const StringRef NeedleExprCode =
Lexer::getSourceText(Range, Source, Result.Context->getLangOpts());

if (NeedleExprCode == "=") {

diag(MatchedUse->getBeginLoc(), "could be std::move()")
<< FixItHint::CreateInsertion(
MatchedUse->getBeginLoc(),
("std::move(" + MatchedDecl->getName() + "),").str());
} else {
diag(MatchedUse->getBeginLoc(), "could be std::move()")
<< FixItHint::CreateReplacement(
Range, ("std::move(" + NeedleExprCode + ")").str());
}
}

} // namespace clang::tidy::performance
33 changes: 33 additions & 0 deletions clang-tools-extra/clang-tidy/performance/LostStdMoveCheck.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//===--- LostStdMoveCheck.h - clang-tidy ------------------------*- C++ -*-===//
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
//===--- 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
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
/// http://clang.llvm.org/extra/clang-tidy/checks/performance/lost-std-move.html
/// https://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.CPlusPlus11;
}
};

} // 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
67 changes: 67 additions & 0 deletions clang-tools-extra/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -145,8 +145,75 @@ New checks
- New :doc:`readability-ambiguous-smartptr-reset-call
<clang-tidy/checks/readability/ambiguous-smartptr-reset-call>` check.

<<<<<<< HEAD
Copy link
Contributor

Choose a reason for hiding this comment

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

Rebase artifact.

Detects implicit conversions between pointers of different levels of
indirection.

- New :doc:`bugprone-optional-value-conversion
<clang-tidy/checks/bugprone/optional-value-conversion>` check.

Detects potentially unintentional and redundant conversions where a value is
extracted from an optional-like type and then used to create a new instance
of the same optional-like type.

- New :doc:`cppcoreguidelines-no-suspend-with-lock
<clang-tidy/checks/cppcoreguidelines/no-suspend-with-lock>` check.

Flags coroutines that suspend while a lock guard is in scope at the
suspension point.

- New :doc:`hicpp-ignored-remove-result
<clang-tidy/checks/hicpp/ignored-remove-result>` check.

Ensure that the result of ``std::remove``, ``std::remove_if`` and
``std::unique`` are not ignored according to rule 17.5.1.

- New :doc:`misc-coroutine-hostile-raii
<clang-tidy/checks/misc/coroutine-hostile-raii>` check.

Detects when objects of certain hostile RAII types persists across suspension
points in a coroutine. Such hostile types include scoped-lockable types and
types belonging to a configurable denylist.

- New :doc:`modernize-use-constraints
<clang-tidy/checks/modernize/use-constraints>` check.

Replace ``enable_if`` with C++20 requires clauses.

- New :doc:`modernize-use-starts-ends-with
<clang-tidy/checks/modernize/use-starts-ends-with>` check.

Copy link
Contributor

Choose a reason for hiding this comment

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

Unintended change?

Checks whether a ``find`` or ``rfind`` result is compared with 0 and suggests
replacing with ``starts_with`` when the method exists in the class. Notably,
this will work with ``std::string`` and ``std::string_view``.

- New :doc:`modernize-use-std-numbers
<clang-tidy/checks/modernize/use-std-numbers>` check.

Finds constants and function calls to math functions that can be replaced
with C++20's mathematical constants from the ``numbers`` header and
offers fix-it hints.

- New :doc:`performance-enum-size
<clang-tidy/checks/performance/enum-size>` check.

Recommends the smallest possible underlying type for an ``enum`` or ``enum``
class based on the range of its enumerators.

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

Searches for lost std::move().

- New :doc:`readability-reference-to-constructed-temporary
<clang-tidy/checks/readability/reference-to-constructed-temporary>` check.

Detects C++ code where a reference variable is used to extend the lifetime
of a temporary object that has just been constructed.
=======
Finds potentially erroneous calls to ``reset`` method on smart pointers when
the pointee type also has a ``reset`` method.
>>>>>>> origin/main

New check aliases
^^^^^^^^^^^^^^^^^
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 @@ -340,6 +340,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
=========================

The check warns if copy constructor is used instead of 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.

Please synchronize with statement in Release Notes. This check should be omitted.


.. 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

Loading
Loading