Skip to content

Conversation

@gamesh411
Copy link
Contributor

@gamesh411 gamesh411 commented Nov 17, 2025

Fixes #166573

@gamesh411 gamesh411 marked this pull request as ready for review November 17, 2025 10:25
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:static analyzer labels Nov 17, 2025
@llvmbot
Copy link
Member

llvmbot commented Nov 17, 2025

@llvm/pr-subscribers-clang-static-analyzer-1

Author: Endre Fülöp (gamesh411)

Changes

Fixes #166573


Full diff: https://github.com/llvm/llvm-project/pull/168338.diff

2 Files Affected:

  • (modified) clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp (+19-1)
  • (modified) clang/test/Analysis/block-in-critical-section.cpp (+9)
diff --git a/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp
index 68bee710e5ce5..99137e038811d 100644
--- a/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp
@@ -115,7 +115,25 @@ class RAIIMutexDescriptor {
       return false;
     const IdentifierInfo *II =
         cast<CXXRecordDecl>(C->getDecl()->getParent())->getIdentifier();
-    return II == Guard;
+    if (II != Guard)
+      return false;
+
+    // For unique_lock, check if it's constructed with a ctor that takes the tag
+    // type defer_lock_t. In this case, the lock is not acquired.
+    if constexpr (std::is_same_v<T, CXXConstructorCall>) {
+      if (GuardName == "unique_lock" && C->getNumArgs() >= 2) {
+        const Expr *SecondArg = C->getArgExpr(1);
+        QualType ArgType = SecondArg->getType().getNonReferenceType();
+        QualType UnqualifiedType = ArgType.getUnqualifiedType();
+        if (const auto *RD = UnqualifiedType->getAsRecordDecl();
+            RD && RD->getName() == "defer_lock_t" &&
+            RD->getDeclContext()->isStdNamespace()) {
+          return false;
+        }
+      }
+    }
+
+    return true;
   }
 
 public:
diff --git a/clang/test/Analysis/block-in-critical-section.cpp b/clang/test/Analysis/block-in-critical-section.cpp
index ee9a708f231a8..674a09265faa5 100644
--- a/clang/test/Analysis/block-in-critical-section.cpp
+++ b/clang/test/Analysis/block-in-critical-section.cpp
@@ -16,9 +16,12 @@ struct lock_guard {
   lock_guard<T>(std::mutex) {}
   ~lock_guard<T>() {}
 };
+struct defer_lock_t {};
+constexpr defer_lock_t defer_lock{};
 template<typename T>
 struct unique_lock {
   unique_lock<T>(std::mutex) {}
+  unique_lock<T>(std::mutex, defer_lock_t) {} // defer_lock parameter
   ~unique_lock<T>() {}
 };
 template<typename T>
@@ -309,3 +312,9 @@ void testTrylockCurrentlyFalsePositive(pthread_mutex_t *m) {
                // FIXME: this is a false positive, the lock was not acquired
   }
 }
+
+void testBlockInCriticalSectionUniqueLockWithDeferLock() {
+  std::mutex g_mutex;
+  std::unique_lock<std::mutex> lock(g_mutex, std::defer_lock);
+  sleep(1); // no-warning
+}

@llvmbot
Copy link
Member

llvmbot commented Nov 17, 2025

@llvm/pr-subscribers-clang

Author: Endre Fülöp (gamesh411)

Changes

Fixes #166573


Full diff: https://github.com/llvm/llvm-project/pull/168338.diff

2 Files Affected:

  • (modified) clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp (+19-1)
  • (modified) clang/test/Analysis/block-in-critical-section.cpp (+9)
diff --git a/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp
index 68bee710e5ce5..99137e038811d 100644
--- a/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp
@@ -115,7 +115,25 @@ class RAIIMutexDescriptor {
       return false;
     const IdentifierInfo *II =
         cast<CXXRecordDecl>(C->getDecl()->getParent())->getIdentifier();
-    return II == Guard;
+    if (II != Guard)
+      return false;
+
+    // For unique_lock, check if it's constructed with a ctor that takes the tag
+    // type defer_lock_t. In this case, the lock is not acquired.
+    if constexpr (std::is_same_v<T, CXXConstructorCall>) {
+      if (GuardName == "unique_lock" && C->getNumArgs() >= 2) {
+        const Expr *SecondArg = C->getArgExpr(1);
+        QualType ArgType = SecondArg->getType().getNonReferenceType();
+        QualType UnqualifiedType = ArgType.getUnqualifiedType();
+        if (const auto *RD = UnqualifiedType->getAsRecordDecl();
+            RD && RD->getName() == "defer_lock_t" &&
+            RD->getDeclContext()->isStdNamespace()) {
+          return false;
+        }
+      }
+    }
+
+    return true;
   }
 
 public:
diff --git a/clang/test/Analysis/block-in-critical-section.cpp b/clang/test/Analysis/block-in-critical-section.cpp
index ee9a708f231a8..674a09265faa5 100644
--- a/clang/test/Analysis/block-in-critical-section.cpp
+++ b/clang/test/Analysis/block-in-critical-section.cpp
@@ -16,9 +16,12 @@ struct lock_guard {
   lock_guard<T>(std::mutex) {}
   ~lock_guard<T>() {}
 };
+struct defer_lock_t {};
+constexpr defer_lock_t defer_lock{};
 template<typename T>
 struct unique_lock {
   unique_lock<T>(std::mutex) {}
+  unique_lock<T>(std::mutex, defer_lock_t) {} // defer_lock parameter
   ~unique_lock<T>() {}
 };
 template<typename T>
@@ -309,3 +312,9 @@ void testTrylockCurrentlyFalsePositive(pthread_mutex_t *m) {
                // FIXME: this is a false positive, the lock was not acquired
   }
 }
+
+void testBlockInCriticalSectionUniqueLockWithDeferLock() {
+  std::mutex g_mutex;
+  std::unique_lock<std::mutex> lock(g_mutex, std::defer_lock);
+  sleep(1); // no-warning
+}

@gamesh411 gamesh411 requested a review from NagyDonat November 17, 2025 10:35
Copy link
Contributor

@NagyDonat NagyDonat left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM with a few minor suggestions.

Copy link
Contributor

@steakhal steakhal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the review. LGTM for me, the raised suggestions look good to me.

@github-actions
Copy link

github-actions bot commented Nov 19, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

@gamesh411
Copy link
Contributor Author

@NagyDonat I may have overcomplicated that part. Thanks for the suggestions. I have applied them.

@github-actions
Copy link

🐧 Linux x64 Test Results

  • 111330 tests passed
  • 4421 tests skipped

@gamesh411 gamesh411 merged commit b11b7b3 into llvm:main Nov 19, 2025
10 checks passed
@gamesh411 gamesh411 deleted the defer-modelling branch November 25, 2025 15:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

clang:static analyzer clang Clang issues not falling into any other category

Projects

None yet

Development

Successfully merging this pull request may close these issues.

clang-tidy clang-analyzer-unix.BlockInCriticalSection false positive with std::defer_lock

5 participants