-
Notifications
You must be signed in to change notification settings - Fork 15.2k
[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
segoon
wants to merge
43
commits into
llvm:main
Choose a base branch
from
segoon:feature/lost-std-move
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all 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 4a1a77b
f(x, x)
segoon f74066d
test
segoon edd4800
review fixes
segoon 5359c69
auto -> Expr
segoon 5bb6af6
FixIt
segoon 592d79b
f((x))
segoon 1710c2f
w
segoon b4824ef
Merge remote-tracking branch 'origin/main' into feature/lost-std-move
segoon 60e4aca
thread_local, extern, static
segoon a27b1b3
better test
segoon 6aacd41
fixes
segoon 71cd708
[=] test
segoon 402ba55
fix
segoon fdf01b6
docs
segoon 24357a2
format
segoon b48425b
format
segoon 986d6aa
Merge remote-tracking branch 'origin/main' into feature/lost-std-move
segoon 5fb15e1
w
segoon 4a1d653
Merge remote-tracking branch 'origin/main' into feature/lost-std-move
segoon 0d612ec
80 symbols
segoon 6f55a1c
``
segoon 8963056
StrictMode
segoon 55c4d16
test
segoon ee844fd
option
segoon 2fd53f6
``
segoon 54571f9
default
segoon d28087b
alphabetical order
segoon b212770
``
segoon aa46750
fix UB
segoon 4ae1247
format
segoon 62e4951
line 80
segoon e12c39c
rm \n
segoon 97db45c
rm \n
segoon 7b091cc
add test
segoon 9816cdb
cxxConstructExpr()
segoon 6b31812
tidy fixes
segoon e8b2d48
fixes
segoon 777eacd
text changes
segoon 67790b8
invert logic
segoon 708317b
Merge remote-tracking branch 'origin/main' into feature/lost-std-move
segoon ed6d376
fix doc
segoon 4f62ad9
fix
segoon File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
196 changes: 196 additions & 0 deletions
196
clang-tools-extra/clang-tidy/performance/LostStdMoveCheck.cpp
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,196 @@ | ||
//===----------------------------------------------------------------------===// | ||
// | ||
// 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> | ||
static 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))), | ||
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 llvm::SmallPtrSet<const VarDecl *, 16> | ||
allVarDeclsExprs(const VarDecl &VarDecl, const Decl &Decl, | ||
ASTContext &Context) { | ||
auto Matches = match( | ||
decl(forEachDescendant(declRefExpr( | ||
to(varDecl(equalsNode(&VarDecl))), | ||
hasParent(decl( | ||
varDecl(hasType(qualType(referenceType()))).bind("varDecl"))), | ||
unless(hasAncestor(lambdaExpr(hasAnyCapture( | ||
lambdaCapture(capturesVar(varDecl(equalsNode(&VarDecl))))))))))), | ||
Decl, Context); | ||
llvm::SmallPtrSet<const class VarDecl *, 16> VarDecls; | ||
extractNodesByIdTo(Matches, "varDecl", VarDecls); | ||
return VarDecls; | ||
} | ||
|
||
static const Expr * | ||
getLastVarUsage(const llvm::SmallPtrSet<const DeclRefExpr *, 16> &Exprs) { | ||
const Expr *LastExpr = nullptr; | ||
for (const clang::DeclRefExpr *Expr : Exprs) { | ||
if (!LastExpr) | ||
LastExpr = Expr; | ||
|
||
if (LastExpr->getBeginLoc() < Expr->getBeginLoc()) | ||
LastExpr = Expr; | ||
} | ||
|
||
return LastExpr; | ||
} | ||
|
||
namespace { | ||
|
||
AST_MATCHER(CXXRecordDecl, hasTrivialMoveConstructor) { | ||
return Node.hasDefinition() && Node.hasTrivialMoveConstructor(); | ||
} | ||
|
||
AST_MATCHER_P(Expr, ignoreParens, ast_matchers::internal::Matcher<Expr>, | ||
InnerMatcher) { | ||
return InnerMatcher.matches(*Node.IgnoreParens(), Finder, Builder); | ||
} | ||
|
||
} // namespace | ||
|
||
void LostStdMoveCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { | ||
Options.store(Opts, "StrictMode", StrictMode); | ||
} | ||
|
||
LostStdMoveCheck::LostStdMoveCheck(StringRef Name, ClangTidyContext *Context) | ||
: ClangTidyCheck(Name, Context), | ||
StrictMode(Options.get("StrictMode", false)) {} | ||
|
||
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( | ||
cxxConstructExpr(has(expr(has(ignoreParens( | ||
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())), | ||
// 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"))) | ||
.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 *MatchedLeafStatement = | ||
Result.Nodes.getNodeAs<Stmt>("leaf_statement"); | ||
|
||
if (!MatchedDecl->hasLocalStorage()) { | ||
// global or static variable | ||
return; | ||
} | ||
|
||
if (!StrictMode) { | ||
llvm::SmallPtrSet<const VarDecl *, 16> AllVarDecls = | ||
allVarDeclsExprs(*MatchedDecl, *MatchedFunc, *Result.Context); | ||
if (!AllVarDecls.empty()) { | ||
// x is referenced by local var, it may outlive the "use" | ||
return; | ||
} | ||
} | ||
|
||
llvm::SmallPtrSet<const DeclRefExpr *, 16> AllReferences = | ||
allDeclRefExprsHonourLambda(*MatchedDecl, *MatchedFunc, *Result.Context); | ||
const Expr *LastUsage = getLastVarUsage(AllReferences); | ||
|
||
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; | ||
|
||
SmallVector<BoundNodes, 1> Matches = match( | ||
findAll( | ||
declRefExpr(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(), | ||
(MatchedDecl->getName() + | ||
" = std::move(" + | ||
MatchedDecl->getName() + "),") | ||
.str()); | ||
} else { | ||
diag(MatchedUse->getBeginLoc(), "could be std::move()") | ||
<< FixItHint::CreateReplacement( | ||
Range, ("std::move(" + NeedleExprCode + ")").str()); | ||
} | ||
} | ||
|
||
} // namespace clang::tidy::performance |
38 changes: 38 additions & 0 deletions
38
clang-tools-extra/clang-tidy/performance/LostStdMoveCheck.h
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
//===----------------------------------------------------------------------===// | ||
// | ||
// 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 { | ||
|
||
/// Warns if copy constructor is used instead of std::move() and suggests a fix. | ||
/// It honours cycles, lambdas, and unspecified call order in compound | ||
/// expressions. | ||
/// | ||
/// For the user-facing documentation see: | ||
/// https://clang.llvm.org/extra/clang-tidy/checks/performance/lost-std-move.html | ||
class LostStdMoveCheck : public ClangTidyCheck { | ||
public: | ||
LostStdMoveCheck(StringRef Name, ClangTidyContext *Context); | ||
void registerMatchers(ast_matchers::MatchFinder *Finder) override; | ||
void check(const ast_matchers::MatchFinder::MatchResult &Result) override; | ||
void storeOptions(ClangTidyOptions::OptionMap &Opts) override; | ||
bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { | ||
return LangOpts.CPlusPlus11; | ||
} | ||
|
||
private: | ||
const bool StrictMode; | ||
}; | ||
|
||
} // namespace clang::tidy::performance | ||
|
||
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_LOSTSTDMOVECHECK_H |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
48 changes: 48 additions & 0 deletions
48
clang-tools-extra/docs/clang-tidy/checks/performance/lost-std-move.rst
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
.. title:: clang-tidy - performance-lost-std-move | ||
|
||
performance-lost-std-move | ||
========================= | ||
|
||
Warns if copy constructor is used instead of ``std::move()`` and suggests a fix. | ||
It honours cycles, lambdas, and unspecified call order in compound expressions. | ||
|
||
.. code-block:: c++ | ||
|
||
void f(X); | ||
|
||
void g(X x) { | ||
f(x); // warning: Could be std::move() [performance-lost-std-move] | ||
} | ||
|
||
It finds the last local variable usage, and if it is a copy, emits a warning. | ||
The check is based on pure AST matching and doesn't take into account any | ||
data flow information. Thus, it doesn't catch assign-after-copy cases. | ||
|
||
Also it does notice variable references "behind the scenes": | ||
|
||
.. code-block:: c++ | ||
|
||
void f(X); | ||
|
||
void g(X x) { | ||
auto &y = x; | ||
f(x); // does not emit a warning | ||
y.f(); // because x is still used via y | ||
} | ||
|
||
If you want to ignore assigns to reference variables, set :option:`StrictMode` | ||
to `true`. | ||
|
||
|
||
Options | ||
------- | ||
|
||
.. option:: StrictMode | ||
|
||
A variable ``X`` can be referenced by another variable ``R``. In this case | ||
the last variable usage might be not from ``X``, but from ``R``. It is quite | ||
difficult to find in a large function, so if the plugin sees some ``R`` | ||
references ``X``, then it will not emit a warning for ``X`` not to provoke | ||
false positive. If you're sure that such references don't extend ``X`` | ||
lifetime and ready to handle possible false positives, then set `StrictMode` | ||
to `true`. Defaults to `false`. | ||
30 changes: 30 additions & 0 deletions
30
clang-tools-extra/test/clang-tidy/checkers/performance/lost-std-move-strict.cpp
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
// RUN: %check_clang_tidy %s performance-lost-std-move %t -- -config='{CheckOptions: {performance-lost-std-move.StrictMode: true}}' | ||
|
||
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_copy_after_ref() | ||
{ | ||
std::shared_ptr<int> ptr; | ||
auto& ref = ptr; | ||
f(ptr); | ||
// CHECK-MESSAGES: :[[@LINE-1]]:5: warning: could be std::move() [performance-lost-std-move] | ||
// CHECK-FIXES: f(std::move(ptr)); | ||
*ref = 1; | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.