https://github.com/serge-sans-paille updated https://github.com/llvm/llvm-project/pull/189638
>From b7a14410c9b685d67955fef020d5e94e5b0a0bb5 Mon Sep 17 00:00:00 2001 From: serge-sans-paille <[email protected]> Date: Tue, 31 Mar 2026 13:41:59 +0200 Subject: [PATCH 1/3] [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 + } +}; >From 986b664a2bf5c4951865a26e6ef3e62acd7e48ce Mon Sep 17 00:00:00 2001 From: serge-sans-paille <[email protected]> Date: Tue, 31 Mar 2026 14:02:37 +0200 Subject: [PATCH 2/3] fixup! [clang-tidy] Prevent false-positive in presence of derived-to-base cast in bugprone.use-after-move --- .../checkers/bugprone/use-after-move-derived-to-base.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 index 16366a0265a1a..a088054f17544 100644 --- 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 @@ -17,10 +17,10 @@ struct B : A { struct C : A { int c; C(C&& other) : - A(std::move(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 + other.a; + // CHECK-NOTES: [[@LINE-1]]:7: warning: 'other' used after it was moved + // CHECK-NOTES: [[@LINE-4]]:5: note: move occurred here } }; >From 470c806890870e4f52eab83a9f9413179d8316a2 Mon Sep 17 00:00:00 2001 From: serge-sans-paille <[email protected]> Date: Tue, 31 Mar 2026 17:29:26 +0200 Subject: [PATCH 3/3] fixup! fixup! [clang-tidy] Prevent false-positive in presence of derived-to-base cast in bugprone.use-after-move --- clang-tools-extra/docs/ReleaseNotes.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst index 69dc5b9633398..78eafe945db40 100644 --- a/clang-tools-extra/docs/ReleaseNotes.rst +++ b/clang-tools-extra/docs/ReleaseNotes.rst @@ -275,6 +275,9 @@ Changes in existing checks - Add support for annotation of user-defined types as having the same moved-from semantics as standard smart pointers. + - Avoid false positives when moving object to a base type then accessing + non-base members. + - Improved :doc:`cppcoreguidelines-init-variables <clang-tidy/checks/cppcoreguidelines/init-variables>` check by ensuring that member pointers are correctly flagged as uninitialized. _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
