https://github.com/Inconnu08 created https://github.com/llvm/llvm-project/pull/234467
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 >From 12df8eb2cd07d66ddc25b23bd8b8bdca4ca9004f 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 | 2 +- .../Analysis/auto-obj-dtors-cfg-output.cpp | 12 +++---- .../Analysis/cfg-range-for-init-order.cpp | 35 +++++++++++++++++++ clang/test/Analysis/scopes-cfg-output.cpp | 22 ++++++------ 4 files changed, 54 insertions(+), 17 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..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)] _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
