Author: Marco Elver
Date: 2026-08-19T11:12:34+02:00
New Revision: 3c2eaf3920a8320930a8334b328a040ec0402091

URL: 
https://github.com/llvm/llvm-project/commit/3c2eaf3920a8320930a8334b328a040ec0402091
DIFF: 
https://github.com/llvm/llvm-project/commit/3c2eaf3920a8320930a8334b328a040ec0402091.diff

LOG: Thread Safety Analysis: Treat blocks constructing noreturn temporaries as 
non-returning (#215691)

When a temporary object with a [[noreturn]] destructor is constructed
conditionally (e.g., inside a branch of a ternary operator), Clang's CFG
inserts a TemporaryDtorsBranch decision block before dispatching to the
destructor block.

Thread Safety Analysis previously treated this block as a generic join
point. Because the path through the temporary construction terminates in
the destructor, and dropped held locks on the continuation path. This
premature join resulted in spurious lockset mismatch warnings

Teach neverReturns() to recognize when a block's single successor is a
TemporaryDtorsBranch whose temporary was constructed in that block and
whose destructor branch is noreturn.

Assisted-by: Antigravity:gemini

Added: 
    

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

Removed: 
    


################################################################################
diff  --git a/clang/lib/Analysis/ThreadSafety.cpp 
b/clang/lib/Analysis/ThreadSafety.cpp
index 5e656a3dbc1cb..192d6a2e1b1d1 100644
--- a/clang/lib/Analysis/ThreadSafety.cpp
+++ b/clang/lib/Analysis/ThreadSafety.cpp
@@ -2815,6 +2815,30 @@ static bool neverReturns(const CFGBlock *B) {
     if (isa<CXXThrowExpr>(S->getStmt()))
       return true;
   }
+
+  // If B constructed a temporary whose destructor is noreturn, control 
entering
+  // the decision block will always branch to the non-returning destructor.
+  if (B->succ_size() == 1) {
+    if (const CFGBlock *Succ = *B->succ_begin()) {
+      if (Succ->getTerminator().isTemporaryDtorsBranch() &&
+          Succ->succ_size() == 2) {
+        // The decision block's terminator is the CXXBindTemporaryExpr; if B
+        // bound this temporary, entering Succ from B takes the true (dtor)
+        // edge; otherwise it takes the false (alternative dtor / continuation)
+        // edge.
+        const Stmt *Term = Succ->getTerminatorStmt();
+        bool Bound = llvm::any_of(*B, [Term](const CFGElement &CE) {
+          auto CS = CE.getAs<CFGStmt>();
+          return CS && CS->getStmt() == Term;
+        });
+        if (const auto *Next =
+                (Bound ? *Succ->succ_begin() : *(Succ->succ_begin() + 1))
+                    .getReachableBlock())
+          return neverReturns(Next);
+      }
+    }
+  }
+
   return false;
 }
 

diff  --git a/clang/test/SemaCXX/warn-thread-safety-analysis.cpp 
b/clang/test/SemaCXX/warn-thread-safety-analysis.cpp
index 54b2c70940c24..a58b0d24e4239 100644
--- a/clang/test/SemaCXX/warn-thread-safety-analysis.cpp
+++ b/clang/test/SemaCXX/warn-thread-safety-analysis.cpp
@@ -4782,9 +4782,24 @@ namespace UnreachableExitTest {
 class FemmeFatale {
 public:
   FemmeFatale();
+  template <typename T>
+  FemmeFatale& operator<<(const T&) { return *this; }
   ~FemmeFatale() __attribute__((noreturn));
 };
 
+class NonFatal {
+public:
+  NonFatal();
+  template <typename T>
+  NonFatal& operator<<(const T&) { return *this; }
+  ~NonFatal();
+};
+
+struct Voidify {
+  template <typename T>
+  void operator&&(T&&) const&&;
+};
+
 void exitNow() __attribute__((noreturn));
 void exitDestruct(const MyString& ms) __attribute__((noreturn));
 
@@ -4813,6 +4828,46 @@ void test4() EXCLUSIVE_LOCKS_REQUIRED(fatalmu_) {
   exitDestruct("foo");
 }
 
+void test5() {
+  fatalmu_.TryLock() ? (void)0 : (void)FemmeFatale();
+  fatalmu_.Unlock();
+}
+
+void test6() {
+  fatalmu_.TryLock() ? (void)0 : Voidify() && FemmeFatale() << "foo";
+  fatalmu_.Unlock();
+}
+
+void test7() {
+  fatalmu_.TryLock() ? (void)0 : Voidify() && NonFatal() << "foo"; // \
+    // expected-warning {{mutex 'fatalmu_' is not held on every path through 
here}} \
+    // expected-note {{mutex acquired here}}
+  fatalmu_.Unlock(); // expected-warning {{releasing mutex 'fatalmu_' that was 
not held}}
+}
+
+void test8() EXCLUSIVE_LOCKS_REQUIRED(fatalmu_) {
+  c ? (void)0 : (void)FemmeFatale();
+}
+
+void test9() EXCLUSIVE_LOCKS_REQUIRED(fatalmu_) {
+  c ? (void)FemmeFatale() : (void)0;
+}
+
+void test10() {
+  !fatalmu_.TryLock() ? (void)FemmeFatale() : (void)0;
+  fatalmu_.Unlock();
+}
+
+void test11() {
+  !fatalmu_.TryLock() ? (void)FemmeFatale() : (void)NonFatal();
+  fatalmu_.Unlock();
+}
+
+void test12() {
+  fatalmu_.TryLock() ? (void)NonFatal() : (void)FemmeFatale();
+  fatalmu_.Unlock();
+}
+
 }   // end namespace UnreachableExitTest
 
 


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

Reply via email to