https://github.com/steakhal updated 
https://github.com/llvm/llvm-project/pull/208729

From 72fc83a3e77a5fd0e957af7ac767bd58d17c947f Mon Sep 17 00:00:00 2001
From: Balazs Benics <[email protected]>
Date: Tue, 16 Jun 2026 15:24:51 +0100
Subject: [PATCH 1/2] [analyzer] Prevent inlining RAII ctors/dtors

BlockInCriticalSectionChecker registers the pre-call for the RAII ctors
and Dtors - and also the raw 'lock' and 'unlock' handlers.

However, pre-call does not prevent inlining. This means that (in the
likely case of) that the body is present, the analyzer will model the
effect of the lock twice. This happens on libc++ unique_lock.

We really should have eval-called the ctor/dtor to avoid the inlining of
those, but here we are.

rdar://175814310
---
 .../BlockInCriticalSectionChecker.cpp         | 34 +++++++++---
 .../block-in-critical-section-raii.cpp        | 53 +++++++++++++++++++
 2 files changed, 81 insertions(+), 6 deletions(-)
 create mode 100644 clang/test/Analysis/block-in-critical-section-raii.cpp

diff --git 
a/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp 
b/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp
index 03c576270797b..ad26324cdab76 100644
--- a/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp
@@ -212,7 +212,8 @@ class SuppressNonBlockingStreams : public 
BugReporterVisitor {
   }
 };
 
-class BlockInCriticalSectionChecker : public Checker<check::PostCall> {
+class BlockInCriticalSectionChecker
+    : public Checker<check::PostCall, eval::Call> {
 private:
   const std::array<MutexDescriptor, 9> MutexDescriptors{
       // NOTE: There are standard library implementations where some methods
@@ -276,6 +277,9 @@ class BlockInCriticalSectionChecker : public 
Checker<check::PostCall> {
   /// Process lock.
   /// Process blocking functions (sleep, getc, fgets, read, recv)
   void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
+
+  // Process RAII lock guard object ctors/dtors.
+  bool evalCall(const CallEvent &Call, CheckerContext &C) const;
 };
 
 } // end anonymous namespace
@@ -373,15 +377,33 @@ void BlockInCriticalSectionChecker::checkPostCall(const 
CallEvent &Call,
                                                   CheckerContext &C) const {
   if (isBlockingInCritSection(Call, C)) {
     reportBlockInCritSection(Call, C);
-  } else if (std::optional<MutexDescriptor> LockDesc =
-                 checkDescriptorMatch(Call, C, /*IsLock=*/true)) {
-    handleLock(*LockDesc, Call, C);
-  } else if (std::optional<MutexDescriptor> UnlockDesc =
-                 checkDescriptorMatch(Call, C, /*IsLock=*/false)) {
+    return;
+  }
+
+  if (std::optional<MutexDescriptor> LockDesc =
+          checkDescriptorMatch(Call, C, /*IsLock=*/true)) {
+    if (!std::holds_alternative<RAIIMutexDescriptor>(*LockDesc))
+      handleLock(*LockDesc, Call, C);
+    return;
+  }
+  if (std::optional<MutexDescriptor> UnlockDesc =
+          checkDescriptorMatch(Call, C, /*IsLock=*/false)) {
     handleUnlock(*UnlockDesc, Call, C);
   }
 }
 
+bool BlockInCriticalSectionChecker::evalCall(const CallEvent &Call,
+                                             CheckerContext &C) const {
+  if (std::optional<MutexDescriptor> LockDesc =
+          checkDescriptorMatch(Call, C, /*IsLock=*/true)) {
+    if (std::holds_alternative<RAIIMutexDescriptor>(*LockDesc)) {
+      handleLock(*LockDesc, Call, C);
+      return true;
+    }
+  }
+  return false;
+}
+
 void BlockInCriticalSectionChecker::reportBlockInCritSection(
     const CallEvent &Call, CheckerContext &C) const {
   ExplodedNode *ErrNode = C.generateNonFatalErrorNode(C.getState());
diff --git a/clang/test/Analysis/block-in-critical-section-raii.cpp 
b/clang/test/Analysis/block-in-critical-section-raii.cpp
new file mode 100644
index 0000000000000..4bc4c94c74259
--- /dev/null
+++ b/clang/test/Analysis/block-in-critical-section-raii.cpp
@@ -0,0 +1,53 @@
+// RUN: %clang_analyze_cc1 -verify %s \
+// RUN:   -analyzer-checker=core,unix.BlockInCriticalSection
+
+void sleep(int x);
+
+namespace std {
+struct mutex {
+  void lock();
+  void unlock();
+};
+// libc++-shaped unique_lock: ctor and dtor have non-empty bodies that call
+// the underlying mutex's lock()/unlock() member functions.
+template <class M>
+struct unique_lock {
+  M *m_;
+  bool owns_;
+  explicit unique_lock(M &m) : m_(&m), owns_(true) { m_->lock(); }
+  ~unique_lock() {
+    if (owns_)
+      m_->unlock();
+  }
+};
+} // namespace std
+
+// Recursive use of the RAII guard. Without the fix, deep recursion would
+// prevent the dtor body from inlining at the inlining-stack-depth limit;
+// the inner mutex unlock would not fire, used to emit a leak FP.
+struct C {
+  std::mutex m;
+  bool aborted;
+  bool getAborted() {
+    std::unique_lock<std::mutex> lk(m);
+    return aborted;
+  }
+  void recurse() {
+    if (getAborted())
+      recurse();
+    sleep(1); // no-warning
+  }
+};
+
+// Direct case: RAII guard scoped to a function body. Constructor inlining +
+// destructor inlining must not double-count, even when both bodies inline.
+void callee(std::mutex &m) {
+  std::unique_lock<std::mutex> lk(m);
+  // The lock IS held here so blocking is correctly diagnosed.
+  sleep(1); // expected-warning {{Call to blocking function 'sleep' inside of 
critical section}}
+} // dtor fires here, releases the lock; no further sleep should warn.
+
+void top(std::mutex &m) {
+  callee(m);
+  sleep(1);  // no-warning: the lock from `callee` was released by 
~unique_lock before this sleep.
+}

From a944ff7712e7731cacf4cdd7fd293c3422f2c9f0 Mon Sep 17 00:00:00 2001
From: Balazs Benics <[email protected]>
Date: Fri, 10 Jul 2026 15:33:43 +0100
Subject: [PATCH 2/2] Escape the Object to model the side-effects of the ctor

---
 .../BlockInCriticalSectionChecker.cpp         | 19 ++++++++++++++-----
 1 file changed, 14 insertions(+), 5 deletions(-)

diff --git 
a/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp 
b/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp
index ad26324cdab76..b722bfd3390a7 100644
--- a/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp
@@ -264,7 +264,7 @@ class BlockInCriticalSectionChecker
                        bool IsLock) const;
 
   void handleLock(const MutexDescriptor &Mutex, const CallEvent &Call,
-                  CheckerContext &C) const;
+                  CheckerContext &C, ProgramStateRef State) const;
 
   void handleUnlock(const MutexDescriptor &Mutex, const CallEvent &Call,
                     CheckerContext &C) const;
