Skip to content

Commit

Permalink
[clang-tidy] Handle C++ structured bindings in `performance-for-range…
Browse files Browse the repository at this point in the history
…-copy` (#77105)

Right now we are not triggering on:

```
for (auto [x, y] : container) {
  // const-only access
}
```
  • Loading branch information
legrosbuffle committed Jan 16, 2024
1 parent 27d963a commit 8fd32b9
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 11 deletions.
13 changes: 10 additions & 3 deletions clang-tools-extra/clang-tidy/performance/ForRangeCopyCheck.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,15 @@ bool ForRangeCopyCheck::handleConstValueCopy(const VarDecl &LoopVar,
return true;
}

static bool isReferenced(const VarDecl &LoopVar, const Stmt &Stmt,
ASTContext &Context) {
const auto IsLoopVar = varDecl(equalsNode(&LoopVar));
return !match(stmt(hasDescendant(declRefExpr(to(valueDecl(anyOf(
IsLoopVar, bindingDecl(forDecomposition(IsLoopVar)))))))),
Stmt, Context)
.empty();
}

bool ForRangeCopyCheck::handleCopyIsOnlyConstReferenced(
const VarDecl &LoopVar, const CXXForRangeStmt &ForRange,
ASTContext &Context) {
Expand All @@ -113,9 +122,7 @@ bool ForRangeCopyCheck::handleCopyIsOnlyConstReferenced(
// compiler warning which can't be suppressed.
// Since this case is very rare, it is safe to ignore it.
if (!ExprMutationAnalyzer(*ForRange.getBody(), Context).isMutated(&LoopVar) &&
!utils::decl_ref_expr::allDeclRefExprs(LoopVar, *ForRange.getBody(),
Context)
.empty()) {
isReferenced(LoopVar, *ForRange.getBody(), Context)) {
auto Diag = diag(
LoopVar.getLocation(),
"loop variable is copied but only used as const reference; consider "
Expand Down
4 changes: 4 additions & 0 deletions clang-tools-extra/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,10 @@ Changes in existing checks
<clang-tidy/checks/performance/faster-string-find>` check to properly escape
single quotes.

- Improved :doc:`performance-for-range-copy
<clang-tidy/checks/performance/for-range-copy>` check to handle cases where
the loop variable is a structured binding.

- Improved :doc:`performance-noexcept-move-constructor
<clang-tidy/checks/performance/noexcept-move-constructor>` to better handle
conditional ``noexcept`` expressions, eliminating false-positives.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ struct S {
S &operator=(const S &);
};

struct Point {
~Point() {}
int x, y;
};

struct Convertible {
operator S() const {
return S();
Expand Down Expand Up @@ -87,6 +92,10 @@ void instantiated() {
// CHECK-MESSAGES: [[@LINE-1]]:16: warning: the loop variable's type is {{.*}}
// CHECK-FIXES: {{^}} for (const S& S2 : View<Iterator<S>>()) {}

for (const auto [X, Y] : View<Iterator<Point>>()) {}
// CHECK-MESSAGES: [[@LINE-1]]:19: warning: the loop variable's type is
// CHECK-FIXES: {{^}} for (const auto& [X, Y] : View<Iterator<Point>>()) {}

for (const T T2 : View<Iterator<T>>()) {}
// CHECK-MESSAGES: [[@LINE-1]]:16: warning: the loop variable's type is {{.*}}
// CHECK-FIXES: {{^}} for (const T& T2 : View<Iterator<T>>()) {}
Expand Down Expand Up @@ -123,11 +132,6 @@ struct Mutable {
~Mutable() {}
};

struct Point {
~Point() {}
int x, y;
};

Mutable& operator<<(Mutable &Out, bool B) {
Out.setBool(B);
return Out;
Expand All @@ -144,6 +148,7 @@ void useByValue(Mutable M);
void useByConstValue(const Mutable M);
void mutate(Mutable *M);
void mutate(Mutable &M);
void mutate(int &);
void onceConstOnceMutated(const Mutable &M1, Mutable &M2);

void negativeVariableIsMutated() {
Expand Down Expand Up @@ -234,6 +239,22 @@ void positiveOnlyAccessedFieldAsConst() {
}
}

void positiveOnlyUsedAsConstBinding() {
for (auto [X, Y] : View<Iterator<Point>>()) {
// CHECK-MESSAGES: [[@LINE-1]]:13: warning: loop variable is copied but
// CHECK-FIXES: for (const auto& [X, Y] : View<Iterator<Point>>()) {
use(X);
use(Y);
}
}

void negativeMutatedBinding() {
for (auto [X, Y] : View<Iterator<Point>>()) {
use(X);
mutate(Y);
}
}

void positiveOnlyUsedInCopyConstructor() {
for (auto A : View<Iterator<Mutable>>()) {
// 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]
Expand Down
13 changes: 10 additions & 3 deletions clang/lib/Analysis/ExprMutationAnalyzer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -243,9 +243,16 @@ const Stmt *ExprMutationAnalyzer::findMutationMemoized(

const Stmt *ExprMutationAnalyzer::tryEachDeclRef(const Decl *Dec,
MutationFinder Finder) {
const auto Refs =
match(findAll(declRefExpr(to(equalsNode(Dec))).bind(NodeID<Expr>::value)),
Stm, Context);
const auto Refs = match(
findAll(
declRefExpr(to(
// `Dec` or a binding if `Dec` is a decomposition.
anyOf(equalsNode(Dec),
bindingDecl(forDecomposition(equalsNode(Dec))))
//
))
.bind(NodeID<Expr>::value)),
Stm, Context);
for (const auto &RefNodes : Refs) {
const auto *E = RefNodes.getNodeAs<Expr>(NodeID<Expr>::value);
if ((this->*Finder)(E))
Expand Down
20 changes: 20 additions & 0 deletions clang/unittests/Analysis/ExprMutationAnalyzerTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -592,6 +592,26 @@ TEST(ExprMutationAnalyzerTest, ByConstRRefArgument) {
ElementsAre("static_cast<A &&>(x)"));
}

// Section: bindings.

TEST(ExprMutationAnalyzerTest, BindingModifies) {
const auto AST =
buildASTFromCode("struct Point { int x; int y; };"
"void mod(int&);"
"void f(Point p) { auto& [x, y] = p; mod(x); }");
const auto Results =
match(withEnclosingCompound(declRefTo("p")), AST->getASTContext());
EXPECT_THAT(mutatedBy(Results, AST.get()), ElementsAre("x", "mod(x)"));
}

TEST(ExprMutationAnalyzerTest, BindingDoesNotModify) {
const auto AST = buildASTFromCode("struct Point { int x; int y; };"
"void f(Point p) { auto& [x, y] = p; x; }");
const auto Results =
match(withEnclosingCompound(declRefTo("p")), AST->getASTContext());
EXPECT_FALSE(isMutated(Results, AST.get()));
}

// section: explicit std::move and std::forward testing

TEST(ExprMutationAnalyzerTest, Move) {
Expand Down

0 comments on commit 8fd32b9

Please sign in to comment.