Author: Yuta Nakamura Date: 2026-06-02T01:58:55Z New Revision: 8ce6c5ff2c134414d8e1815bdc122699abd89954
URL: https://github.com/llvm/llvm-project/commit/8ce6c5ff2c134414d8e1815bdc122699abd89954 DIFF: https://github.com/llvm/llvm-project/commit/8ce6c5ff2c134414d8e1815bdc122699abd89954.diff LOG: [clang-tidy] Fix false positive in bugprone-use-after-move for std::tie (#192895) std::tie(a, b) = expr reinitializes all variables passed to std::tie because the tuple assignment operator writes back through the stored references. The check was not recognizing this pattern, causing a false positive on the second std::tie assignment in loops like: std::tie(a, b) = foo(std::move(a), std::move(b)); std::tie(a, b) = foo(std::move(a), std::move(b)); // false positive Add std::tie assignment as a reinitialization case in makeReinitMatcher(). Fixes #136105. --- **AI Disclosure:** Claude (Anthropic) was used to assist in diagnosing the CI test failure and identifying the off-by-one line number in the CHECK-NOTES. --------- Co-authored-by: Claude Sonnet 4.6 <[email protected]> Co-authored-by: Zeyi Xu <[email protected]> Added: Modified: clang-tools-extra/clang-tidy/bugprone/UseAfterMoveCheck.cpp clang-tools-extra/docs/ReleaseNotes.rst clang-tools-extra/docs/clang-tidy/checks/bugprone/use-after-move.rst clang-tools-extra/test/clang-tidy/checkers/bugprone/use-after-move.cpp Removed: ################################################################################ diff --git a/clang-tools-extra/clang-tidy/bugprone/UseAfterMoveCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/UseAfterMoveCheck.cpp index 399442f52bd33..361be321185df 100644 --- a/clang-tools-extra/clang-tidy/bugprone/UseAfterMoveCheck.cpp +++ b/clang-tools-extra/clang-tidy/bugprone/UseAfterMoveCheck.cpp @@ -134,6 +134,15 @@ makeReinitMatcher(const ValueDecl *MovedVariable, // built-in types. binaryOperation(hasOperatorName("="), hasLHS(ignoringParenImpCasts(DeclRefMatcher))), + // std::tie() assignment: std::tie(a, b) = expr reinitializes + // all variables passed to std::tie because the tuple + // assignment writes back through the stored references. + binaryOperation( + hasOperatorName("="), + hasLHS(ignoringImplicit(ignoringParenImpCasts( + callExpr(callee(functionDecl(hasName("::std::tie"))), + hasAnyArgument(ignoringParenImpCasts( + DeclRefMatcher))))))), // Declaration. We treat this as a type of reinitialization // too, so we don't need to treat it separately. declStmt(hasDescendant(equalsNode(MovedVariable))), diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst index 0189e84f8f12f..ba0cea800cd4e 100644 --- a/clang-tools-extra/docs/ReleaseNotes.rst +++ b/clang-tools-extra/docs/ReleaseNotes.rst @@ -441,6 +441,11 @@ Changes in existing checks - Avoid false positives when moving object is reinitialized via the base class's ``operator=``. + - Fix a false positive when a moved-from variable is reinitialized + via a ``std::tie()`` assignment (e.g. ``std::tie(a, b) = f(std::move(a), + std::move(b))``). The tuple assignment writes back through the stored + references, which fully reinitializes the captured variables. + - 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/docs/clang-tidy/checks/bugprone/use-after-move.rst b/clang-tools-extra/docs/clang-tidy/checks/bugprone/use-after-move.rst index da72f742b38d0..4e746b2633c07 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/bugprone/use-after-move.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/bugprone/use-after-move.rst @@ -233,6 +233,11 @@ The check considers a variable to be reinitialized in the following cases: - A member function marked with the ``[[clang::reinitializes]]`` attribute is called on the variable. + - The variable is passed as an argument to ``std::tie`` on the left-hand + side of an assignment (e.g. ``std::tie(a, b) = f(...)``). The tuple + assignment operator writes back through the stored references, which + reinitializes each named variable. + If the variable in question is a struct and an individual member variable of that struct is written to, the check does not consider this to be a reinitialization -- even if, eventually, all member variables of the struct are 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 d4e78d359b654..0844aa32825f6 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 @@ -17,6 +17,7 @@ #include <map> #include <set> #include <string> +#include <tuple> #include <unordered_map> #include <unordered_set> #include <utility> @@ -1814,3 +1815,129 @@ namespace GH62206 { (d) = b; // Should not warn } } // namespace GH62206 + + +std::pair<std::string, std::string> makeStringPair(std::string a, + std::string b); + +void stdTieIsReinit() { + std::string a, b; + std::move(a); + std::move(b); + std::tie(a, b) = makeStringPair("x", "y"); + a.size(); + b.size(); +} + +void stdTieWithIgnore() { + std::string a; + std::move(a); + std::tie(a, std::ignore) = makeStringPair("x", "y"); + a.size(); +} + +void stdTieIgnoreFlipped() { + std::string a; + std::move(a); + std::tie(std::ignore, a) = makeStringPair("x", "y"); + a.size(); +} + +void stdTieThreeVars() { + std::string a, b, c; + std::move(a); + std::move(b); + std::move(c); + std::tie(a, b, c) = + std::make_tuple(std::string("x"), std::string("y"), std::string("z")); + a.size(); + b.size(); + c.size(); +} + +void stdTiePartialReinit() { + std::string a, b, c; + std::move(a); + std::move(b); + std::move(c); + std::tie(b) = std::make_tuple(std::string("y")); + b.size(); + std::tie(c, b, std::ignore) = + std::make_tuple(std::string("x"), std::string("y"), std::string("z")); + b.size(); + c.size(); + a.size(); + // CHECK-NOTES: [[@LINE-1]]:3: warning: 'a' used after it was moved + // CHECK-NOTES: [[@LINE-11]]:3: note: move occurred here +} + +void stdTieInLoop() { + std::string a, b; + while (true) { + std::tie(a, b) = makeStringPair(std::move(a), std::move(b)); + std::tie(a, b) = makeStringPair(std::move(a), std::move(b)); + } +} + +template <typename T> +void stdTieReinitInTemplate() { + T a, b; + std::move(a); + std::move(b); + std::tie(a, b) = std::make_tuple(T(), T()); + a.size(); + b.size(); +} + +template <typename T> +void stdTiePartialReinitInTemplate() { + T a, b; + std::move(a); + std::tie(b) = std::make_tuple(T()); + b.size(); + a.size(); + // CHECK-NOTES: [[@LINE-1]]:3: warning: 'a' used after it was moved + // CHECK-NOTES: [[@LINE-5]]:3: note: move occurred here +} + +void callStdTieTemplateTests() { + stdTieReinitInTemplate<std::string>(); + stdTiePartialReinitInTemplate<std::string>(); +} + +void stdTieParenthesized() { + // Parenthesized std::tie on the LHS still reinitializes captured variables. + std::string a, b; + std::move(a); + std::move(b); + (std::tie(a, b)) = makeStringPair("x", "y"); // no-warning: both reinitialized + a.size(); + b.size(); +} + +void stdTieParenthesizedWithIgnore() { + // Parenthesized std::tie with std::ignore still reinitializes named variables. + std::string a; + std::move(a); + (std::tie(a, std::ignore)) = makeStringPair("x", "y"); // no-warning: a reinitialized + a.size(); +} + +void stdTieParenthesizedIgnoreFlipped() { + // std::ignore in first position inside parenthesized std::tie. + std::string a; + std::move(a); + (std::tie(std::ignore, a)) = makeStringPair("x", "y"); // no-warning: a reinitialized + a.size(); +} + +void stdTieParenthesizedPartialReinit() { + // Parenthesized std::tie only reinitializes variables named in the call. + std::string a, b; + std::move(a); + (std::tie(b)) = std::make_tuple(std::string("y")); // reinitializes b, not a + b.size(); // no-warning: b was reinitialized + a.size(); + // CHECK-NOTES: [[@LINE-1]]:3: warning: 'a' used after it was moved + // CHECK-NOTES: [[@LINE-5]]:3: note: move occurred here +} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
