https://github.com/Inconnu08 updated 
https://github.com/llvm/llvm-project/pull/234467

>From a3be0abd1d651f16b1b027107f3022f086f25c0c Mon Sep 17 00:00:00 2001
From: inconnu08 <[email protected]>
Date: Wed, 21 Oct 2026 22:51:43 -0400
Subject: [PATCH] [clang][CFG] Fix range-for iterator initialization order

---
 clang/lib/Analysis/CFG.cpp                    |  5 +-
 .../Analysis/auto-obj-dtors-cfg-output.cpp    | 12 ++---
 .../Analysis/cfg-range-for-init-order.cpp     | 51 +++++++++++++++++++
 clang/test/Analysis/scopes-cfg-output.cpp     | 22 ++++----
 .../Sema/LifetimeSafety/invalidations.cpp     |  4 +-
 5 files changed, 74 insertions(+), 20 deletions(-)
 create mode 100644 clang/test/Analysis/cfg-range-for-init-order.cpp

diff --git a/clang/lib/Analysis/CFG.cpp b/clang/lib/Analysis/CFG.cpp
index 556e08f5c1df80..4d6bf0ba8bd9b5 100644
--- a/clang/lib/Analysis/CFG.cpp
+++ b/clang/lib/Analysis/CFG.cpp
@@ -4987,10 +4987,11 @@ CFGBlock 
*CFGBuilder::VisitCXXForRangeStmt(CXXForRangeStmt *S) {
   // false branch).
   addSuccessor(ConditionBlock, KnownVal.isTrue() ? nullptr : LoopSuccessor);
 
-  // Add the initialization statements.
+  // Add the initialization statements in reverse execution order so that
+  // begin is initialized before end.
   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..eb93ffc53d5273
--- /dev/null
+++ b/clang/test/Analysis/cfg-range-for-init-order.cpp
@@ -0,0 +1,51 @@
+// RUN: %clang_analyze_cc1 -std=c++17 -analyzer-checker=debug.DumpCFG 
-analyzer-config cfg-rich-constructors=false %s 2>&1 | FileCheck %s
+// RUN: %clang_analyze_cc1 -std=c++17 -analyzer-checker=debug.DumpCFG 
-analyzer-config cfg-rich-constructors=true %s 2>&1 | FileCheck %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:      [B1]
+// CHECK-NEXT:   1: [B5.12].~End() (Implicit destructor)
+// CHECK-NEXT:   2: [B5.7].~Begin() (Implicit destructor)
+// CHECK-NEXT:   Preds (1): B2
+// CHECK-NEXT:   Succs (1): B0
+// CHECK-EMPTY:
+//
+// CHECK:      [B5]
+// CHECK-NEXT:   1: range
+// CHECK-NEXT:   2: auto &&__range1 = range;
+// CHECK-NEXT:   3: __range1
+// CHECK-NEXT:   4: [B5.3].begin
+// CHECK-NEXT:   5: [B5.4](){{.*}}
+// CHECK-NEXT:   6: [B5.5] (BindTemporary)
+// CHECK-NEXT:   7: auto __begin1 = __range1.begin();
+// CHECK-NEXT:   8: __range1
+// CHECK-NEXT:   9: [B5.8].end
+// CHECK-NEXT:  10: [B5.9](){{.*}}
+// CHECK-NEXT:  11: [B5.10] (BindTemporary)
+// CHECK-NEXT:  12: auto __end1 = __range1.end();
+// CHECK-NEXT:   Preds (1): B6
+// CHECK-NEXT:   Succs (1): B2
+// CHECK-EMPTY:
+void range_for_init_order(Range range) {
+  for (int &value : range)
+    (void)value;
+}
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)]
diff --git a/clang/test/Sema/LifetimeSafety/invalidations.cpp 
b/clang/test/Sema/LifetimeSafety/invalidations.cpp
index f96e8259b64ebc..4bed13b2cc844d 100644
--- a/clang/test/Sema/LifetimeSafety/invalidations.cpp
+++ b/clang/test/Sema/LifetimeSafety/invalidations.cpp
@@ -164,7 +164,7 @@ void 
NoIteratorInvalidationInAWhileLoopErase(std::unordered_map<int, int> mp) {
 void IteratorInvalidationInAForeachLoop(std::vector<int> v) {
   for (int& x : v) { // expected-warning {{parameter 'v' is later 
invalidated}} \
                      // expected-note {{later used here}} \
-                     // expected-note {{result of call to 'end' aliases the 
storage of parameter 'v'}}
+                     // expected-note {{result of call to 'begin' aliases the 
storage of parameter 'v'}}
     if (x % 2 == 0) {
       v.erase(std::find(v.begin(), v.end(), 1)); // expected-note {{parameter 
'v' is invalidated here}}
     }
@@ -556,7 +556,7 @@ void IteratorFromPointerToContainerIsInvalidated() {
 void ChangingRegionOwnedByContainerIsOk() {
   std::vector<std::string> subdirs;
   for (std::string& path : subdirs) // expected-warning {{local variable 
'subdirs' is later invalidated}} expected-note {{later used here}} \
-                                    // expected-note {{result of call to 'end' 
aliases the storage of local variable 'subdirs'}}
+                                    // expected-note {{result of call to 
'begin' aliases the storage of local variable 'subdirs'}}
     path = std::string();           // expected-note {{local variable 
'subdirs' is invalidated here}}
 }
 

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to