[clang] Thread Safety Analysis: Support passing scoped locks between functions with appropriate annotations (PR #110523)

Aaron Puchert via cfe-commits cfe-commits at lists.llvm.org
Mon Sep 30 15:47:30 PDT 2024


================
@@ -3171,6 +3171,372 @@ void lockUnlock() EXCLUSIVE_LOCKS_REQUIRED(mu) {
 
 } // end namespace ScopedUnlock
 
+namespace PassingScope {
+
+class SCOPED_LOCKABLE RelockableScope {
+public:
+  RelockableScope(Mutex *mu) EXCLUSIVE_LOCK_FUNCTION(mu);
+  void Release() UNLOCK_FUNCTION();
+  void Acquire() EXCLUSIVE_LOCK_FUNCTION();
+  ~RelockableScope() EXCLUSIVE_UNLOCK_FUNCTION();
+};
+
+class SCOPED_LOCKABLE ReaderRelockableScope {
+public:
+  ReaderRelockableScope(Mutex *mu) SHARED_LOCK_FUNCTION(mu);
+  void Release() UNLOCK_FUNCTION();
+  void Acquire() SHARED_LOCK_FUNCTION();
+  ~ReaderRelockableScope() UNLOCK_FUNCTION();
+};
+
+Mutex mu;
+Mutex mu1;
+Mutex mu2;
+int x GUARDED_BY(mu);
+int y GUARDED_BY(mu) GUARDED_BY(mu2);
+void print(int);
+
+// Analysis inside the function with annotated parameters
+
+void sharedRequired(ReleasableMutexLock& scope SHARED_LOCKS_REQUIRED(mu)) {
+  print(x); // OK.
+}
+
+void sharedAcquire(ReaderRelockableScope& scope SHARED_LOCK_FUNCTION(mu)) {
+  scope.Acquire();
+  print(x);
+}
+
+void sharedRelease(RelockableScope& scope SHARED_UNLOCK_FUNCTION(mu)) {
+  print(x);
+  scope.Release();
+  print(x); // expected-warning{{reading variable 'x' requires holding mutex 'mu'}}
+}
+
+void requiredLock(ReleasableMutexLock& scope EXCLUSIVE_LOCKS_REQUIRED(mu)) {
+  x = 1; // OK.
+}
+
+void reacquireRequiredLock(RelockableScope& scope EXCLUSIVE_LOCKS_REQUIRED(mu)) {
+  scope.Release();
+  scope.Acquire();
+  x = 2; // OK.
+}
+
+void releaseSingleMutex(ReleasableMutexLock& scope EXCLUSIVE_UNLOCK_FUNCTION(mu)) {
+  x = 1; // OK.
+  scope.Release();
+  x = 2; // expected-warning{{writing variable 'x' requires holding mutex 'mu' exclusively}}
+}
+
+void releaseMultipleMutexes(ReleasableMutexLock& scope EXCLUSIVE_UNLOCK_FUNCTION(mu, mu2)) {
+  y = 1; // OK.
+  scope.Release();
+  y = 2; // expected-warning{{writing variable 'y' requires holding mutex 'mu' exclusively}}
+         // expected-warning at -1{{writing variable 'y' requires holding mutex 'mu2' exclusively}}
+}
+
+void acquireLock(RelockableScope& scope EXCLUSIVE_LOCK_FUNCTION(mu)) {
+  x = 1; // expected-warning{{writing variable 'x' requires holding mutex 'mu' exclusively}}
+  scope.Acquire();
+  x = 2; // OK.
+}
+
+void acquireMultipleLocks(RelockableScope& scope EXCLUSIVE_LOCK_FUNCTION(mu, mu2)) {
+  y = 1; // expected-warning{{writing variable 'y' requires holding mutex 'mu' exclusively}}
+         // expected-warning at -1{{writing variable 'y' requires holding mutex 'mu2' exclusively}}
+  scope.Acquire();
+  y = 2;  // OK.
+}
+
+void excludedLock(ReleasableMutexLock& scope LOCKS_EXCLUDED(mu)) {
+  x = 1; // expected-warning{{writing variable 'x' requires holding mutex 'mu' exclusively}}
+}
+
+void acquireAndReleaseExcludedLocks(RelockableScope& scope LOCKS_EXCLUDED(mu)) {
+  scope.Acquire();
+  scope.Release();
+}
+
+// expected-note at +1{{mutex acquired here}}
+void unreleasedMutex(ReleasableMutexLock& scope EXCLUSIVE_UNLOCK_FUNCTION(mu)) {
+  x = 1; // OK.
+} // expected-warning{{mutex 'mu' is still held at the end of function}}
+
+// expected-note at +1{{mutex acquired here}}
+void acquireAlreadyHeldMutex(RelockableScope& scope EXCLUSIVE_UNLOCK_FUNCTION(mu)) {
+  scope.Acquire(); // expected-warning{{acquiring mutex 'mu' that is already held}}
+  scope.Release();
+}
+
+void reacquireMutex(RelockableScope& scope EXCLUSIVE_UNLOCK_FUNCTION(mu)) {
+  scope.Release();
+  scope.Acquire(); // expected-note{{mutex acquired here}}
+  x = 2; // OK.
+} // expected-warning{{mutex 'mu' is still held at the end of function}}
+
+// expected-note at +1{{mutex acquired here}}
+void requireSingleMutex(ReleasableMutexLock& scope EXCLUSIVE_LOCKS_REQUIRED(mu)) {
+  x = 1; // OK.
+  scope.Release();
+  x = 2; // expected-warning{{writing variable 'x' requires holding mutex 'mu' exclusively}}
+} // expected-warning{{expecting mutex 'mu' to be held at the end of function}}
+
+// expected-note at +1 2{{mutex acquired here}}
+void requireMultipleMutexes(ReleasableMutexLock& scope EXCLUSIVE_LOCKS_REQUIRED(mu, mu2)) {
+  y = 1; // OK.
+  scope.Release();
+  y = 2; // expected-warning{{writing variable 'y' requires holding mutex 'mu' exclusively}}
+         // expected-warning at -1{{writing variable 'y' requires holding mutex 'mu2' exclusively}}
+} // expected-warning{{expecting mutex 'mu' to be held at the end of function}}
+  // expected-warning at -1{{expecting mutex 'mu2' to be held at the end of function}}
+
+// expected-note at +1{{mutex acquired here}}
+void acquireAlreadyHeldLock(RelockableScope& scope EXCLUSIVE_LOCKS_REQUIRED(mu)) {
+  scope.Acquire(); // expected-warning{{acquiring mutex 'mu' that is already held}}
+}
+
+// expected-note at +1 {{mutex acquired here}}
+void releaseWithoutHoldingLock(ReleasableMutexLock& scope EXCLUSIVE_LOCK_FUNCTION(mu)) {
+  scope.Release(); // expected-warning{{releasing mutex 'mu' that was not held}}
+} // expected-warning{{expecting mutex 'mu' to be held at the end of function}}
+
+// expected-note at +1 {{mutex acquired here}}
+void endWithReleasedMutex(RelockableScope& scope EXCLUSIVE_LOCK_FUNCTION(mu)) {
+  scope.Acquire();
+  scope.Release();
+} // expected-warning{{expecting mutex 'mu' to be held at the end of function}}
+
+void acquireExcludedLock(RelockableScope& scope LOCKS_EXCLUDED(mu)) {
+  x = 1; // expected-warning{{writing variable 'x' requires holding mutex 'mu' exclusively}}
+  scope.Acquire(); // expected-note {{mutex acquired here}}
+  x = 2; // OK.
+} // expected-warning{{mutex 'mu' is still held at the end of function}}
+
+void acquireMultipleExcludedLocks(RelockableScope& scope LOCKS_EXCLUDED(mu, mu2)) {
+  y = 1; // expected-warning{{writing variable 'y' requires holding mutex 'mu' exclusively}}
+         // expected-warning at -1{{writing variable 'y' requires holding mutex 'mu2' exclusively}}
+  scope.Acquire(); // expected-note 2{{mutex acquired here}}
+  y = 2; // OK.
+} // expected-warning{{mutex 'mu' is still held at the end of function}}
+  // expected-warning at -1{{mutex 'mu2' is still held at the end of function}}
+
+void reacquireExcludedLocks(RelockableScope& scope LOCKS_EXCLUDED(mu)) {
+  scope.Release(); // expected-warning{{releasing mutex 'mu' that was not held}}
+  scope.Acquire(); // expected-note {{mutex acquired here}}
+  x = 2; // OK.
+} // expected-warning{{mutex 'mu' is still held at the end of function}}
+
+// expected-note at +1{{mutex acquired here}}
+void sharedRequired2(ReleasableMutexLock& scope SHARED_LOCKS_REQUIRED(mu)) {
+  print(x); // OK.
+  scope.Release();
+  print(x); // expected-warning{{reading variable 'x' requires holding mutex 'mu'}}
+} // expected-warning{{expecting mutex 'mu' to be held at the end of function}}
+
+// expected-note at +1{{mutex acquired here}}
+void sharedAcquire2(RelockableScope& scope SHARED_LOCK_FUNCTION(mu)) {
+  print(x); // expected-warning{{reading variable 'x' requires holding mutex 'mu'}}
+  scope.Release(); // expected-warning{{releasing mutex 'mu' that was not held}}
+} // expected-warning{{expecting mutex 'mu' to be held at the end of function}}
+
+// expected-note at +1 2{{mutex acquired here}}
+void sharedRelease2(RelockableScope& scope SHARED_UNLOCK_FUNCTION(mu)) {
+  scope.Acquire(); //expected-warning{{acquiring mutex 'mu' that is already held}}
+} //expected-warning{{mutex 'mu' is still held at the end of function}}
+
+// Handling the call of the function with annotated parameters
+
+// expected-note at +1{{see attribute on parameter here}}
+void release(ReleasableMutexLock& scope EXCLUSIVE_UNLOCK_FUNCTION(mu));
+// expected-note at +1{{see attribute on parameter here}}
+void release_DoubleMutexLock(DoubleMutexLock& scope EXCLUSIVE_UNLOCK_FUNCTION(mu));
+// expected-note at +1 2{{see attribute on parameter here}}
+void release_two(ReleasableMutexLock& scope EXCLUSIVE_UNLOCK_FUNCTION(mu, mu2));
+// expected-note at +1{{see attribute on parameter here}}
+void release_double(DoubleMutexLock& scope EXCLUSIVE_UNLOCK_FUNCTION(mu, mu2));
+void require(ReleasableMutexLock& scope EXCLUSIVE_LOCKS_REQUIRED(mu));
+void acquire(RelockableScope& scope EXCLUSIVE_LOCK_FUNCTION(mu));
+void exclude(RelockableScope& scope LOCKS_EXCLUDED(mu));
+
+void release_shared(ReaderRelockableScope& scope SHARED_UNLOCK_FUNCTION(mu));
+void require_shared(ReleasableMutexLock& scope SHARED_LOCKS_REQUIRED(mu));
+void acquire_shared(ReaderRelockableScope& scope SHARED_LOCK_FUNCTION(mu));
+
+void unlockCall() {
+  ReleasableMutexLock scope(&mu);
+  x = 1; // OK.
+  release(scope);
+  x = 2; // expected-warning{{writing variable 'x' requires holding mutex 'mu' exclusively}}
+}
+
+void unlockSharedCall() {
+  ReaderRelockableScope scope(&mu);
+  print(x); // OK.
+  release_shared(scope);
+  print(x); // expected-warning{{reading variable 'x' requires holding mutex 'mu'}}
+}
+
+void requireCall() {
+  ReleasableMutexLock scope(&mu);
+  x = 1; // OK.
+  require(scope);
+  x = 2; // Ok.
+}
+
+void requireSharedCall() {
+  ReleasableMutexLock scope(&mu);
+  print(x); // OK.
+  require_shared(scope);
+  print(x); // Ok.
+}
+
+void acquireCall() {
+  RelockableScope scope(&mu);
+  scope.Release();
+  acquire(scope);
+  x = 2; // Ok.
+}
+
+void acquireSharedCall() {
+  ReaderRelockableScope scope(&mu);
+  scope.Release();
+  acquire_shared(scope);
+  print(x); // Ok.
+}
+
+void writeAfterExcludeCall() {
+  RelockableScope scope(&mu);
+  scope.Release();
+  exclude(scope);
+  x = 2; // expected-warning{{writing variable 'x' requires holding mutex 'mu' exclusively}}
+}
+
+void unlockCallAfterExplicitRelease() {
+  ReleasableMutexLock scope(&mu);
+  x = 1; // OK.
+  scope.Release(); // expected-note{{mutex released here}}
+  release(scope); // expected-warning{{releasing mutex 'mu' that was not held}}
+  x = 2; // expected-warning{{writing variable 'x' requires holding mutex 'mu' exclusively}}
+}
+
+void unmatchedMutexes() {
+  ReleasableMutexLock scope(&mu2);
+  release(scope); // expected-warning{{mutex managed by 'scope' is 'mu2' instead of 'mu'}}
+                  // expected-warning at -1{{releasing mutex 'mu' that was not held}}
+}
+
+void wrongOrder() {
+  DoubleMutexLock scope(&mu2, &mu);
+  release_double(scope); // expected-warning{{mutex managed by 'scope' is 'mu2' instead of 'mu'}}
+}
+
+void differentNumberOfMutexes() {
+  ReleasableMutexLock scope(&mu);
+  release_two(scope); // expected-warning{{mutex 'mu2' not managed by 'scope'}}
+                      // expected-warning at -1{{releasing mutex 'mu2' that was not held}}
+}
+
+void differentNumberOfMutexes2() {
+  ReleasableMutexLock scope(&mu2);
+  release_two(scope); // expected-warning{{mutex managed by 'scope' is 'mu2' instead of 'mu'}}
+                      // expected-warning at -1{{releasing mutex 'mu' that was not held}}
+}
+
+void differentNumberOfMutexes3() {
+  DoubleMutexLock scope(&mu, &mu2);
+  release_DoubleMutexLock(scope); // expected-warning{{did not expect mutex 'mu2' to be managed by 'scope'}}
+}
+
+void releaseDefault(ReleasableMutexLock& scope EXCLUSIVE_UNLOCK_FUNCTION(mu), int = 0);
+
+void unlockFunctionDefault() {
+  ReleasableMutexLock scope(&mu);
+  x = 1; // OK.
+  releaseDefault(scope);
+  x = 2; // expected-warning{{writing variable 'x' requires holding mutex 'mu' exclusively}}
+}
+
+void requireCallWithReleasedLock() {
+  ReleasableMutexLock scope(&mu);
+  scope.Release();
+  require(scope);  // expected-warning{{calling function 'require' requires holding mutex 'mu' exclusively}}
+}
+
+void acquireCallWithAlreadyHeldLock() {
+  RelockableScope scope(&mu); // expected-note{{mutex acquired here}}
+  acquire(scope); // expected-warning{{acquiring mutex 'mu' that is already held}}
+  x = 1;
+}
+
+void excludeCallWithAlreadyHeldLock() {
+  RelockableScope scope(&mu);
+  exclude(scope);// expected-warning{{cannot call function 'exclude' while mutex 'mu' is held}}
+  x = 2; // OK.
+}
+
+// Passing a scope by value
+RelockableScope lock() EXCLUSIVE_LOCK_FUNCTION(mu);
+void destruct(RelockableScope scope) {}
+void passScopeByValue() {
+  destruct(lock());
+}
+
+void requireConst(const ReleasableMutexLock& scope EXCLUSIVE_LOCKS_REQUIRED(mu));
+void requireConstCall() {
+  requireConst(ReleasableMutexLock(&mu));
+}
+
+void passScopeUndeclared(ReleasableMutexLock &scope) {
+  release(scope); // expected-warning{{calling function 'release' requires holding mutex 'scope' exclusively}}
+                  // expected-warning at -1{{releasing mutex 'mu' that was not held}}
+}
+
+class SCOPED_LOCKABLE ScopedWithoutLock {
+public:
+  ScopedWithoutLock();
+
+  ~ScopedWithoutLock() EXCLUSIVE_UNLOCK_FUNCTION();
+};
+
+void require(ScopedWithoutLock &scope EXCLUSIVE_LOCKS_REQUIRED(mu));
+
+void constructWithoutLock() {
+  ScopedWithoutLock scope;
+  require(scope); // expected-warning{{calling function 'require' requires holding mutex 'mu' exclusively}}
+                  // expected-warning at -1{{calling function 'require' requires holding mutex 'scope' exclusively}}
+} // expected-warning {{releasing mutex 'scope' that was not held}}
+
+void requireConst(const ScopedWithoutLock& scope EXCLUSIVE_LOCKS_REQUIRED(mu));
+
+void requireCallWithReleasedLock2() {
+  requireConst(ScopedWithoutLock());
+  // expected-warning at -1{{calling function 'requireConst' requires holding mutex '#undefined' exclusively}}
+  // expected-warning at -2{{calling function 'requireConst' requires holding mutex 'mu' exclusively}}
+}
+
+void requireDecl(RelockableScope &scope EXCLUSIVE_LOCKS_REQUIRED(mu));
+void requireDecl(RelockableScope &scope){
----------------
aaronpuchert wrote:

```suggestion
void requireDecl(RelockableScope &scope) {
```
Same below.

https://github.com/llvm/llvm-project/pull/110523


More information about the cfe-commits mailing list