Skip to content

Commit eecdd4b

Browse files
[clang-tidy] Extend bugprone-use-after-move to allow custom invalidation functions (#170346)
1 parent 290b32a commit eecdd4b

File tree

5 files changed

+129
-30
lines changed

5 files changed

+129
-30
lines changed

clang-tools-extra/clang-tidy/bugprone/UseAfterMoveCheck.cpp

Lines changed: 50 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
#include "../utils/ExprSequence.h"
2121
#include "../utils/Matchers.h"
22+
#include "../utils/OptionsUtils.h"
2223
#include <optional>
2324

2425
using namespace clang::ast_matchers;
@@ -48,7 +49,8 @@ struct UseAfterMove {
4849
/// various internal helper functions).
4950
class UseAfterMoveFinder {
5051
public:
51-
UseAfterMoveFinder(ASTContext *TheContext);
52+
UseAfterMoveFinder(ASTContext *TheContext,
53+
llvm::ArrayRef<StringRef> InvalidationFunctions);
5254

5355
// Within the given code block, finds the first use of 'MovedVariable' that
5456
// occurs after 'MovingCall' (the expression that performs the move). If a
@@ -71,13 +73,19 @@ class UseAfterMoveFinder {
7173
llvm::SmallPtrSetImpl<const DeclRefExpr *> *DeclRefs);
7274

7375
ASTContext *Context;
76+
llvm::ArrayRef<StringRef> InvalidationFunctions;
7477
std::unique_ptr<ExprSequence> Sequence;
7578
std::unique_ptr<StmtToBlockMap> BlockMap;
7679
llvm::SmallPtrSet<const CFGBlock *, 8> Visited;
7780
};
7881

7982
} // namespace
8083

84+
static auto getNameMatcher(llvm::ArrayRef<StringRef> InvalidationFunctions) {
85+
return anyOf(hasAnyName("::std::move", "::std::forward"),
86+
matchers::matchesAnyListedName(InvalidationFunctions));
87+
}
88+
8189
// Matches nodes that are
8290
// - Part of a decltype argument or class template argument (we check this by
8391
// seeing if they are children of a TypeLoc), or
@@ -92,8 +100,9 @@ static StatementMatcher inDecltypeOrTemplateArg() {
92100
hasAncestor(expr(hasUnevaluatedContext())));
93101
}
94102

95-
UseAfterMoveFinder::UseAfterMoveFinder(ASTContext *TheContext)
96-
: Context(TheContext) {}
103+
UseAfterMoveFinder::UseAfterMoveFinder(
104+
ASTContext *TheContext, llvm::ArrayRef<StringRef> InvalidationFunctions)
105+
: Context(TheContext), InvalidationFunctions(InvalidationFunctions) {}
97106

98107
std::optional<UseAfterMove>
99108
UseAfterMoveFinder::find(Stmt *CodeBlock, const Expr *MovingCall,
@@ -359,7 +368,7 @@ void UseAfterMoveFinder::getReinits(
359368
unless(parmVarDecl(hasType(
360369
references(qualType(isConstQualified())))))),
361370
unless(callee(functionDecl(
362-
hasAnyName("::std::move", "::std::forward")))))))
371+
getNameMatcher(InvalidationFunctions)))))))
363372
.bind("reinit");
364373

365374
Stmts->clear();
@@ -388,18 +397,21 @@ void UseAfterMoveFinder::getReinits(
388397
}
389398
}
390399

