[clang] [clang][CFG] Fix range-for iterator initialization order (PR #234467)
via cfe-commits
cfe-commits at lists.llvm.org
Wed Oct 21 19:57:34 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang-static-analyzer-1
@llvm/pr-subscribers-clang-analysis
Author: Taufiq Rahman (Inconnu08)
<details>
<summary>Changes</summary>
The CFG builder adds statements in reverse execution order. Add the range-for end declaration before the begin declaration so that the resulting CFG initializes begin before end.
Add a regression covering distinct iterator and sentinel types with non-trivial destructors, checking initialization and reverse destruction order with rich constructors both enabled and disabled. Update the affected CFG dump expectations.
Validation: all 28 selected CFG and range-for Analysis tests passed. The new regression also fails with the original implementation.
Fixes #<!-- -->233806
---
Full diff: https://github.com/llvm/llvm-project/pull/234467.diff
4 Files Affected:
- (modified) clang/lib/Analysis/CFG.cpp (+1-1)
- (modified) clang/test/Analysis/auto-obj-dtors-cfg-output.cpp (+6-6)
- (added) clang/test/Analysis/cfg-range-for-init-order.cpp (+35)
- (modified) clang/test/Analysis/scopes-cfg-output.cpp (+12-10)
``````````diff
diff --git a/clang/lib/Analysis/CFG.cpp b/clang/lib/Analysis/CFG.cpp
index 556e08f5c1df80..63c8d62c5808e1 100644
--- a/clang/lib/Analysis/CFG.cpp
+++ b/clang/lib/Analysis/CFG.cpp
@@ -4989,8 +4989,8 @@ CFGBlock *CFGBuilder::VisitCXXForRangeStmt(CXXForRangeStmt *S) {
// Add the initialization statements.
Block = createBlock();
- addStmt(S->getBeginStmt());
addStmt(S->getEndStmt());
+ addStmt(S->getBeginStmt());
CFGBlock *Head = addStmt(S->getRangeStmt());
if (S->getInit())
Head = addStmt(S->getInit());
diff --git a/clang/test/Analysis/auto-obj-dtors-cfg-output.cpp b/clang/test/Analysis/auto-obj-dtors-cfg-output.cpp
index 96b9a5508cc084..4a39b70f8ab335 100644
--- a/clang/test/Analysis/auto-obj-dtors-cfg-output.cpp
+++ b/clang/test/Analysis/auto-obj-dtors-cfg-output.cpp
@@ -1117,12 +1117,12 @@ void test_for_implicit_scope() {
// CHECK-NEXT: 3: auto &&__range1 = nums;
// CHECK-NEXT: 4: __range1
// CHECK-NEXT: 5: [B4.4] (ImplicitCastExpr, ArrayToPointerDecay, int *)
-// CHECK-NEXT: 6: 4{{L*}}
-// CHECK-NEXT: 7: [B4.5] + [B4.6]
-// CHECK-NEXT: 8: auto __end1 = __range1 + 4{{L*}};
-// CHECK-NEXT: 9: __range1
-// CHECK-NEXT: 10: [B4.9] (ImplicitCastExpr, ArrayToPointerDecay, int *)
-// CHECK-NEXT: 11: auto __begin1 = __range1;
+// CHECK-NEXT: 6: auto __begin1 = __range1;
+// CHECK-NEXT: 7: __range1
+// CHECK-NEXT: 8: [B4.7] (ImplicitCastExpr, ArrayToPointerDecay, int *)
+// CHECK-NEXT: 9: 4{{L*}}
+// CHECK-NEXT: 10: [B4.8] + [B4.9]
+// CHECK-NEXT: 11: auto __end1 = __range1 + 4{{L*}};
// CHECK-NEXT: Preds (1): B5
// CHECK-NEXT: Succs (1): B1
// CHECK: [B0 (EXIT)]
diff --git a/clang/test/Analysis/cfg-range-for-init-order.cpp b/clang/test/Analysis/cfg-range-for-init-order.cpp
new file mode 100644
index 00000000000000..2bbca5c1fd83a7
--- /dev/null
+++ b/clang/test/Analysis/cfg-range-for-init-order.cpp
@@ -0,0 +1,35 @@
+// RUN: %clang_analyze_cc1 -std=c++17 -analyzer-checker=debug.DumpCFG -analyzer-config cfg-rich-constructors=false %s > %t 2>&1
+// RUN: FileCheck --input-file=%t %s
+// RUN: %clang_analyze_cc1 -std=c++17 -analyzer-checker=debug.DumpCFG -analyzer-config cfg-rich-constructors=true %s > %t 2>&1
+// RUN: FileCheck --input-file=%t %s
+
+struct Begin {
+ ~Begin();
+};
+
+struct End {
+ ~End();
+};
+
+bool operator!=(const Begin &, const End &);
+void operator++(Begin &);
+int &operator*(const Begin &);
+
+struct Range {
+ Begin begin();
+ End end();
+};
+
+// The cleanup block appears before the initialization block in the dump.
+// Iterators must initialize begin before end and be destroyed in reverse order.
+//
+// CHECK-LABEL: void range_for_init_order(
+// CHECK: .~End() (Implicit destructor)
+// CHECK: .~Begin() (Implicit destructor)
+// CHECK: auto &&__range{{[0-9]*}} = range;
+// CHECK: auto __begin{{[0-9]*}} = {{.*}}.begin();
+// CHECK: auto __end{{[0-9]*}} = {{.*}}.end();
+void range_for_init_order(Range range) {
+ for (int &value : range)
+ (void)value;
+}
\ No newline at end of file
diff --git a/clang/test/Analysis/scopes-cfg-output.cpp b/clang/test/Analysis/scopes-cfg-output.cpp
index ac8ff1179a9945..13c6c42555ffae 100644
--- a/clang/test/Analysis/scopes-cfg-output.cpp
+++ b/clang/test/Analysis/scopes-cfg-output.cpp
@@ -890,7 +890,7 @@ void test_for_compound_and_break() {
// CHECK-NEXT: 3: __end1
// CHECK-NEXT: 4: [B2.3] (ImplicitCastExpr, LValueToRValue, A *)
// CHECK-NEXT: 5: [B2.2] != [B2.4]
-// CHECK-NEXT: T: for (auto &i : [B5.4])
+// CHECK-NEXT: T: for (auto &i : [B5.5])
// CHECK: [B4.11];
// CHECK: Preds (2): B3 B5
// CHECK-NEXT: Succs (2): B4 B1
@@ -918,17 +918,19 @@ void test_for_compound_and_break() {
// CHECK-NEXT: 1: CFGScopeBegin(a)
// CHECK-NEXT: 2: (CXXConstructExpr, [B5.3], A[10])
// CHECK-NEXT: 3: A a[10];
-// CHECK-NEXT: 4: a
-// CHECK-NEXT: 5: auto &&__range1 = a;
-// CHECK-NEXT: 6: CFGScopeBegin(__end1)
-// CHECK-NEXT: 7: __range1
-// CHECK-NEXT: 8: [B5.7] (ImplicitCastExpr, ArrayToPointerDecay, A *)
-// CHECK-NEXT: 9: 10
-// CHECK-NEXT: 10: [B5.8] + [B5.9]
-// CHECK-NEXT: 11: auto __end1 = __range1 + 10
+// CHECK-NEXT: 4: CFGScopeBegin(__range1)
+// CHECK-NEXT: 5: a
+// CHECK-NEXT: 6: auto &&__range1 = a;
+// CHECK-NEXT: 7: CFGScopeBegin(__begin1)
+// CHECK-NEXT: 8: __range1
+// CHECK-NEXT: 9: [B5.8] (ImplicitCastExpr, ArrayToPointerDecay, A *)
+// CHECK-NEXT: 10: auto __begin1 = __range1;
+// CHECK-NEXT: 11: CFGScopeBegin(__end1)
// CHECK-NEXT: 12: __range1
// CHECK-NEXT: 13: [B5.12] (ImplicitCastExpr, ArrayToPointerDecay, A *)
-// CHECK-NEXT: 14: auto __begin1 = __range1;
+// CHECK-NEXT: 14: 10{{L*}}
+// CHECK-NEXT: 15: [B5.13] + [B5.14]
+// CHECK-NEXT: 16: auto __end1 = __range1 + 10{{L*}};
// CHECK-NEXT: Preds (1): B6
// CHECK-NEXT: Succs (1): B2
// CHECK: [B0 (EXIT)]
``````````
</details>
https://github.com/llvm/llvm-project/pull/234467
More information about the cfe-commits
mailing list