[clang-tools-extra] 8ce6c5f - [clang-tidy] Fix false positive in bugprone-use-after-move for std::tie (#192895)
via cfe-commits
cfe-commits at lists.llvm.org
Mon Jun 1 18:59:01 PDT 2026
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 <noreply at anthropic.com>
Co-authored-by: Zeyi Xu <mitchell.xu2 at gmail.com>
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
+}
More information about the cfe-commits
mailing list