https://github.com/voyager-jhk updated https://github.com/llvm/llvm-project/pull/199905
>From 18e778ec73b194a44abdf67268574882189deea5 Mon Sep 17 00:00:00 2001 From: voyager-jhk <[email protected]> Date: Wed, 27 May 2026 16:54:08 +0800 Subject: [PATCH] [clang-tidy] Fix false positive in bugprone-use-after-move with std::forward on derived classes The `bugprone-use-after-move` check correctly identified partial moves when using `std::move` by matching the `ImplicitCastExpr` (DerivedToBase) as the parent of the call. However, when using `std::forward<Base>`, the cast occurs inside the argument, causing the matcher to miss the cast and falsely report a use-after-move. This patch manually checks the first argument of the moving call for a `DerivedToBase` cast if the parent matcher fails, ensuring both `move` and `forward` are correctly identified as partial moves. Fixes #63202 --- .../clang-tidy/bugprone/UseAfterMoveCheck.cpp | 9 ++- clang-tools-extra/docs/ReleaseNotes.rst | 3 + .../checkers/bugprone/use-after-move.cpp | 57 +++++++++++++++++++ 3 files changed, 67 insertions(+), 2 deletions(-) diff --git a/clang-tools-extra/clang-tidy/bugprone/UseAfterMoveCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/UseAfterMoveCheck.cpp index 361be321185df..a5473709cb802 100644 --- a/clang-tools-extra/clang-tidy/bugprone/UseAfterMoveCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/UseAfterMoveCheck.cpp @@ -560,6 +560,8 @@ void UseAfterMoveCheck::registerMatchers(MatchFinder *Finder) { cxxMemberCallExpr(callee(cxxMethodDecl(hasName("try_emplace")))); auto Arg = declRefExpr().bind("arg"); auto IsMemberCallee = callee(functionDecl(unless(isStaticStorageClass()))); + auto DerivedToBaseCast = + implicitCastExpr(hasCastKind(CK_DerivedToBase)).bind("optional-cast"); auto CallMoveMatcher = callExpr( callee(functionDecl(getNameMatcher(InvalidationFunctions)) .bind("move-decl")), @@ -568,8 +570,11 @@ void UseAfterMoveCheck::registerMatchers(MatchFinder *Finder) { hasArgument(0, Arg))), unless(inDecltypeOrTemplateArg()), unless(hasParent(TryEmplaceMatcher)), expr().bind("call-move"), - optionally(hasParent(implicitCastExpr(hasCastKind(CK_DerivedToBase)) - .bind("optional-cast"))), + optionally( + anyOf(hasParent(DerivedToBaseCast), + hasArgument( + 0, traverse(TK_AsIs, +expr(hasParent(DerivedToBaseCast)))))), anyOf(hasAncestor(compoundStmt( hasParent(lambdaExpr().bind("containing-lambda")))), hasAncestor(functionDecl( diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst index fa53072be1eac..deddecaf65276 100644 --- a/clang-tools-extra/docs/ReleaseNotes.rst +++ b/clang-tools-extra/docs/ReleaseNotes.rst @@ -452,6 +452,9 @@ Changes in existing checks std::move(b))``). The tuple assignment writes back through the stored references, which fully reinitializes the captured variables. + - Avoid false positives when forwarding a derived object to initialize a base + class subobject. + - Improved :doc:`cert-err33-c <clang-tidy/checks/cert/err33-c>` check by not inheriting `CheckedReturnTypes` option from :doc:`bugprone-unused-return-value diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/use-after-move.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/use-after-move.cpp index 0844aa32825f6..1dd67941bd84c 100644 --- a/clang-tools-extra/test/clang-tidy/checkers/bugprone/use-after-move.cpp +++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/use-after-move.cpp @@ -1941,3 +1941,60 @@ void stdTieParenthesizedPartialReinit() { // CHECK-NOTES: [[@LINE-1]]:3: warning: 'a' used after it was moved // CHECK-NOTES: [[@LINE-5]]:3: note: move occurred here } + +namespace GH63202 { + +struct Base { + std::string name; + Base() = default; + Base(Base &&) = default; + Base &operator=(Base &&) = default; +}; + +struct Derived : Base { + std::string surname; + + Derived() = default; + + Derived(Derived &&d) + : Base(std::move(d)), surname(std::move(d.surname)) {} + + Derived(Derived &&d, int) + : Base(std::forward<Base>(d)), surname(std::move(d.surname)) {} + + Derived(Derived &&d, char) : Base(std::forward<Base>(d)) { + d.name.size(); + // CHECK-NOTES: [[@LINE-1]]:5: warning: 'd' used after it was forwarded + // CHECK-NOTES: [[@LINE-3]]:32: note: forward occurred here + } +}; + +void invalidFullMove(Derived &&d) { + Derived other(std::move(d)); + d.surname = "1"; + // CHECK-NOTES: [[@LINE-1]]:3: warning: 'd' used after it was moved + // CHECK-NOTES: [[@LINE-3]]:11: note: move occurred here +} + +void invalidFullForward(Derived &&d) { + Derived other(std::forward<Derived>(d)); + d.surname = "1"; + // CHECK-NOTES: [[@LINE-1]]:3: warning: 'd' used after it was forwarded + // CHECK-NOTES: [[@LINE-3]]:11: note: forward occurred here +} + +void partialForward(Derived &&d) { + Base other(std::forward<Base>(d)); + d.surname = "1"; +} + +template <typename T> +void partialForwardTemplate(T &&d) { + Base other(std::forward<Base>(d)); + d.surname = "1"; +} + +void callPartialForwardTemplate(Derived &&d) { + partialForwardTemplate<Derived>(std::forward<Derived>(d)); +} +} // namespace GH63202 _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
