[clang-tools-extra] [clang-tidy] Fix `altera-id-dependent-backward-branch` false positives (PR #200660)
Daniil Dudkin via cfe-commits
cfe-commits at lists.llvm.org
Sat Jun 6 11:21:06 PDT 2026
https://github.com/unterumarmung updated https://github.com/llvm/llvm-project/pull/200660
>From 52499242bb66f58abd1f664274f8ee042985f37c Mon Sep 17 00:00:00 2001
From: Daniil Dudkin <unterumarmung at yandex.ru>
Date: Sun, 31 May 2026 16:01:00 +0300
Subject: [PATCH 1/4] [clang-tidy] Fix `altera-id-dependent-backward-branch`
false positives
The check unconditionally marked the LHS of any assignment from a variable
or field as ID-dependent. As a result, ordinary assignments like `x = y`
or `i += chunk_size` could trigger false positives in later loop conditions.
Only propagate ID-dependency when the RHS variable or field is already known
ID-dependent.
Based on the fix by @yepytons, with helper renaming and extra tests for
ordinary assignments, fields, macros, aliases, cv/ref cases, lambdas,
templates, concepts, and the #53777 range-for case.
Fix #52790
Fix #53777
Assisted by Codex.
---
.../altera/IdDependentBackwardBranchCheck.cpp | 34 ++--
.../altera/IdDependentBackwardBranchCheck.h | 20 +--
.../altera/id-dependent-backward-branch.cpp | 164 ++++++++++++++++++
3 files changed, 196 insertions(+), 22 deletions(-)
diff --git a/clang-tools-extra/clang-tidy/altera/IdDependentBackwardBranchCheck.cpp b/clang-tools-extra/clang-tidy/altera/IdDependentBackwardBranchCheck.cpp
index 58db9c9ecb62a..51f02b417bca6 100644
--- a/clang-tools-extra/clang-tidy/altera/IdDependentBackwardBranchCheck.cpp
+++ b/clang-tools-extra/clang-tidy/altera/IdDependentBackwardBranchCheck.cpp
@@ -138,7 +138,7 @@ void IdDependentBackwardBranchCheck::saveIdDepField(const Stmt *Statement,
Twine("assignment of ID-dependent field ") + Field->getNameAsString());
}
-void IdDependentBackwardBranchCheck::saveIdDepVarFromReference(
+void IdDependentBackwardBranchCheck::saveIdDepVarFromPotentialReference(
const DeclRefExpr *RefExpr, const MemberExpr *MemExpr,
const VarDecl *PotentialVar) {
// If the variable is already in IdDepVarsMap, ignore it.
@@ -151,20 +151,25 @@ void IdDependentBackwardBranchCheck::saveIdDepVarFromReference(
if (RefExpr) {
const auto *RefVar = dyn_cast<VarDecl>(RefExpr->getDecl());
// If variable isn't ID-dependent, but RefVar is.
- if (IdDepVarsMap.contains(RefVar))
+ if (IdDepVarsMap.contains(RefVar)) {
StringStream << "variable " << RefVar->getNameAsString();
+ IdDepVarsMap[PotentialVar] = IdDependencyRecord(
+ PotentialVar, PotentialVar->getBeginLoc(), Message);
+ return;
+ }
}
if (MemExpr) {
const auto *RefField = dyn_cast<FieldDecl>(MemExpr->getMemberDecl());
// If variable isn't ID-dependent, but RefField is.
- if (IdDepFieldsMap.contains(RefField))
+ if (IdDepFieldsMap.contains(RefField)) {
StringStream << "member " << RefField->getNameAsString();
+ IdDepVarsMap[PotentialVar] = IdDependencyRecord(
+ PotentialVar, PotentialVar->getBeginLoc(), Message);
+ }
}
- IdDepVarsMap[PotentialVar] =
- IdDependencyRecord(PotentialVar, PotentialVar->getBeginLoc(), Message);
}
-void IdDependentBackwardBranchCheck::saveIdDepFieldFromReference(
+void IdDependentBackwardBranchCheck::saveIdDepFieldFromPotentialReference(
const DeclRefExpr *RefExpr, const MemberExpr *MemExpr,
const FieldDecl *PotentialField) {
// If the field is already in IdDepFieldsMap, ignore it.
@@ -177,16 +182,21 @@ void IdDependentBackwardBranchCheck::saveIdDepFieldFromReference(
if (RefExpr) {
const auto *RefVar = dyn_cast<VarDecl>(RefExpr->getDecl());
// If field isn't ID-dependent, but RefVar is.
- if (IdDepVarsMap.contains(RefVar))
+ if (IdDepVarsMap.contains(RefVar)) {
StringStream << "variable " << RefVar->getNameAsString();
+ IdDepFieldsMap[PotentialField] = IdDependencyRecord(
+ PotentialField, PotentialField->getBeginLoc(), Message);
+ return;
+ }
}
if (MemExpr) {
const auto *RefField = dyn_cast<FieldDecl>(MemExpr->getMemberDecl());
- if (IdDepFieldsMap.contains(RefField))
+ if (IdDepFieldsMap.contains(RefField)) {
StringStream << "member " << RefField->getNameAsString();
+ IdDepFieldsMap[PotentialField] = IdDependencyRecord(
+ PotentialField, PotentialField->getBeginLoc(), Message);
+ }
}
- IdDepFieldsMap[PotentialField] = IdDependencyRecord(
- PotentialField, PotentialField->getBeginLoc(), Message);
}
IdDependentBackwardBranchCheck::LoopType
@@ -226,11 +236,11 @@ void IdDependentBackwardBranchCheck::check(
// Save variables assigned to values of Id-dependent variables and fields.
if ((RefExpr || MemExpr) && PotentialVar)
- saveIdDepVarFromReference(RefExpr, MemExpr, PotentialVar);
+ saveIdDepVarFromPotentialReference(RefExpr, MemExpr, PotentialVar);
// Save fields assigned to values of ID-dependent variables and fields.
if ((RefExpr || MemExpr) && PotentialField)
- saveIdDepFieldFromReference(RefExpr, MemExpr, PotentialField);
+ saveIdDepFieldFromPotentialReference(RefExpr, MemExpr, PotentialField);
// The second part of the callback deals with checking if a branch inside a
// loop is thread dependent.
diff --git a/clang-tools-extra/clang-tidy/altera/IdDependentBackwardBranchCheck.h b/clang-tools-extra/clang-tidy/altera/IdDependentBackwardBranchCheck.h
index 01a4ccdf1e717..e8e22cf8c992a 100644
--- a/clang-tools-extra/clang-tidy/altera/IdDependentBackwardBranchCheck.h
+++ b/clang-tools-extra/clang-tidy/altera/IdDependentBackwardBranchCheck.h
@@ -54,16 +54,16 @@ class IdDependentBackwardBranchCheck : public ClangTidyCheck {
/// Stores the location an ID-dependent field is created from a call to an ID
/// function in IdDepFieldsMap.
void saveIdDepField(const Stmt *Statement, const FieldDecl *Field);
- /// Stores the location an ID-dependent variable is created from a reference
- /// to another ID-dependent variable or field in IdDepVarsMap.
- void saveIdDepVarFromReference(const DeclRefExpr *RefExpr,
- const MemberExpr *MemExpr,
- const VarDecl *PotentialVar);
- /// Stores the location an ID-dependent field is created from a reference to
- /// another ID-dependent variable or field in IdDepFieldsMap.
- void saveIdDepFieldFromReference(const DeclRefExpr *RefExpr,
- const MemberExpr *MemExpr,
- const FieldDecl *PotentialField);
+ /// Stores the location an ID-dependent variable is created from a potential
+ /// reference to another ID-dependent variable or field in IdDepVarsMap.
+ void saveIdDepVarFromPotentialReference(const DeclRefExpr *RefExpr,
+ const MemberExpr *MemExpr,
+ const VarDecl *PotentialVar);
+ /// Stores the location an ID-dependent field is created from a potential
+ /// reference to another ID-dependent variable or field in IdDepFieldsMap.
+ void saveIdDepFieldFromPotentialReference(const DeclRefExpr *RefExpr,
+ const MemberExpr *MemExpr,
+ const FieldDecl *PotentialField);
/// Returns the loop type.
LoopType getLoopType(const Stmt *Loop);
diff --git a/clang-tools-extra/test/clang-tidy/checkers/altera/id-dependent-backward-branch.cpp b/clang-tools-extra/test/clang-tidy/checkers/altera/id-dependent-backward-branch.cpp
index 6137e6f929dc0..1a587bf1929bd 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/altera/id-dependent-backward-branch.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/altera/id-dependent-backward-branch.cpp
@@ -1,4 +1,35 @@
// RUN: %check_clang_tidy %s altera-id-dependent-backward-branch %t -- -header-filter=.* "--" -cl-std=CLC++1.0 -c
+// RUN: %check_clang_tidy -std=c++20-or-later %s altera-id-dependent-backward-branch %t -- -header-filter=.* --
+
+unsigned long get_local_id(unsigned);
+int foo(int);
+
+#ifndef __OPENCL_CPP_VERSION__
+namespace std {
+typedef decltype(sizeof(0)) size_t;
+
+template <class E>
+class initializer_list {
+public:
+ typedef const E *iterator;
+ typedef const E *const_iterator;
+ typedef size_t size_type;
+
+private:
+ iterator Begin;
+ size_type Size;
+
+ constexpr initializer_list(const_iterator Begin, size_type Size)
+ : Begin(Begin), Size(Size) {}
+
+public:
+ constexpr initializer_list() : Begin(nullptr), Size(0) {}
+ constexpr size_type size() const { return Size; }
+ constexpr const_iterator begin() const { return Begin; }
+ constexpr const_iterator end() const { return Begin + Size; }
+};
+} // namespace std
+#endif
void error() {
// ==== Conditional Expressions ====
@@ -64,6 +95,31 @@ void error() {
accumulator++;
}
+ struct {
+ int FieldFromVar;
+ int FieldFromField;
+ } InferredField;
+ InferredField.FieldFromVar = ThreadID * 2;
+ while (j < InferredField.FieldFromVar) {
+ // CHECK-NOTES: :[[@LINE-1]]:10: warning: backward branch (while loop) is ID-dependent due to member reference to 'FieldFromVar' and may cause performance degradation [altera-id-dependent-backward-branch]
+ // CHECK-NOTES: :[[@LINE-6]]:5: note: inferred assignment of ID-dependent member from ID-dependent variable ThreadID
+ accumulator++;
+ }
+
+ InferredField.FieldFromField = Example.IDDepField;
+ while (j < InferredField.FieldFromField) {
+ // CHECK-NOTES: :[[@LINE-1]]:10: warning: backward branch (while loop) is ID-dependent due to member reference to 'FieldFromField' and may cause performance degradation [altera-id-dependent-backward-branch]
+ // CHECK-NOTES: :[[@LINE-12]]:5: note: inferred assignment of ID-dependent member from ID-dependent member IDDepField
+ accumulator++;
+ }
+
+ int ThreadIDFromField = Example.IDDepField;
+ while (j < ThreadIDFromField) {
+ // CHECK-NOTES: :[[@LINE-1]]:10: warning: backward branch (while loop) is ID-dependent due to variable reference to 'ThreadIDFromField' and may cause performance degradation [altera-id-dependent-backward-branch]
+ // CHECK-NOTES: :[[@LINE-3]]:3: note: inferred assignment of ID-dependent value from ID-dependent member IDDepField
+ accumulator++;
+ }
+
// ==== Unused Inferred Assignments ====
int UnusedThreadID = Example.IDDepField; // OK: not used in any loops
@@ -79,6 +135,83 @@ void success() {
accumulator++;
}
}
+
+ // ==== Regression tests: ordinary values are not ID-dependent ====
+ int Value = 0;
+ int OtherValue = 1;
+ Value = OtherValue;
+ while (Value < OtherValue) {
+ ++Value;
+ }
+
+ int ComputedValue = foo(0);
+ while (Value < ComputedValue) {
+ ++Value;
+ }
+
+ int InferredValue = OtherValue * 2;
+ for (int i = 0; i < InferredValue; ++i) {
+ accumulator += i;
+ }
+
+ struct {
+ int OrdinaryField;
+ int OtherOrdinaryField;
+ } OrdinaryStruct;
+ OrdinaryStruct.OrdinaryField = foo(0);
+ while (Value < OrdinaryStruct.OrdinaryField) {
+ ++Value;
+ }
+
+ int FromOrdinaryField = OrdinaryStruct.OrdinaryField;
+ while (Value < FromOrdinaryField) {
+ ++Value;
+ }
+
+ OrdinaryStruct.OtherOrdinaryField = OtherValue;
+ while (Value < OrdinaryStruct.OtherOrdinaryField) {
+ ++Value;
+ }
+
+#define ASSIGN_ORDINARY_ID_DEPENDENT_TEST(lhs, rhs) lhs = rhs
+ int MacroAssigned = 0;
+ ASSIGN_ORDINARY_ID_DEPENDENT_TEST(MacroAssigned, OtherValue);
+ while (MacroAssigned < OtherValue) {
+ ++MacroAssigned;
+ }
+#undef ASSIGN_ORDINARY_ID_DEPENDENT_TEST
+
+ typedef int IntTypedef;
+ using IntAlias = int;
+ IntTypedef TypedefValue = OtherValue;
+ IntAlias AliasValue = TypedefValue;
+ while (AliasValue < OtherValue) {
+ ++AliasValue;
+ }
+
+ const int ConstValue = OtherValue;
+ volatile int VolatileValue = OtherValue;
+ int &ValueRef = Value;
+ ValueRef = ConstValue;
+ while (ValueRef < VolatileValue) {
+ ++ValueRef;
+ }
+
+ auto OrdinaryLambda = [&Value, OtherValue]() {
+ int LambdaValue = OtherValue;
+ while (Value < LambdaValue) {
+ ++Value;
+ }
+ };
+ OrdinaryLambda();
+
+#ifndef __OPENCL_CPP_VERSION__
+ for (int ChunkSize : {1, 2, 3}) {
+ for (int i = 0; i < 500; i += ChunkSize) {
+ static_cast<void>(i);
+ }
+ }
+#endif
}
template<char... STOP>
@@ -86,3 +219,34 @@ void gh55408(char const input[], int pos) {
while (((input[pos] != STOP) && ...));
}
+template <typename T>
+void ordinary_template(T Value) {
+ T OtherValue = Value;
+ while (OtherValue < Value) {
+ ++OtherValue;
+ }
+}
+
+template <typename T>
+void dependent_template() {
+ T Value = T();
+ T OtherValue = Value;
+ while (OtherValue < Value) {
+ ++OtherValue;
+ }
+}
+
+#ifndef __OPENCL_CPP_VERSION__
+template <typename T>
+concept HasValue = requires(T Value) {
+ Value + 1;
+};
+
+template <HasValue T>
+void ordinary_constrained_template(T Value) {
+ T OtherValue = Value;
+ while (OtherValue < Value) {
+ ++OtherValue;
+ }
+}
+#endif
>From 51bd3d999269ed04173ea01e6bf9442ae1c14d6f Mon Sep 17 00:00:00 2001
From: Daniil Dudkin <unterumarmung at yandex.ru>
Date: Sun, 31 May 2026 16:11:28 +0300
Subject: [PATCH 2/4] Add release notes
---
clang-tools-extra/docs/ReleaseNotes.rst | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index e40150f3603da..a385c69ca0714 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -332,6 +332,11 @@ New check aliases
Changes in existing checks
^^^^^^^^^^^^^^^^^^^^^^^^^^
+- Improved :doc:`altera-id-dependent-backward-branch
+ <clang-tidy/checks/altera/id-dependent-backward-branch>` check by fixing false
+ positives when ordinary variable or field assignments are used in loop
+ conditions.
+
- Improved :doc:`bugprone-argument-comment
<clang-tidy/checks/bugprone/argument-comment>`:
>From 5c251975d8de8f5ea8e65011866fdac13fb5f644 Mon Sep 17 00:00:00 2001
From: Daniil Dudkin <unterumarmung at yandex.ru>
Date: Fri, 5 Jun 2026 23:11:28 +0300
Subject: [PATCH 3/4] Add blank like
---
.../clang-tidy/checkers/altera/id-dependent-backward-branch.cpp | 1 +
1 file changed, 1 insertion(+)
diff --git a/clang-tools-extra/test/clang-tidy/checkers/altera/id-dependent-backward-branch.cpp b/clang-tools-extra/test/clang-tidy/checkers/altera/id-dependent-backward-branch.cpp
index 1a587bf1929bd..fa187bf025e1e 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/altera/id-dependent-backward-branch.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/altera/id-dependent-backward-branch.cpp
@@ -99,6 +99,7 @@ void error() {
int FieldFromVar;
int FieldFromField;
} InferredField;
+
InferredField.FieldFromVar = ThreadID * 2;
while (j < InferredField.FieldFromVar) {
// CHECK-NOTES: :[[@LINE-1]]:10: warning: backward branch (while loop) is ID-dependent due to member reference to 'FieldFromVar' and may cause performance degradation [altera-id-dependent-backward-branch]
>From 718bf8a2748507b94b26eca1431729e814298d9b Mon Sep 17 00:00:00 2001
From: Daniil Dudkin <unterumarmung at yandex.ru>
Date: Sat, 6 Jun 2026 21:20:05 +0300
Subject: [PATCH 4/4] fix tests
---
.../checkers/altera/id-dependent-backward-branch.cpp | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/clang-tools-extra/test/clang-tidy/checkers/altera/id-dependent-backward-branch.cpp b/clang-tools-extra/test/clang-tidy/checkers/altera/id-dependent-backward-branch.cpp
index fa187bf025e1e..c3eb6b6ea541a 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/altera/id-dependent-backward-branch.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/altera/id-dependent-backward-branch.cpp
@@ -103,14 +103,14 @@ void error() {
InferredField.FieldFromVar = ThreadID * 2;
while (j < InferredField.FieldFromVar) {
// CHECK-NOTES: :[[@LINE-1]]:10: warning: backward branch (while loop) is ID-dependent due to member reference to 'FieldFromVar' and may cause performance degradation [altera-id-dependent-backward-branch]
- // CHECK-NOTES: :[[@LINE-6]]:5: note: inferred assignment of ID-dependent member from ID-dependent variable ThreadID
+ // CHECK-NOTES: :[[@LINE-7]]:5: note: inferred assignment of ID-dependent member from ID-dependent variable ThreadID
accumulator++;
}
InferredField.FieldFromField = Example.IDDepField;
while (j < InferredField.FieldFromField) {
// CHECK-NOTES: :[[@LINE-1]]:10: warning: backward branch (while loop) is ID-dependent due to member reference to 'FieldFromField' and may cause performance degradation [altera-id-dependent-backward-branch]
- // CHECK-NOTES: :[[@LINE-12]]:5: note: inferred assignment of ID-dependent member from ID-dependent member IDDepField
+ // CHECK-NOTES: :[[@LINE-13]]:5: note: inferred assignment of ID-dependent member from ID-dependent member IDDepField
accumulator++;
}
More information about the cfe-commits
mailing list