-
Notifications
You must be signed in to change notification settings - Fork 13.6k
[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
base: main
Are you sure you want to change the base?
Changes from all commits
3abbce9
35ac17a
b48a1f7
32ebfed
f526417
13c1433
d3b8c17
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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) { | ||||||
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) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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( | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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()"); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use lovercase for diag messages
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Try to provide fixits with the insertion of |
||||||
} | ||||||
|
||||||
} // namespace clang::tidy::performance |
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(). | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
} | ||||||
|
||||||
private: | ||||||
const Expr *getLastVarUsage(const VarDecl &Var, const Decl &Func, | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this should be converted to |
||||||
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 |
---|---|---|
@@ -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()``. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
||
|
||
.. code-block:: c++ | ||
|
||
void f(X); | ||
|
||
void g(X x) { | ||
f(x); // warning: Could be std::move() [performance-lost-std-move] | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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); | ||
} |
Uh oh!
There was an error while loading. Please reload this page.
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.
Use explicit type instead of
auto
if it isn't written explicitly in code line.