Author: Marco Elver
Date: 2026-01-14T16:06:50+01:00
New Revision: 220bed3de71e152e519085ec8815feb9203ab58c

URL: 
https://github.com/llvm/llvm-project/commit/220bed3de71e152e519085ec8815feb9203ab58c
DIFF: 
https://github.com/llvm/llvm-project/commit/220bed3de71e152e519085ec8815feb9203ab58c.diff

LOG: [Analysis] Fix missing destructor in CFG for C++20 range-for 
init-statement (#175859)

In C++20 range-based for loops, variables declared in the init-statement
were not correctly added to the CFG builder's local scope. Consequently,
implicit destructors for these variables were missing from the CFG.

This caused analyses relying on the CFG to incorrectly model the
lifetime of these variables. Specifically, Thread Safety Analysis
reported false positives for RAII locks declared in the init-statement.

Fix it by calling addLocalScopeForStmt for the init-statement in
CFGBuilder::VisitCXXForRangeStmt. This ensures destructors are correctly
inserted into the CFG.

Fixes: https://github.com/abseil/abseil-cpp/issues/1901

Added: 
    clang/test/Analysis/cxx20-range-for-cfg.cpp

Modified: 
    clang/lib/Analysis/CFG.cpp
    clang/test/SemaCXX/warn-thread-safety-analysis.cpp

Removed: 
    


################################################################################
diff  --git a/clang/lib/Analysis/CFG.cpp b/clang/lib/Analysis/CFG.cpp
index f8a2afec79700..a9c7baa00543c 100644
--- a/clang/lib/Analysis/CFG.cpp
+++ b/clang/lib/Analysis/CFG.cpp
@@ -4807,7 +4807,10 @@ CFGBlock 
*CFGBuilder::VisitCXXForRangeStmt(CXXForRangeStmt *S) {
   // Save local scope position before the addition of the implicit variables.
   SaveAndRestore save_scope_pos(ScopePos);
 
-  // Create local scopes and destructors for range, begin and end variables.
+  // Create local scopes and destructors for init, range, begin and end
+  // variables.
+  if (Stmt *Init = S->getInit())
+    addLocalScopeForStmt(Init);
   if (Stmt *Range = S->getRangeStmt())
     addLocalScopeForStmt(Range);
   if (Stmt *Begin = S->getBeginStmt())

diff  --git a/clang/test/Analysis/cxx20-range-for-cfg.cpp 
b/clang/test/Analysis/cxx20-range-for-cfg.cpp
new file mode 100644
index 0000000000000..990c1aa75c1b7
--- /dev/null
+++ b/clang/test/Analysis/cxx20-range-for-cfg.cpp
@@ -0,0 +1,41 @@
+// RUN: %clang_analyze_cc1 -analyzer-checker=debug.DumpCFG -std=c++20 %s > %t 
2>&1
+// RUN: FileCheck --input-file=%t %s
+
+struct A { ~A(); };
+void range_for_init() {
+  for (A a; int x : (int[]){1, 2, 3}) {}
+}
+
+// CHECK-LABEL: void range_for_init()
+// CHECK:       [B6 (ENTRY)]
+// CHECK-NEXT:    Succs (1): B5
+
+// CHECK:       [B1]
+// CHECK-NEXT:    1: [B5.2].~A() (Implicit destructor)
+// CHECK-NEXT:    Preds (1): B2
+// CHECK-NEXT:    Succs (1): B0
+
+// CHECK:       [B2]
+// CHECK-NEXT:    1: __begin1
+// CHECK-NEXT:    2: [B2.1] (ImplicitCastExpr, LValueToRValue, int *)
+// CHECK-NEXT:    3: __end1
+// CHECK-NEXT:    4: [B2.3] (ImplicitCastExpr, LValueToRValue, int *)
+// CHECK-NEXT:    5: [B2.2] != [B2.4]
+// CHECK-NEXT:    T: for (A a; int x : [B5.8]) {
+// CHECK-NEXT:  }
+// CHECK:         Preds (2): B3 B5
+// CHECK-NEXT:    Succs (2): B4 B1
+
+// CHECK:       [B5]
+// CHECK-NEXT:    1:  (CXXConstructExpr, [B5.2], A)
+// CHECK-NEXT:    2: A a;
+// CHECK-NEXT:    3: 1
+// CHECK-NEXT:    4: 2
+// CHECK-NEXT:    5: 3
+// CHECK-NEXT:    6: {[B5.3], [B5.4], [B5.5]}
+// CHECK-NEXT:    7: (int[3])[B5.6]
+// CHECK-NEXT:    8: [B5.7]
+// CHECK-NEXT:    9: auto &&__range1 = (int[3]){1, 2, 3};
+
+// CHECK:       [B0 (EXIT)]
+// CHECK-NEXT:    Preds (1): B1

diff  --git a/clang/test/SemaCXX/warn-thread-safety-analysis.cpp 
b/clang/test/SemaCXX/warn-thread-safety-analysis.cpp
index d5ae2eedc2bd7..d9efa745b7d59 100644
--- a/clang/test/SemaCXX/warn-thread-safety-analysis.cpp
+++ b/clang/test/SemaCXX/warn-thread-safety-analysis.cpp
@@ -1,7 +1,7 @@
 // RUN: %clang_cc1 -fsyntax-only -verify -std=c++11 -Wthread-safety 
-Wthread-safety-pointer -Wthread-safety-beta -Wno-thread-safety-negative 
-fcxx-exceptions -DUSE_CAPABILITY=0 %s
 // RUN: %clang_cc1 -fsyntax-only -verify -std=c++11 -Wthread-safety 
-Wthread-safety-pointer -Wthread-safety-beta -Wno-thread-safety-negative 
-fcxx-exceptions -DUSE_CAPABILITY=1 %s
-// RUN: %clang_cc1 -fsyntax-only -verify -std=c++17 -Wthread-safety 
-Wthread-safety-pointer -Wthread-safety-beta -Wno-thread-safety-negative 
-fcxx-exceptions -DUSE_CAPABILITY=0 %s
-// RUN: %clang_cc1 -fsyntax-only -verify -std=c++17 -Wthread-safety 
-Wthread-safety-pointer -Wthread-safety-beta -Wno-thread-safety-negative 
-fcxx-exceptions -DUSE_CAPABILITY=1 %s
+// RUN: %clang_cc1 -fsyntax-only -verify -std=c++20 -Wthread-safety 
-Wthread-safety-pointer -Wthread-safety-beta -Wno-thread-safety-negative 
-fcxx-exceptions -DUSE_CAPABILITY=0 %s
+// RUN: %clang_cc1 -fsyntax-only -verify -std=c++20 -Wthread-safety 
-Wthread-safety-pointer -Wthread-safety-beta -Wno-thread-safety-negative 
-fcxx-exceptions -DUSE_CAPABILITY=1 %s
 
 // FIXME: should also run  %clang_cc1 -fsyntax-only -verify -Wthread-safety 
-std=c++11 -Wc++98-compat %s
 // FIXME: should also run  %clang_cc1 -fsyntax-only -verify -Wthread-safety %s
@@ -1847,6 +1847,14 @@ struct TestScopedLockable {
     a = b + 1;
     b = a + 1;
   }
+
+#if __cplusplus >= 202002L
+  void rangeForLoopInitializer() {
+    for (MutexLock lock{&mu1}; int& x : (int[]){1, 2, 3}) {
+      a = 42;
+    }
+  }
+#endif
 };
 
 namespace test_function_param_lock_unlock {


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

Reply via email to