Skip to content

Conversation

@usx95
Copy link
Contributor

@usx95 usx95 commented Dec 5, 2025

Fixes a false positive in thread safety analysis when function parameters have lock/unlock annotations in their constructor/destructor.

PR #169320 added destructors to the CFG, which introduced false positives in thread safety analysis for function parameters. According to expr.call#6, parameter initialization occurs in the caller's context while destruction occurs in the callee's context.

class A {
 public:
  A() EXCLUSIVE_LOCK_FUNCTION(mu_) { mu_.Lock(); }
  ~A() UNLOCK_FUNCTION(mu_) { mu_.Unlock(); }
 private:
  Mutex mu_;
};

int do_something(A a) {  // Previously: false positive warning
  return 0;
}

Previously, thread safety analysis would see the destructor (unlock) in do_something but not the corresponding constructor (lock) that happened in the caller's context, causing a false positive.

Copy link
Contributor Author

usx95 commented Dec 5, 2025

This stack of pull requests is managed by Graphite. Learn more about stacking.

@usx95 usx95 changed the title false-posiitve thread-safety [ThreadSafety] Ignore parameter destructors in analysis Dec 5, 2025
@usx95 usx95 changed the title [ThreadSafety] Ignore parameter destructors in analysis [ThreadSafety] Ignore parameter destructors Dec 5, 2025
@usx95 usx95 requested review from Xazax-hun and melver December 5, 2025 12:12
@usx95 usx95 marked this pull request as ready for review December 5, 2025 12:13
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:analysis labels Dec 5, 2025
@llvmbot
Copy link
Member

llvmbot commented Dec 5, 2025

@llvm/pr-subscribers-clang-analysis

@llvm/pr-subscribers-clang

Author: Utkarsh Saxena (usx95)

Changes

Fixes a false positive in thread safety analysis when function parameters have lock/unlock annotations in their constructor/destructor.

PR #169320 added destructors to the CFG, which introduced false positives in thread safety analysis for function parameters. According to [expr.call#6](https://eel.is/c++draft/expr.call#6), parameter initialization occurs in the caller's context while destruction occurs in the callee's context.

class A {
 public:
  A() EXCLUSIVE_LOCK_FUNCTION(mu_) { mu_.Lock(); }
  ~A() UNLOCK_FUNCTION(mu_) { mu_.Unlock(); }
 private:
  Mutex mu_;
};

int do_something(A a) {  // Previously: false positive warning
  return 0;
}

Previously, thread safety analysis would see the destructor (unlock) in do_something but not the corresponding constructor (lock) that happened in the caller's context, causing a false positive.


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

2 Files Affected:

  • (modified) clang/lib/Analysis/ThreadSafety.cpp (+4)
  • (modified) clang/test/SemaCXX/warn-thread-safety-analysis.cpp (+23)
diff --git a/clang/lib/Analysis/ThreadSafety.cpp b/clang/lib/Analysis/ThreadSafety.cpp
index a25bd6007d5ed..12bcf2a78821e 100644
--- a/clang/lib/Analysis/ThreadSafety.cpp
+++ b/clang/lib/Analysis/ThreadSafety.cpp
@@ -43,6 +43,7 @@
 #include "llvm/ADT/SmallVector.h"
 #include "llvm/ADT/StringRef.h"
 #include "llvm/Support/Allocator.h"
+#include "llvm/Support/Casting.h"
 #include "llvm/Support/ErrorHandling.h"
 #include "llvm/Support/TrailingObjects.h"
 #include "llvm/Support/raw_ostream.h"
@@ -2820,6 +2821,9 @@ void ThreadSafetyAnalyzer::runAnalysis(AnalysisDeclContext &AC) {
         case CFGElement::AutomaticObjectDtor: {
           CFGAutomaticObjDtor AD = BI.castAs<CFGAutomaticObjDtor>();
           const auto *DD = AD.getDestructorDecl(AC.getASTContext());
+          // Ignore parameter dtors: their ctors happen in caller context.
+          if (isa_and_nonnull<ParmVarDecl>(AD.getVarDecl()))
+            break;
           if (!DD || !DD->hasAttrs())
             break;
 
diff --git a/clang/test/SemaCXX/warn-thread-safety-analysis.cpp b/clang/test/SemaCXX/warn-thread-safety-analysis.cpp
index 0e91639a271c5..203ea424020b6 100644
--- a/clang/test/SemaCXX/warn-thread-safety-analysis.cpp
+++ b/clang/test/SemaCXX/warn-thread-safety-analysis.cpp
@@ -7622,3 +7622,26 @@ void testLoopConditionalReassignment(Foo *f1, Foo *f2, bool cond) {
   ptr->mu.Unlock(); // expected-warning{{releasing mutex 'ptr->mu' that was not held}}
 } // expected-warning{{mutex 'f1->mu' is still held at the end of function}}
 }  // namespace CapabilityAliases
+
+namespace test_function_param_lock_unlock {
+class A {
+ public:
+  A() EXCLUSIVE_LOCK_FUNCTION(mu_) { mu_.Lock(); }
+  ~A() UNLOCK_FUNCTION(mu_) { mu_.Unlock(); }
+ private:
+  Mutex mu_;
+};
+int do_something(A a) { return 0; }
+
+// Unlock in dtor without lock in ctor.
+// FIXME: We cannot detect that we are releasing a lock that was never held!
+class B {
+ public:
+  B() {}
+  B(int) {}
+  ~B() UNLOCK_FUNCTION(mu_) { mu_.Unlock(); }
+ private:
+  Mutex mu_;
+};
+int do_something(B b) { return 0; }
+} // namespace test_function_param_lock_unlock

Copy link
Contributor

@melver melver left a comment

Choose a reason for hiding this comment

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

In general LGTM, since I think you're only restoring previous behavior.

@usx95 usx95 requested a review from melver December 5, 2025 14:00
Copy link
Contributor Author

usx95 commented Dec 5, 2025

I will land this for now to unblock the existing breakages. Happy to address more comments post-commit.

Copy link
Collaborator

@AaronBallman AaronBallman left a comment

Choose a reason for hiding this comment

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

LGTM!

@usx95 usx95 merged commit c1e17e5 into main Dec 5, 2025
9 of 10 checks passed
@usx95 usx95 deleted the users/usx95/thread-safety-dtor branch December 5, 2025 14:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

clang:analysis clang Clang issues not falling into any other category

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants