Skip to content

Commit 8fbbf33

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

File tree

3 files changed

+89
-24
lines changed

3 files changed

+89
-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,11 +73,17 @@ 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

82+
static auto getNameMatcher(llvm::ArrayRef<StringRef> InvalidationFunctions) {
83+
return anyOf(hasAnyName("::std::move", "::std::forward"),
84+
matchers::matchesAnyListedName(InvalidationFunctions));
85+
}
86+
7987
} // namespace
8088

8189
// Matches nodes that are
@@ -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/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)