Skip to content

Commit b3dc437

Browse files
Extend bugprone-use-after-move check to allow custom invalidation functions
1 parent b9e2f7a commit b3dc437

File tree

5 files changed

+103
-24
lines changed

5 files changed

+103
-24
lines changed

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

Lines changed: 44 additions & 20 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();
@@ -389,8 +398,9 @@ void UseAfterMoveFinder::getReinits(
389398
}
390399

391400
enum class MoveType {
392-
Move, // std::move
393-
Forward, // std::forward
401+
Forward, // std::forward
402+
Move, // std::move
403+
Invalidation, // other
394404
};
395405

396406
static MoveType determineMoveType(const FunctionDecl *FuncDecl) {
@@ -399,7 +409,7 @@ static MoveType determineMoveType(const FunctionDecl *FuncDecl) {
399409
if (FuncDecl->getName() == "forward")
400410
return MoveType::Forward;
401411

402-
llvm_unreachable("Invalid move type");
412+
return MoveType::Invalidation;
403413
}
404414

405415
static void emitDiagnostic(const Expr *MovingCall, const DeclRefExpr *MoveArg,
@@ -408,41 +418,55 @@ static void emitDiagnostic(const Expr *MovingCall, const DeclRefExpr *MoveArg,
408418
const SourceLocation UseLoc = Use.DeclRef->getExprLoc();
409419
const SourceLocation MoveLoc = MovingCall->getExprLoc();
410420

411-
const bool IsMove = (Type == MoveType::Move);
421+
const int Kind = static_cast<int>(Type);
412422

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() << Kind;
426+
Check->diag(MoveLoc, "%select{forward|move|invalidation}0 occurred here",
416427
DiagnosticIDs::Note)
417-
<< IsMove;
428+
<< Kind;
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+
<< Kind;
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+
<< Kind;
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
@@ -316,6 +316,10 @@ Changes in existing checks
316316
an additional matcher that generalizes the copy-and-swap idiom pattern
317317
detection.
318318

319+
- Improved :doc:`bugprone-bugprone-use-after-move
320+
<clang-tidy/checks/bugprone/bugprone-use-after-move>` check by adding
321+
an option to support custom invalidation functions.
322+
319323
- Improved :doc:`cppcoreguidelines-init-variables
320324
<clang-tidy/checks/cppcoreguidelines/init-variables>` check by fixing the
321325
insertion location for function pointers with multiple parameters.

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 initial
263+
arguments to be invalidated (e.g., closing a handle).
264+
For member functions, the initial argument is considered to be the implicit
265+
object argument (`this`).

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

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
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 -- -config="{CheckOptions: {bugprone-use-after-move.InvalidationFunctions: 'Database::StaticCloseConnection;Database::CloseConnection;FriendCloseConnection'}}" -- -fno-delayed-template-parsing
2+
// RUN: %check_clang_tidy -std=c++17-or-later %s bugprone-use-after-move %t -- -config="{CheckOptions: {bugprone-use-after-move.InvalidationFunctions: 'Database::StaticCloseConnection;Database::CloseConnection;FriendCloseConnection'}}" -- -fno-delayed-template-parsing
33

44
typedef decltype(nullptr) nullptr_t;
55

@@ -1645,3 +1645,41 @@ void create() {
16451645
}
16461646

16471647
} // namespace issue82023
1648+
1649+
namespace custom_invalidation
1650+
{
1651+
1652+
struct Database {
1653+
void CloseConnection();
1654+
static void StaticCloseConnection(Database&);
1655+
friend void FriendCloseConnection(Database&);
1656+
void Query();
1657+
};
1658+
1659+
void Run() {
1660+
Database db1;
1661+
db1.CloseConnection();
1662+
db1.Query();
1663+
// CHECK-NOTES: [[@LINE-1]]:3: warning: 'db1' used after it was invalidated
1664+
// CHECK-NOTES: [[@LINE-3]]:7: note: invalidation occurred here
1665+
1666+
Database db2;
1667+
Database::StaticCloseConnection(db2);
1668+
db2.Query();
1669+
// CHECK-NOTES: [[@LINE-1]]:3: warning: 'db2' used after it was invalidated
1670+
// CHECK-NOTES: [[@LINE-3]]:3: note: invalidation occurred here
1671+
1672+
Database db3;
1673+
Database().StaticCloseConnection(db3);
1674+
db3.Query();
1675+
// CHECK-NOTES: [[@LINE-1]]:3: warning: 'db3' used after it was invalidated
1676+
// CHECK-NOTES: [[@LINE-3]]:3: note: invalidation occurred here
1677+
1678+
Database db4;
1679+
FriendCloseConnection(db4);
1680+
db4.Query();
1681+
// CHECK-NOTES: [[@LINE-1]]:3: warning: 'db4' used after it was invalidated
1682+
// CHECK-NOTES: [[@LINE-3]]:3: note: invalidation occurred here
1683+
}
1684+
1685+
} // namespace custom_invalidation

0 commit comments

Comments
 (0)