https://github.com/serge-sans-paille created
https://github.com/llvm/llvm-project/pull/189638
The following scenario is quite common, but was reported as a use-after-move:
struct Base {
Base(Base&&);
};
struct C : Base {
int field;
C(C&& c) :
Base(std::move(c)), // only moves through the base type
field(c.field) // << this is a valid use-after-move
{}
};
Fix this by checking field origin when the moved value is immediately cast to
base.
>From ed695a9d42ea2e085e62d86a14898e14f392ffa9 Mon Sep 17 00:00:00 2001
From: serge-sans-paille <[email protected]>
Date: Tue, 31 Mar 2026 13:41:59 +0200
Subject: [PATCH] [clang-tidy] Prevent false-positive in presence of
derived-to-base cast in bugprone.use-after-move
The following scenario is quite common, but was reported as a
use-after-move:
struct Base {
Base(Base&&);
};
struct C : Base {
int field;
C(C&& c) :
Base(std::move(c)), // only moves through the base type
field(c.field) // << this is a valid use-after-move
{}
};
Fix this by checking field origin when the moved value is immediately
cast to base.
---
.../clang-tidy/bugprone/UseAfterMoveCheck.cpp | 33 +++++++++++++++----
.../use-after-move-derived-to-base.cpp | 26 +++++++++++++++
2 files changed, 52 insertions(+), 7 deletions(-)
create mode 100644
clang-tools-extra/test/clang-tidy/checkers/bugprone/use-after-move-derived-to-base.cpp
diff --git a/clang-tools-extra/clang-tidy/bugprone/UseAfterMoveCheck.cpp
b/clang-tools-extra/clang-tidy/bugprone/UseAfterMoveCheck.cpp
index 06b5940c648a3..045710f0b0068 100644
--- a/clang-tools-extra/clang-tidy/bugprone/UseAfterMoveCheck.cpp
+++ b/clang-tools-extra/clang-tidy/bugprone/UseAfterMoveCheck.cpp
@@ -28,6 +28,7 @@ using namespace clang::tidy::utils;
namespace clang::tidy::bugprone {
+using ast_matchers::optionally;
using matchers::hasUnevaluatedContext;
namespace {
@@ -52,7 +53,8 @@ class UseAfterMoveFinder {
public:
UseAfterMoveFinder(ASTContext *TheContext,
llvm::ArrayRef<StringRef> InvalidationFunctions,
- llvm::ArrayRef<StringRef> ReinitializationFunctions);
+ llvm::ArrayRef<StringRef> ReinitializationFunctions,
+ const CXXRecordDecl *MovedAs);
// Within the given code block, finds the first use of 'MovedVariable' that
// occurs after 'MovingCall' (the expression that performs the move). If a
@@ -77,6 +79,7 @@ class UseAfterMoveFinder {
ASTContext *Context;
llvm::ArrayRef<StringRef> InvalidationFunctions;
llvm::ArrayRef<StringRef> ReinitializationFunctions;
+ const CXXRecordDecl *MovedAs;
std::unique_ptr<ExprSequence> Sequence;
std::unique_ptr<StmtToBlockMap> BlockMap;
llvm::SmallPtrSet<const CFGBlock *, 8> Visited;
@@ -178,9 +181,10 @@ static StatementMatcher inDecltypeOrTemplateArg() {
UseAfterMoveFinder::UseAfterMoveFinder(
ASTContext *TheContext, llvm::ArrayRef<StringRef> InvalidationFunctions,
- llvm::ArrayRef<StringRef> ReinitializationFunctions)
+ llvm::ArrayRef<StringRef> ReinitializationFunctions,
+ const CXXRecordDecl *MovedAs)
: Context(TheContext), InvalidationFunctions(InvalidationFunctions),
- ReinitializationFunctions(ReinitializationFunctions) {}
+ ReinitializationFunctions(ReinitializationFunctions), MovedAs(MovedAs) {}
std::optional<UseAfterMove>
UseAfterMoveFinder::find(Stmt *CodeBlock, const Expr *MovingCall,
@@ -390,7 +394,13 @@ void UseAfterMoveFinder::getDeclRefs(
DeclRefs](const ArrayRef<BoundNodes> Matches) {
for (const auto &Match : Matches) {
const auto *DeclRef = Match.getNodeAs<DeclRefExpr>("declref");
+ const auto *Member = Match.getNodeAs<MemberExpr>("member-expr");
const auto *Operator =
Match.getNodeAs<CXXOperatorCallExpr>("operator");
+ // Non-moved member as the move only implies a base class.
+ if (Member && MovedAs &&
+ !MovedAs->hasMemberName(Member->getMemberDecl()->getIdentifier()))
{
+ continue;
+ }
if (DeclRef && BlockMap->blockContainingStmt(DeclRef) == Block) {
// Ignore uses of a standard smart pointer or classes annotated as
// "null_after_move" (smart-pointer-like behavior) that don't
@@ -401,9 +411,11 @@ void UseAfterMoveFinder::getDeclRefs(
}
};
- auto DeclRefMatcher =
declRefExpr(hasDeclaration(equalsNode(MovedVariable)),
- unless(inDecltypeOrTemplateArg()))
- .bind("declref");
+ auto DeclRefMatcher =
+ declRefExpr(hasDeclaration(equalsNode(MovedVariable)),
+ unless(inDecltypeOrTemplateArg()),
+ optionally(hasParent(memberExpr().bind("member-expr"))))
+ .bind("declref");
AddDeclRefs(match(traverse(TK_AsIs, findAll(DeclRefMatcher)),
*S->getStmt(),
*Context));
@@ -530,6 +542,7 @@ void UseAfterMoveCheck::registerMatchers(MatchFinder
*Finder) {
hasArgument(0, Arg))),
unless(inDecltypeOrTemplateArg()),
unless(hasParent(TryEmplaceMatcher)), expr().bind("call-move"),
+ optionally(hasParent(implicitCastExpr().bind("optional-cast"))),
anyOf(hasAncestor(compoundStmt(
hasParent(lambdaExpr().bind("containing-lambda")))),
hasAncestor(functionDecl(anyOf(
@@ -575,6 +588,8 @@ void UseAfterMoveCheck::check(const
MatchFinder::MatchResult &Result) {
const auto *MovingCall = Result.Nodes.getNodeAs<Expr>("moving-call");
const auto *Arg = Result.Nodes.getNodeAs<DeclRefExpr>("arg");
const auto *MoveDecl = Result.Nodes.getNodeAs<FunctionDecl>("move-decl");
+ const auto *ParentCast =
+ Result.Nodes.getNodeAs<ImplicitCastExpr>("optional-cast");
if (!MovingCall || !MovingCall->getExprLoc().isValid())
MovingCall = CallMove;
@@ -605,9 +620,13 @@ void UseAfterMoveCheck::check(const
MatchFinder::MatchResult &Result) {
CodeBlocks.push_back(ContainingFunc->getBody());
}
+ const CXXRecordDecl *MovedAs = nullptr;
+ if (ParentCast && ParentCast->getCastKind() == CK_DerivedToBase)
+ MovedAs = ParentCast->getType()->getAsCXXRecordDecl();
+
for (Stmt *CodeBlock : CodeBlocks) {
UseAfterMoveFinder Finder(Result.Context, InvalidationFunctions,
- ReinitializationFunctions);
+ ReinitializationFunctions, MovedAs);
if (auto Use = Finder.find(CodeBlock, MovingCall, Arg))
emitDiagnostic(MovingCall, Arg, *Use, this, Result.Context,
determineMoveType(MoveDecl), MoveDecl);
diff --git
a/clang-tools-extra/test/clang-tidy/checkers/bugprone/use-after-move-derived-to-base.cpp
b/clang-tools-extra/test/clang-tidy/checkers/bugprone/use-after-move-derived-to-base.cpp
new file mode 100644
index 0000000000000..16366a0265a1a
--- /dev/null
+++
b/clang-tools-extra/test/clang-tidy/checkers/bugprone/use-after-move-derived-to-base.cpp
@@ -0,0 +1,26 @@
+// RUN: %check_clang_tidy -std=c++11,c++14 %s bugprone-use-after-move %t
+
+#include <utility>
+
+struct A {
+ int a;
+};
+
+struct B : A {
+ int b;
+ B(B&& other) :
+ A(std::move(other)),
+ b(std::move(other.b)) // No error raised
+ {}
+};
+
+struct C : A {
+ int c;
+ C(C&& other) :
+ A(std::move(other)),
+ {
+ std::move(other.a)
+ // CHECK-NOTES: [[@LINE-1]]:17: warning: 'other' used after it was moved
+ // CHECK-NOTES: [[@LINE-3]]:5: note: move occurred here
+ }
+};
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits