Skip to content

[clang-tidy] Fix loop-convert for iterators with non-trivial destructors - #236084

Open
yashaggarwal85d wants to merge 3 commits into
llvm:mainfrom
yashaggarwal85d:fix-loop-convert-nontrivial-iterator-dtor
Open

yashaggarwal85d wants to merge 3 commits into
llvm:mainfrom
yashaggarwal85d:fix-loop-convert-nontrivial-iterator-dtor

Conversation

@yashaggarwal85d

Copy link
Copy Markdown
Contributor

When the iterator type has a non-trivial destructor, the end() call in the loop condition is wrapped in a CXXBindTemporaryExpr. The matcher for the loop bound only used ignoringParenImpCasts, which doesn't skip it, so loops like for (It it = c.begin(); it != c.end(); ++it) were never flagged. This also hits MSVC STL iterators in debug builds (_ITERATOR_DEBUG_LEVEL=2).

I added ignoringImplicit inside it so the bound temporary is skipped too. The old materializeTemporaryExpr(...) alternative is dropped because ignoringParenImpCasts already looks through MaterializeTemporaryExpr, so it didn't match anything the first branch didn't.

Fixes #234990

This was AI-assisted; I reviewed and tested the change myself.

@github-actions

Copy link
Copy Markdown

Hello @yashaggarwal85d 👋

Thank you for submitting a Pull Request (PR) to the LLVM Project. Since this is your first PR, here are a few useful links covering our main contribution policies and review practices.

  • All contributions to LLVM must follow our LLVM AI Tool Use Policy. In particular, if you used AI while working on this PR, remember to add a note to the PR description.
  • The LLVM Code-Review Policy and Practices document contains practical information about the PR process, including how patches are reviewed and accepted, and who can review a PR.
  • Our LLVM Developer Policy describes our expectations for code quality, commit summaries and contains notes on our CI system.

Please reply to this message to confirm that you have read these policies, especially the LLVM AI Tool Use Policy, and that any AI tool usage has been noted in the PR description.


Frequently asked questions

How do I add reviewers?

This PR will be automatically labeled, and the relevant teams will be notified. For some parts of the project, reviewers may also be added automatically.

You can also add reviewers manually using the Reviewers section on this page. If you cannot use that section, it is probably because you do not have write permissions for the repository. In that case, you can request a review by tagging reviewers in a comment using @ followed by their GitHub username.

What if there are no comments?

If you have not received any comments on your PR after a week, you can request a review by pinging the PR with a comment such as “Ping”. The common courtesy ping rate is once a week. Please remember that you are asking for volunteer time from other developers.

Are any special GitHub settings required to contribute to LLVM?

We only require contributors to have a public email address associated with their GitHub commits, see this section of LLVM Developer Policy for details.


If you have questions, feel free to leave a comment on this PR, or ask on LLVM Discord or LLVM Discourse.

Thank you,
The LLVM Community

@llvmorg-github-actions

Copy link
Copy Markdown

@llvm/pr-subscribers-clang-tools-extra

Author: Yash Aggarwal (yashaggarwal85d)

Changes

When the iterator type has a non-trivial destructor, the end() call in the loop condition is wrapped in a CXXBindTemporaryExpr. The matcher for the loop bound only used ignoringParenImpCasts, which doesn't skip it, so loops like for (It it = c.begin(); it != c.end(); ++it) were never flagged. This also hits MSVC STL iterators in debug builds (_ITERATOR_DEBUG_LEVEL=2).

I added ignoringImplicit inside it so the bound temporary is skipped too. The old materializeTemporaryExpr(...) alternative is dropped because ignoringParenImpCasts already looks through MaterializeTemporaryExpr, so it didn't match anything the first branch didn't.

Fixes #234990

This was AI-assisted; I reviewed and tested the change myself.


Full diff: https://github.com/llvm/llvm-project/pull/236084.diff

3 Files Affected:

  • (modified) clang-tools-extra/clang-tidy/modernize/LoopConvertCheck.cpp (+1-2)
  • (modified) clang-tools-extra/docs/ReleaseNotes.md (+5)
  • (added) clang-tools-extra/test/clang-tidy/checkers/modernize/loop-convert-nontrivial-iterator-dtor.cpp (+43)
diff --git a/clang-tools-extra/clang-tidy/modernize/LoopConvertCheck.cpp b/clang-tools-extra/clang-tidy/modernize/LoopConvertCheck.cpp
index 75bf2a73259000..d5b252b66522c3 100644
--- a/clang-tools-extra/clang-tidy/modernize/LoopConvertCheck.cpp
+++ b/clang-tools-extra/clang-tidy/modernize/LoopConvertCheck.cpp
@@ -197,8 +197,7 @@ static StatementMatcher makeIteratorLoopMatcher(bool IsReverse) {
   const StatementMatcher IteratorBoundMatcher =
       expr(anyOf(ignoringParenImpCasts(
                      declRefExpr(to(varDecl(equalsBoundNode(EndVarName))))),
-                 ignoringParenImpCasts(expr(EndCallMatcher).bind(EndCallName)),
-                 materializeTemporaryExpr(ignoringParenImpCasts(
+                 ignoringParenImpCasts(ignoringImplicit(
                      expr(EndCallMatcher).bind(EndCallName)))));
 
   const StatementMatcher IteratorComparisonMatcher = expr(ignoringParenImpCasts(
diff --git a/clang-tools-extra/docs/ReleaseNotes.md b/clang-tools-extra/docs/ReleaseNotes.md
index 310b084c04eb04..45c5f30d05be7f 100644
--- a/clang-tools-extra/docs/ReleaseNotes.md
+++ b/clang-tools-extra/docs/ReleaseNotes.md
@@ -283,6 +283,11 @@ infrastructure are described first, followed by tool-specific sections.
   nested expressions involving different macros or a mix of macro and
   non-macro operands.
 
+- Improved {doc}`modernize-loop-convert
+  <clang-tidy/checks/modernize/loop-convert>` check to also convert iterator
+  loops that call `end()` in the condition when the iterator type has a
+  non-trivial destructor.
+
 - Fixed a crash in {doc}`modernize-raw-string-literal
   <clang-tidy/checks/modernize/raw-string-literal>` on synthetic string
   literals created for raw user-defined literal operators, such as `12_w`.
diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/loop-convert-nontrivial-iterator-dtor.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/loop-convert-nontrivial-iterator-dtor.cpp
new file mode 100644
index 00000000000000..c19f21a59183c0
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/loop-convert-nontrivial-iterator-dtor.cpp
@@ -0,0 +1,43 @@
+// RUN: %check_clang_tidy %s modernize-loop-convert %t
+
+struct Container {
+  struct iterator {
+    ~iterator() {}
+    int &operator*() const;
+    iterator &operator++();
+    bool operator!=(const iterator &) const;
+  };
+  iterator begin();
+  iterator end();
+};
+
+void f(Container &C) {
+  for (Container::iterator It = C.begin(); It != C.end(); ++It)
+    (void)*It;
+  // CHECK-MESSAGES: :[[@LINE-2]]:3: warning: use range-based for loop instead
+  // CHECK-FIXES: for (int & It : C)
+  // CHECK-FIXES-NEXT: (void)It;
+
+  for (Container::iterator It = C.begin(); It != (C.end()); ++It)
+    (void)*It;
+  // CHECK-MESSAGES: :[[@LINE-2]]:3: warning: use range-based for loop instead
+  // CHECK-FIXES: for (int & It : C)
+  // CHECK-FIXES-NEXT: (void)It;
+
+  for (Container::iterator It = C.begin(); C.end() != It; ++It)
+    (void)*It;
+  // CHECK-MESSAGES: :[[@LINE-2]]:3: warning: use range-based for loop instead
+  // CHECK-FIXES: for (int & It : C)
+  // CHECK-FIXES-NEXT: (void)It;
+
+  for (Container::iterator It = C.begin(), E = C.end(); It != E; ++It)
+    (void)*It;
+  // CHECK-MESSAGES: :[[@LINE-2]]:3: warning: use range-based for loop instead
+  // CHECK-FIXES: for (int & It : C)
+  // CHECK-FIXES-NEXT: (void)It;
+}
+
+void negative(Container &C, Container &D) {
+  for (Container::iterator It = C.begin(); It != D.end(); ++It)
+    (void)*It;
+}

@llvmorg-github-actions

Copy link
Copy Markdown

@llvm/pr-subscribers-clang-tidy

Author: Yash Aggarwal (yashaggarwal85d)

Changes

When the iterator type has a non-trivial destructor, the end() call in the loop condition is wrapped in a CXXBindTemporaryExpr. The matcher for the loop bound only used ignoringParenImpCasts, which doesn't skip it, so loops like for (It it = c.begin(); it != c.end(); ++it) were never flagged. This also hits MSVC STL iterators in debug builds (_ITERATOR_DEBUG_LEVEL=2).

I added ignoringImplicit inside it so the bound temporary is skipped too. The old materializeTemporaryExpr(...) alternative is dropped because ignoringParenImpCasts already looks through MaterializeTemporaryExpr, so it didn't match anything the first branch didn't.

Fixes #234990

This was AI-assisted; I reviewed and tested the change myself.


Full diff: https://github.com/llvm/llvm-project/pull/236084.diff

3 Files Affected:

  • (modified) clang-tools-extra/clang-tidy/modernize/LoopConvertCheck.cpp (+1-2)
  • (modified) clang-tools-extra/docs/ReleaseNotes.md (+5)
  • (added) clang-tools-extra/test/clang-tidy/checkers/modernize/loop-convert-nontrivial-iterator-dtor.cpp (+43)
diff --git a/clang-tools-extra/clang-tidy/modernize/LoopConvertCheck.cpp b/clang-tools-extra/clang-tidy/modernize/LoopConvertCheck.cpp
index 75bf2a73259000..d5b252b66522c3 100644
--- a/clang-tools-extra/clang-tidy/modernize/LoopConvertCheck.cpp
+++ b/clang-tools-extra/clang-tidy/modernize/LoopConvertCheck.cpp
@@ -197,8 +197,7 @@ static StatementMatcher makeIteratorLoopMatcher(bool IsReverse) {
   const StatementMatcher IteratorBoundMatcher =
       expr(anyOf(ignoringParenImpCasts(
                      declRefExpr(to(varDecl(equalsBoundNode(EndVarName))))),
-                 ignoringParenImpCasts(expr(EndCallMatcher).bind(EndCallName)),
-                 materializeTemporaryExpr(ignoringParenImpCasts(
+                 ignoringParenImpCasts(ignoringImplicit(
                      expr(EndCallMatcher).bind(EndCallName)))));
 
   const StatementMatcher IteratorComparisonMatcher = expr(ignoringParenImpCasts(
diff --git a/clang-tools-extra/docs/ReleaseNotes.md b/clang-tools-extra/docs/ReleaseNotes.md
index 310b084c04eb04..45c5f30d05be7f 100644
--- a/clang-tools-extra/docs/ReleaseNotes.md
+++ b/clang-tools-extra/docs/ReleaseNotes.md
@@ -283,6 +283,11 @@ infrastructure are described first, followed by tool-specific sections.
   nested expressions involving different macros or a mix of macro and
   non-macro operands.
 
+- Improved {doc}`modernize-loop-convert
+  <clang-tidy/checks/modernize/loop-convert>` check to also convert iterator
+  loops that call `end()` in the condition when the iterator type has a
+  non-trivial destructor.
+
 - Fixed a crash in {doc}`modernize-raw-string-literal
   <clang-tidy/checks/modernize/raw-string-literal>` on synthetic string
   literals created for raw user-defined literal operators, such as `12_w`.
diff --git a/clang-tools-extra/test/clang-tidy/checkers/modernize/loop-convert-nontrivial-iterator-dtor.cpp b/clang-tools-extra/test/clang-tidy/checkers/modernize/loop-convert-nontrivial-iterator-dtor.cpp
new file mode 100644
index 00000000000000..c19f21a59183c0
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/modernize/loop-convert-nontrivial-iterator-dtor.cpp
@@ -0,0 +1,43 @@
+// RUN: %check_clang_tidy %s modernize-loop-convert %t
+
+struct Container {
+  struct iterator {
+    ~iterator() {}
+    int &operator*() const;
+    iterator &operator++();
+    bool operator!=(const iterator &) const;
+  };
+  iterator begin();
+  iterator end();
+};
+
+void f(Container &C) {
+  for (Container::iterator It = C.begin(); It != C.end(); ++It)
+    (void)*It;
+  // CHECK-MESSAGES: :[[@LINE-2]]:3: warning: use range-based for loop instead
+  // CHECK-FIXES: for (int & It : C)
+  // CHECK-FIXES-NEXT: (void)It;
+
+  for (Container::iterator It = C.begin(); It != (C.end()); ++It)
+    (void)*It;
+  // CHECK-MESSAGES: :[[@LINE-2]]:3: warning: use range-based for loop instead
+  // CHECK-FIXES: for (int & It : C)
+  // CHECK-FIXES-NEXT: (void)It;
+
+  for (Container::iterator It = C.begin(); C.end() != It; ++It)
+    (void)*It;
+  // CHECK-MESSAGES: :[[@LINE-2]]:3: warning: use range-based for loop instead
+  // CHECK-FIXES: for (int & It : C)
+  // CHECK-FIXES-NEXT: (void)It;
+
+  for (Container::iterator It = C.begin(), E = C.end(); It != E; ++It)
+    (void)*It;
+  // CHECK-MESSAGES: :[[@LINE-2]]:3: warning: use range-based for loop instead
+  // CHECK-FIXES: for (int & It : C)
+  // CHECK-FIXES-NEXT: (void)It;
+}
+
+void negative(Container &C, Container &D) {
+  for (Container::iterator It = C.begin(); It != D.end(); ++It)
+    (void)*It;
+}

@yashaggarwal85d

Copy link
Copy Markdown
Contributor Author

Read the policies, thanks

@github-actions

Copy link
Copy Markdown

⚠️ We detected that you are using a GitHub private e-mail address to contribute to the repo.
Please turn off Keep my email addresses private setting in your account.
See LLVM Developer Policy and LLVM Discourse for more information.

@zeyi2 zeyi2 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mostly LG! One comment for the tests.

Also please make your email address public :)

@@ -0,0 +1,43 @@
// RUN: %check_clang_tidy %s modernize-loop-convert %t

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we move the test to existing loop-convert-extra.cpp?

There are also iterator and container types in Inputs/loop-convert/structures.h that we may be able to reuse or extend, rather than introducing a new Container here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Moved the tests into loop-convert-extra.cpp and dropped the separate file.

@yashaggarwal85d

Copy link
Copy Markdown
Contributor Author

Mostly LG! One comment for the tests.

Also please make your email address public :)

Thanks @zeyi2 for the review. I have adressed them and made my email public as well. Thanks!

@yashaggarwal85d
yashaggarwal85d requested a review from zeyi2 November 2, 2026 05:18

@zeyi2 zeyi2 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

zeyi2 added a commit to clang-tidy-infra/CTIT that referenced this pull request Nov 2, 2026
A test run on llvm/llvm-project#236084 marked all 884 warnings TP with 873
"Same as #1" rationales and no reproducers, missing 4 real FPs. Require a
source read per row, name the shared pattern in "Same as", and choose
reproducer rows by pattern rather than by expected verdict.

Amp-Thread-ID: https://ampcode.com/threads/T-01a19acc-7034-72fe-908f-15ca2649429a
Co-authored-by: Zeyi Xu <[email protected]>
zeyi2 added a commit to clang-tidy-infra/CTIT that referenced this pull request Nov 2, 2026
A run reviewing all 884 warnings of llvm/llvm-project#236084 hit the
45-minute step limit, which also skipped the second pass and the final
report check. Raise the step limit to 120 minutes with per-pass timeouts so
the check always runs, and have the agent save verdicts every 20 rows so a
timeout keeps the finished rows.

Amp-Thread-ID: https://ampcode.com/threads/T-01a19acc-7034-72fe-908f-15ca2649429a
Co-authored-by: Zeyi Xu <[email protected]>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[clang-tidy] modernize-loop-convert: loops not detected when the iterator type has a non-trivial destructor

2 participants