391-
enum class MoveType {
392-
Move, // std::move
393-
Forward, // std::forward
400+
enum MoveType {
401+
Forward = 0, // std::forward
402+
Move = 1, // std::move
403+
Invalidation = 2, // other
394404
};
395405

396406
static MoveType determineMoveType(const FunctionDecl *FuncDecl) {
397-
if (FuncDecl->getName() == "move")
398-
return MoveType::Move;
399-
if (FuncDecl->getName() == "forward")
400-
return MoveType::Forward;
407+
if (FuncDecl->isInStdNamespace()) {
408+
if (FuncDecl->getName() == "move")
409+
return MoveType::Move;
410+
if (FuncDecl->getName() == "forward")
411+
return MoveType::Forward;
412+
}
401413

402-
llvm_unreachable("Invalid move type");
414+
return MoveType::Invalidation;
403415
}
404416

405417
static void emitDiagnostic(const Expr *MovingCall, const DeclRefExpr *MoveArg,
@@ -408,41 +420,53 @@ static void emitDiagnostic(const Expr *MovingCall, const DeclRefExpr *MoveArg,
408420
const SourceLocation UseLoc = Use.DeclRef->getExprLoc();
409421
const SourceLocation MoveLoc = MovingCall->getExprLoc();
410422

411-
const bool IsMove = (Type == MoveType::Move);
412-
413-
Check->diag(UseLoc, "'%0' used after it was %select{forwarded|moved}1")
414-
<< MoveArg->getDecl()->getName() << IsMove;
415-
Check->diag(MoveLoc, "%select{forward|move}0 occurred here",
423+
Check->diag(UseLoc,
424+
"'%0' used after it was %select{forwarded|moved|invalidated}1")
425+
<< MoveArg->getDecl()->getName() << Type;
426+
Check->diag(MoveLoc, "%select{forward|move|invalidation}0 occurred here",
416427
DiagnosticIDs::Note)
417-
<< IsMove;
428+
<< Type;
418429
if (Use.EvaluationOrderUndefined) {
419430
Check->diag(
420431
UseLoc,
421-
"the use and %select{forward|move}0 are unsequenced, i.e. "
432+
"the use and %select{forward|move|invalidation}0 are unsequenced, i.e. "
422433
"there is no guarantee about the order in which they are evaluated",
423434
DiagnosticIDs::Note)
424-
<< IsMove;
435+
<< Type;
425436
} else if (Use.UseHappensInLaterLoopIteration) {
426437
Check->diag(UseLoc,
427438
"the use happens in a later loop iteration than the "
428-
"%select{forward|move}0",
439+
"%select{forward|move|invalidation}0",
429440
DiagnosticIDs::Note)
430-
<< IsMove;
441+
<< Type;
431442
}
432443
}
433444

445+
UseAfterMoveCheck::UseAfterMoveCheck(StringRef Name, ClangTidyContext *Context)
446+
: ClangTidyCheck(Name, Context),
447+
InvalidationFunctions(utils::options::parseStringList(
448+
Options.get("InvalidationFunctions", ""))) {}
449+
450+
void UseAfterMoveCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
451+
Options.store(Opts, "InvalidationFunctions",
452+
utils::options::serializeStringList(InvalidationFunctions));
453+
}
454+
434455
void UseAfterMoveCheck::registerMatchers(MatchFinder *Finder) {
435456
// try_emplace is a common maybe-moving function that returns a
436457
// bool to tell callers whether it moved. Ignore std::move inside
437458
// try_emplace to avoid false positives as we don't track uses of
438459
// the bool.
439460
auto TryEmplaceMatcher =
440461
cxxMemberCallExpr(callee(cxxMethodDecl(hasName("try_emplace"))));
462+
auto Arg = declRefExpr().bind("arg");
463+
auto IsMemberCallee = callee(functionDecl(unless(isStaticStorageClass())));
441464
auto CallMoveMatcher =
442-
callExpr(argumentCountIs(1),
443-
callee(functionDecl(hasAnyName("::std::move", "::std::forward"))
465+
callExpr(callee(functionDecl(getNameMatcher(InvalidationFunctions))
444466
.bind("move-decl")),
445-
hasArgument(0, declRefExpr().bind("arg")),
467+
anyOf(cxxMemberCallExpr(IsMemberCallee, on(Arg)),
468+
callExpr(unless(cxxMemberCallExpr(IsMemberCallee)),
469+
hasArgument(0, Arg))),
446470
unless(inDecltypeOrTemplateArg()),
447471
unless(hasParent(TryEmplaceMatcher)), expr().bind("call-move"),
448472
anyOf(hasAncestor(compoundStmt(
@@ -521,7 +545,7 @@ void UseAfterMoveCheck::check(const MatchFinder::MatchResult &Result) {
521545
}
522546

523547
for (Stmt *CodeBlock : CodeBlocks) {
524-
UseAfterMoveFinder Finder(Result.Context);
548+
UseAfterMoveFinder Finder(Result.Context, InvalidationFunctions);
525549
if (auto Use = Finder.find(CodeBlock, MovingCall, Arg))
526550
emitDiagnostic(MovingCall, Arg, *Use, this, Result.Context,
527551
determineMoveType(MoveDecl));

clang-tools-extra/clang-tidy/bugprone/UseAfterMoveCheck.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,16 @@ namespace clang::tidy::bugprone {
2020
/// https://clang.llvm.org/extra/clang-tidy/checks/bugprone/use-after-move.html
2121
class UseAfterMoveCheck : public ClangTidyCheck {
2222
public:
23-
UseAfterMoveCheck(StringRef Name, ClangTidyContext *Context)
24-
: ClangTidyCheck(Name, Context) {}
23+
UseAfterMoveCheck(StringRef Name, ClangTidyContext *Context);
24+
void storeOptions(ClangTidyOptions::OptionMap &Opts) override;
2525
bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
2626
return LangOpts.CPlusPlus11;
2727
}
2828
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
2929
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
30+
31+
private:
32+
std::vector<StringRef> InvalidationFunctions;
3033
};
3134

3235
} // namespace clang::tidy::bugprone

clang-tools-extra/docs/ReleaseNotes.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,10 @@ Changes in existing checks
408408
suffix when the reason starts with the character `>` in the `CustomFunctions`
409409
option.
410410

411+
- Improved :doc:`bugprone-use-after-move
412+
<clang-tidy/checks/bugprone/use-after-move>` check by adding
413+
`InvalidationFunctions` option to support custom invalidation functions.
414+
411415
- Improved :doc:`cppcoreguidelines-avoid-non-const-global-variables
412416
<clang-tidy/checks/cppcoreguidelines/avoid-non-const-global-variables>` check
413417
by adding a new option `AllowThreadLocal` that suppresses warnings on

clang-tools-extra/docs/clang-tidy/checks/bugprone/use-after-move.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,3 +253,13 @@ For example, if an additional member variable is added to ``S``, it is easy to
253253
forget to add the reinitialization for this additional member. Instead, it is
254254
safer to assign to the entire struct in one go, and this will also avoid the
255255
use-after-move warning.
256+
257+
Options
258+
-------
259+
260+
.. option:: InvalidationFunctions
261+
262+
A semicolon-separated list of names of functions that cause their first
263+
arguments to be invalidated (e.g., closing a handle).
264+
For member functions, the first argument is considered to be the implicit
265+
object argument (``this``). Default value is an empty string.

clang-tools-extra/test/clang-tidy/checkers/bugprone/use-after-move.cpp

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
1-
// RUN: %check_clang_tidy -std=c++11 -check-suffixes=,CXX11 %s bugprone-use-after-move %t -- -- -fno-delayed-template-parsing
2-
// RUN: %check_clang_tidy -std=c++17-or-later %s bugprone-use-after-move %t -- -- -fno-delayed-template-parsing
1+
// RUN: %check_clang_tidy -std=c++11 -check-suffixes=,CXX11 %s bugprone-use-after-move %t -- \
2+
// RUN: -config='{CheckOptions: { \
3+
// RUN: bugprone-use-after-move.InvalidationFunctions: "::Database<>::StaticCloseConnection;Database<>::CloseConnection;FriendCloseConnection" \
4+
// RUN: }}' -- \
5+
// RUN: -fno-delayed-template-parsing
6+
// RUN: %check_clang_tidy -std=c++17-or-later %s bugprone-use-after-move %t -- \
7+
// RUN: -config='{CheckOptions: { \
8+
// RUN: bugprone-use-after-move.InvalidationFunctions: "::Database<>::StaticCloseConnection;Database<>::CloseConnection;FriendCloseConnection" \
9+
// RUN: }}' -- \
10+
// RUN: -fno-delayed-template-parsing
311

412
typedef decltype(nullptr) nullptr_t;
513

@@ -1645,3 +1653,53 @@ void create() {
16451653
}
16461654

16471655
} // namespace issue82023
1656+
1657+
namespace custom_invalidation
1658+
{
1659+
1660+
template<class T = int>
1661+
struct Database {
1662+
template<class...>
1663+
void CloseConnection(T = T()) {}
1664+
template<class...>
1665+
static void StaticCloseConnection(Database&, T = T()) {}
1666+
template<class...>
1667+
friend void FriendCloseConnection(Database&, T = T()) {}
1668+
void Query();
1669+
};
1670+
1671+
void Run() {
1672+
using DB = Database<>;
1673+
1674+
DB db1;
1675+
db1.CloseConnection();
1676+
db1.Query();
1677+
// CHECK-NOTES: [[@LINE-1]]:3: warning: 'db1' used after it was invalidated
1678+
// CHECK-NOTES: [[@LINE-3]]:7: note: invalidation occurred here
1679+
1680+
DB db2;
1681+
DB::StaticCloseConnection(db2);
1682+
db2.Query();
1683+
// CHECK-NOTES: [[@LINE-1]]:3: warning: 'db2' used after it was invalidated
1684+
// CHECK-NOTES: [[@LINE-3]]:3: note: invalidation occurred here
1685+
1686+
DB db3;
1687+
DB().StaticCloseConnection(db3);
1688+
db3.Query();
1689+
// CHECK-NOTES: [[@LINE-1]]:3: warning: 'db3' used after it was invalidated
1690+
// CHECK-NOTES: [[@LINE-3]]:3: note: invalidation occurred here
1691+
1692+
DB db4;
1693+
FriendCloseConnection(db4);
1694+
db4.Query();
1695+
// CHECK-NOTES: [[@LINE-1]]:3: warning: 'db4' used after it was invalidated
1696+
// CHECK-NOTES: [[@LINE-3]]:3: note: invalidation occurred here
1697+
1698+
DB db5;
1699+
FriendCloseConnection(db5, /*disconnect timeout*/ 5);
1700+
db5.Query();
1701+
// CHECK-NOTES: [[@LINE-1]]:3: warning: 'db5' used after it was invalidated
1702+
// CHECK-NOTES: [[@LINE-3]]:3: note: invalidation occurred here
1703+
}
1704+
1705+
} // namespace custom_invalidation

0 commit comments

Comments
 (0)