@@ -325,7 +325,7 @@ static const MemRegion *getRegion(const CallEvent &Call,
 
 void BlockInCriticalSectionChecker::handleLock(
     const MutexDescriptor &LockDescriptor, const CallEvent &Call,
-    CheckerContext &C) const {
+    CheckerContext &C, ProgramStateRef State) const {
   const MemRegion *MutexRegion =
       getRegion(Call, LockDescriptor, /*IsLock=*/true);
   if (!MutexRegion)
@@ -333,7 +333,7 @@ void BlockInCriticalSectionChecker::handleLock(
 
   const CritSectionMarker MarkToAdd{Call.getOriginExpr(), MutexRegion};
   ProgramStateRef StateWithLockEvent =
-      C.getState()->add<ActiveCritSections>(MarkToAdd);
+      State->add<ActiveCritSections>(MarkToAdd);
   C.addTransition(StateWithLockEvent, createCritSectionNote(MarkToAdd, C));
 }
 
@@ -383,7 +383,7 @@ void BlockInCriticalSectionChecker::checkPostCall(const 
CallEvent &Call,
   if (std::optional<MutexDescriptor> LockDesc =
           checkDescriptorMatch(Call, C, /*IsLock=*/true)) {
     if (!std::holds_alternative<RAIIMutexDescriptor>(*LockDesc))
-      handleLock(*LockDesc, Call, C);
+      handleLock(*LockDesc, Call, C, C.getState());
     return;
   }
   if (std::optional<MutexDescriptor> UnlockDesc =
@@ -397,7 +397,16 @@ bool BlockInCriticalSectionChecker::evalCall(const 
CallEvent &Call,
   if (std::optional<MutexDescriptor> LockDesc =
           checkDescriptorMatch(Call, C, /*IsLock=*/true)) {
     if (std::holds_alternative<RAIIMutexDescriptor>(*LockDesc)) {
-      handleLock(*LockDesc, Call, C);
+      ProgramStateRef State = C.getState();
+      // Escape the object under construction to model the side-effects of the
+      // constructor.
+      if (const auto *Ctor = dyn_cast<AnyCXXConstructorCall>(&Call)) {
+        const MemRegion *ObjRegion = Ctor->getCXXThisVal().getAsRegion();
+        State = State->invalidateRegions(ObjRegion, C.getCFGElementRef(),
+                                         C.blockCount(), C.getStackFrame(),
+                                         /*CausesPointerEscape=*/false);
+      }
+      handleLock(*LockDesc, Call, C, State);
       return true;
     }
   }

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

Reply via email to