Skip to content

Commit 8fd32b9

Browse files
authored
[clang-tidy] Handle C++ structured bindings in performance-for-range-copy (#77105)
Right now we are not triggering on: ``` for (auto [x, y] : container) { // const-only access } ```
1 parent 27d963a commit 8fd32b9

File tree

5 files changed

+70
-11
lines changed

5 files changed

+70
-11
lines changed

clang-tools-extra/clang-tidy/performance/ForRangeCopyCheck.cpp

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,15 @@ bool ForRangeCopyCheck::handleConstValueCopy(const VarDecl &LoopVar,
9797
return true;
9898
}
9999

100+
static bool isReferenced(const VarDecl &LoopVar, const Stmt &Stmt,
101+
ASTContext &Context) {
102+
const auto IsLoopVar = varDecl(equalsNode(&LoopVar));
103+
return !match(stmt(hasDescendant(declRefExpr(to(valueDecl(anyOf(
104+
IsLoopVar, bindingDecl(forDecomposition(IsLoopVar)))))))),
105+
Stmt, Context)
106+
.empty();
107+
}
108+
100109
bool ForRangeCopyCheck::handleCopyIsOnlyConstReferenced(
101110
const VarDecl &LoopVar, const CXXForRangeStmt &ForRange,
102111
ASTContext &Context) {
@@ -113,9 +122,7 @@ bool ForRangeCopyCheck::handleCopyIsOnlyConstReferenced(
113122
// compiler warning which can't be suppressed.
114123
// Since this case is very rare, it is safe to ignore it.
115124
if (!ExprMutationAnalyzer(*ForRange.getBody(), Context).isMutated(&LoopVar) &&
116-
!utils::decl_ref_expr::allDeclRefExprs(LoopVar, *ForRange.getBody(),
117-
Context)
118-
.empty()) {
125+
isReferenced(LoopVar, *ForRange.getBody(), Context)) {
119126
auto Diag = diag(
120127
LoopVar.getLocation(),
121128
"loop variable is copied but only used as const reference; consider "

clang-tools-extra/docs/ReleaseNotes.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,10 @@ Changes in existing checks
447447
<clang-tidy/checks/performance/faster-string-find>` check to properly escape
448448
single quotes.
449449

450+
- Improved :doc:`performance-for-range-copy
451+
<clang-tidy/checks/performance/for-range-copy>` check to handle cases where
452+
the loop variable is a structured binding.
453+
450454
- Improved :doc:`performance-noexcept-move-constructor
451455
<clang-tidy/checks/performance/noexcept-move-constructor>` to better handle
452456
conditional ``noexcept`` expressions, eliminating false-positives.

clang-tools-extra/test/clang-tidy/checkers/performance/for-range-copy.cpp

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,11 @@ struct S {
4747
S &operator=(const S &);
4848
};
4949

50+
struct Point {
51+
~Point() {}
52+
int x, y;
53+
};
54+
5055
struct Convertible {
5156
operator S() const {
5257
return S();
@@ -87,6 +92,10 @@ void instantiated() {
8792
// CHECK-MESSAGES: [[@LINE-1]]:16: warning: the loop variable's type is {{.*}}
8893
// CHECK-FIXES: {{^}} for (const S& S2 : View<Iterator<S>>()) {}
8994

95+
for (const auto [X, Y] : View<Iterator<Point>>()) {}
96+
// CHECK-MESSAGES: [[@LINE-1]]:19: warning: the loop variable's type is
97+
// CHECK-FIXES: {{^}} for (const auto& [X, Y] : View<Iterator<Point>>()) {}
98+
9099
for (const T T2 : View<Iterator<T>>()) {}
91100
// CHECK-MESSAGES: [[@LINE-1]]:16: warning: the loop variable's type is {{.*}}
92101
// CHECK-FIXES: {{^}} for (const T& T2 : View<Iterator<T>>()) {}
@@ -123,11 +132,6 @@ struct Mutable {
123132
~Mutable() {}
124133
};
125134

126-
struct Point {
127-
~Point() {}
128-
int x, y;
129-
};
130-
131135
Mutable& operator<<(Mutable &Out, bool B) {
132136
Out.setBool(B);
133137
return Out;
@@ -144,6 +148,7 @@ void useByValue(Mutable M);
144148
void useByConstValue(const Mutable M);
145149
void mutate(Mutable *M);
146150
void mutate(Mutable &M);
151+
void mutate(int &);
147152
void onceConstOnceMutated(const Mutable &M1, Mutable &M2);
148153

149154
void negativeVariableIsMutated() {
@@ -234,6 +239,22 @@ void positiveOnlyAccessedFieldAsConst() {
234239
}
235240
}
236241

242+
void positiveOnlyUsedAsConstBinding() {
243+
for (auto [X, Y] : View<Iterator<Point>>()) {
244+
// CHECK-MESSAGES: [[@LINE-1]]:13: warning: loop variable is copied but
245+
// CHECK-FIXES: for (const auto& [X, Y] : View<Iterator<Point>>()) {
246+
use(X);
247+
use(Y);
248+
}
249+
}
250+
251+
void negativeMutatedBinding() {
252+
for (auto [X, Y] : View<Iterator<Point>>()) {
253+
use(X);
254+
mutate(Y);
255+
}
256+
}
257+
237258
void positiveOnlyUsedInCopyConstructor() {
238259
for (auto A : View<Iterator<Mutable>>()) {
239260
// CHECK-MESSAGES: [[@LINE-1]]:13: warning: loop variable is copied but only used as const reference; consider making it a const reference [performance-for-range-copy]

clang/lib/Analysis/ExprMutationAnalyzer.cpp

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -243,9 +243,16 @@ const Stmt *ExprMutationAnalyzer::findMutationMemoized(
243243

244244
const Stmt *ExprMutationAnalyzer::tryEachDeclRef(const Decl *Dec,
245245
MutationFinder Finder) {
246-
const auto Refs =
247-
match(findAll(declRefExpr(to(equalsNode(Dec))).bind(NodeID<Expr>::value)),
248-
Stm, Context);
246+
const auto Refs = match(
247+
findAll(
248+
declRefExpr(to(
249+
// `Dec` or a binding if `Dec` is a decomposition.
250+
anyOf(equalsNode(Dec),
251+
bindingDecl(forDecomposition(equalsNode(Dec))))
252+
//
253+
))
254+
.bind(NodeID<Expr>::value)),
255+
Stm, Context);
249256
for (const auto &RefNodes : Refs) {
250257
const auto *E = RefNodes.getNodeAs<Expr>(NodeID<Expr>::value);
251258
if ((this->*Finder)(E))

clang/unittests/Analysis/ExprMutationAnalyzerTest.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -592,6 +592,26 @@ TEST(ExprMutationAnalyzerTest, ByConstRRefArgument) {
592592
ElementsAre("static_cast<A &&>(x)"));
593593
}
594594

595+
// Section: bindings.
596+
597+
TEST(ExprMutationAnalyzerTest, BindingModifies) {
598+
const auto AST =
599+
buildASTFromCode("struct Point { int x; int y; };"
600+
"void mod(int&);"
601+
"void f(Point p) { auto& [x, y] = p; mod(x); }");
602+
const auto Results =
603+
match(withEnclosingCompound(declRefTo("p")), AST->getASTContext());
604+
EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("x", "mod(x)"));
605+
}
606+
607+
TEST(ExprMutationAnalyzerTest, BindingDoesNotModify) {
608+
const auto AST = buildASTFromCode("struct Point { int x; int y; };"
609+
"void f(Point p) { auto& [x, y] = p; x; }");
610+
const auto Results =
611+
match(withEnclosingCompound(declRefTo("p")), AST->getASTContext());
612+
EXPECT_FALSE(isMutated(Results, AST.get()));
613+
}
614+
595615
// section: explicit std::move and std::forward testing
596616

597617
TEST(ExprMutationAnalyzerTest, Move) {

0 commit comments

Comments
 (0)