Skip to content

[clang][ThreadSafety]: fix discrepancy between capability attributes #139343

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

theuni
Copy link

@theuni theuni commented May 10, 2025

Fix the case where release_generic_capability did not correctly release when used as a reverse capability as enabled by commit 6a68efc.

I noticed this when trying to implement a reverse lock.

My my project still uses the old UNLOCK_FUNCTION macro which maps to unlock_function. With that attribute, the MutexUnlock test-case seen here does not work.

I'm not at all familiar with the clang code so I have no idea if this is the correct fix, but it fixes my problem. Hopefully it's helpful.

Here's a minimal reproducer:

class __attribute__((capability(""))) Mutex {
 public:
  const Mutex& operator!() const { return *this; }
};

class  __attribute__((scoped_lockable)) MutexLock {
public:
  MutexLock(Mutex *mu) __attribute__((acquire_capability(mu))) {}
  ~MutexLock() __attribute__((release_capability())){}
};

  class  __attribute__((scoped_lockable)) MutexLockOld {
  public:
      MutexLockOld(Mutex *mu) __attribute__((exclusive_lock_function(mu))) {}
      ~MutexLockOld() __attribute__((unlock_function())){}
  };

class  __attribute__((scoped_lockable)) MutexLockGeneric {
public:
    MutexLockGeneric(Mutex *mu) __attribute__((acquire_capability(mu))) {}
    ~MutexLockGeneric() __attribute__((release_generic_capability())){}
};

class  __attribute__((scoped_lockable)) MutexUnlock {
public:
  MutexUnlock(Mutex *mu) __attribute__((release_capability(mu))){}
  ~MutexUnlock() __attribute__((release_capability())){}
};

class  __attribute__((scoped_lockable)) MutexUnlockOld {
public:
  MutexUnlockOld(Mutex *mu) __attribute__((unlock_function(mu))){}
  ~MutexUnlockOld() __attribute__((unlock_function())){}
};

class  __attribute__((scoped_lockable)) MutexUnlockGeneric {
public:
  MutexUnlockGeneric(Mutex *mu) __attribute__((release_generic_capability(mu))){}
  ~MutexUnlockGeneric() __attribute__((release_generic_capability())){}
};


Mutex mut;
void req() __attribute__((requires_capability(mut))){}
void req2() __attribute__((exclusive_locks_required(mut))){}
void not_req() __attribute__((requires_capability(!mut))){}

void good()
{
    MutexLock lock(&mut);
    req();
    {
        MutexUnlock reverse_lock(&mut);
        not_req();
    }
    req();
}

void bad()
{
    MutexLockGeneric lock(&mut);
    req();
    {
        MutexUnlockGeneric reverse_lock(&mut);
        not_req();
    }
    req();
    req2();
}

void bad2()
{
    MutexLockOld lock(&mut);
    req();
    {
        MutexUnlockOld reverse_lock(&mut);
        not_req();
    }
    req();
    req2();
}

Result:

clangtest.cpp:67:5: warning: calling function 'req' requires holding  'mut' exclusively [-Wthread-safety-analysis]
   67 |     req();
      |     ^
clangtest.cpp:68:5: warning: calling function 'req2' requires holding  'mut' exclusively [-Wthread-safety-analysis]
   68 |     req2();
      |     ^
clangtest.cpp:79:5: warning: calling function 'req' requires holding  'mut' exclusively [-Wthread-safety-analysis]
   79 |     req();
      |     ^
clangtest.cpp:80:5: warning: calling function 'req2' requires holding  'mut' exclusively [-Wthread-safety-analysis]
   80 |     req2();

Fix the case where release_generic_capability did not correctly release when
used as a reverse capability as enabled by commit 6a68efc.
Copy link

Thank you for submitting a Pull Request (PR) to the LLVM Project!

This PR will be automatically labeled and the relevant teams will be notified.

If you wish to, you can add reviewers by using the "Reviewers" section on this page.

If this is not working for you, it is probably because you do not have write permissions for the repository. In which case you can instead tag reviewers by name in a comment by using @ followed by their GitHub username.

If you have received no comments on your PR for a week, you can request a review by "ping"ing the PR by adding a comment “Ping”. The common courtesy "ping" rate is once a week. Please remember that you are asking for valuable time from other developers.

If you have further questions, they may be answered by the LLVM GitHub User Guide.

You can also ask questions in a comment on this PR, on the LLVM Discord or on the forums.

@llvmbot llvmbot added clang Clang issues not falling into any other category clang:analysis labels May 10, 2025
@theuni theuni changed the title [clang][ThreadSafety]: fix descrepency between capability attributes [clang][ThreadSafety]: fix discrepancy between capability attributes May 10, 2025
@llvmbot
Copy link
Member

llvmbot commented May 10, 2025

@llvm/pr-subscribers-clang

Author: Cory Fields (theuni)

Changes

Fix the case where release_generic_capability did not correctly release when used as a reverse capability as enabled by commit 6a68efc.

I noticed this when trying to implement a reverse lock.

My my project still uses the old UNLOCK_FUNCTION macro which maps to unlock_function. With that attribute, the MutexUnlock test-case seen here does not work.

I'm not at all familiar with the clang code so I have no idea if this is the correct fix, but it fixes my problem. Hopefully it's helpful.

Here's a minimal reproducer:

class __attribute__((capability(""))) Mutex {
 public:
  const Mutex& operator!() const { return *this; }
};

class  __attribute__((scoped_lockable)) MutexLock {
public:
  MutexLock(Mutex *mu) __attribute__((acquire_capability(mu))) {}
  ~MutexLock() __attribute__((release_capability())){}
};

  class  __attribute__((scoped_lockable)) MutexLockOld {
  public:
      MutexLockOld(Mutex *mu) __attribute__((exclusive_lock_function(mu))) {}
      ~MutexLockOld() __attribute__((unlock_function())){}
  };

class  __attribute__((scoped_lockable)) MutexLockGeneric {
public:
    MutexLockGeneric(Mutex *mu) __attribute__((acquire_capability(mu))) {}
    ~MutexLockGeneric() __attribute__((release_generic_capability())){}
};

class  __attribute__((scoped_lockable)) MutexUnlock {
public:
  MutexUnlock(Mutex *mu) __attribute__((release_capability(mu))){}
  ~MutexUnlock() __attribute__((release_capability())){}
};

class  __attribute__((scoped_lockable)) MutexUnlockOld {
public:
  MutexUnlockOld(Mutex *mu) __attribute__((unlock_function(mu))){}
  ~MutexUnlockOld() __attribute__((unlock_function())){}
};

class  __attribute__((scoped_lockable)) MutexUnlockGeneric {
public:
  MutexUnlockGeneric(Mutex *mu) __attribute__((release_generic_capability(mu))){}
  ~MutexUnlockGeneric() __attribute__((release_generic_capability())){}
};


Mutex mut;
void req() __attribute__((requires_capability(mut))){}
void req2() __attribute__((exclusive_locks_required(mut))){}
void not_req() __attribute__((requires_capability(!mut))){}

void good()
{
    MutexLock lock(&mut);
    req();
    {
        MutexUnlock reverse_lock(&mut);
        not_req();
    }
    req();
}

void bad()
{
    MutexLockGeneric lock(&mut);
    req();
    {
        MutexUnlockGeneric reverse_lock(&mut);
        not_req();
    }
    req();
    req2();
}

void bad2()
{
    MutexLockOld lock(&mut);
    req();
    {
        MutexUnlockOld reverse_lock(&mut);
        not_req();
    }
    req();
    req2();
}

Result:

clangtest.cpp:67:5: warning: calling function 'req' requires holding  'mut' exclusively [-Wthread-safety-analysis]
   67 |     req();
      |     ^
clangtest.cpp:68:5: warning: calling function 'req2' requires holding  'mut' exclusively [-Wthread-safety-analysis]
   68 |     req2();
      |     ^
clangtest.cpp:79:5: warning: calling function 'req' requires holding  'mut' exclusively [-Wthread-safety-analysis]
   79 |     req();
      |     ^
clangtest.cpp:80:5: warning: calling function 'req2' requires holding  'mut' exclusively [-Wthread-safety-analysis]
   80 |     req2();

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

1 Files Affected:

  • (modified) clang/lib/Analysis/ThreadSafety.cpp (+2)
diff --git a/clang/lib/Analysis/ThreadSafety.cpp b/clang/lib/Analysis/ThreadSafety.cpp
index 7e86af6b4a317..a963bcda0c2d0 100644
--- a/clang/lib/Analysis/ThreadSafety.cpp
+++ b/clang/lib/Analysis/ThreadSafety.cpp
@@ -2026,6 +2026,8 @@ void BuildLockset::handleCall(const Expr *Exp, const NamedDecl *D,
       ScopedEntry->addExclusiveUnlock(M);
     for (const auto &M : SharedLocksToRemove)
       ScopedEntry->addSharedUnlock(M);
+    for (const auto &M : GenericLocksToRemove)
+      ScopedEntry->addExclusiveUnlock(M);
     Analyzer->addLock(FSet, std::move(ScopedEntry));
   }
 }

@llvmbot
Copy link
Member

llvmbot commented May 10, 2025

@llvm/pr-subscribers-clang-analysis

Author: Cory Fields (theuni)

Changes

Fix the case where release_generic_capability did not correctly release when used as a reverse capability as enabled by commit 6a68efc.

I noticed this when trying to implement a reverse lock.

My my project still uses the old UNLOCK_FUNCTION macro which maps to unlock_function. With that attribute, the MutexUnlock test-case seen here does not work.

I'm not at all familiar with the clang code so I have no idea if this is the correct fix, but it fixes my problem. Hopefully it's helpful.

Here's a minimal reproducer:

class __attribute__((capability(""))) Mutex {
 public:
  const Mutex& operator!() const { return *this; }
};

class  __attribute__((scoped_lockable)) MutexLock {
public:
  MutexLock(Mutex *mu) __attribute__((acquire_capability(mu))) {}
  ~MutexLock() __attribute__((release_capability())){}
};

  class  __attribute__((scoped_lockable)) MutexLockOld {
  public:
      MutexLockOld(Mutex *mu) __attribute__((exclusive_lock_function(mu))) {}
      ~MutexLockOld() __attribute__((unlock_function())){}
  };

class  __attribute__((scoped_lockable)) MutexLockGeneric {
public:
    MutexLockGeneric(Mutex *mu) __attribute__((acquire_capability(mu))) {}
    ~MutexLockGeneric() __attribute__((release_generic_capability())){}
};

class  __attribute__((scoped_lockable)) MutexUnlock {
public:
  MutexUnlock(Mutex *mu) __attribute__((release_capability(mu))){}
  ~MutexUnlock() __attribute__((release_capability())){}
};

class  __attribute__((scoped_lockable)) MutexUnlockOld {
public:
  MutexUnlockOld(Mutex *mu) __attribute__((unlock_function(mu))){}
  ~MutexUnlockOld() __attribute__((unlock_function())){}
};

class  __attribute__((scoped_lockable)) MutexUnlockGeneric {
public:
  MutexUnlockGeneric(Mutex *mu) __attribute__((release_generic_capability(mu))){}
  ~MutexUnlockGeneric() __attribute__((release_generic_capability())){}
};


Mutex mut;
void req() __attribute__((requires_capability(mut))){}
void req2() __attribute__((exclusive_locks_required(mut))){}
void not_req() __attribute__((requires_capability(!mut))){}

void good()
{
    MutexLock lock(&mut);
    req();
    {
        MutexUnlock reverse_lock(&mut);
        not_req();
    }
    req();
}

void bad()
{
    MutexLockGeneric lock(&mut);
    req();
    {
        MutexUnlockGeneric reverse_lock(&mut);
        not_req();
    }
    req();
    req2();
}

void bad2()
{
    MutexLockOld lock(&mut);
    req();
    {
        MutexUnlockOld reverse_lock(&mut);
        not_req();
    }
    req();
    req2();
}

Result:

clangtest.cpp:67:5: warning: calling function 'req' requires holding  'mut' exclusively [-Wthread-safety-analysis]
   67 |     req();
      |     ^
clangtest.cpp:68:5: warning: calling function 'req2' requires holding  'mut' exclusively [-Wthread-safety-analysis]
   68 |     req2();
      |     ^
clangtest.cpp:79:5: warning: calling function 'req' requires holding  'mut' exclusively [-Wthread-safety-analysis]
   79 |     req();
      |     ^
clangtest.cpp:80:5: warning: calling function 'req2' requires holding  'mut' exclusively [-Wthread-safety-analysis]
   80 |     req2();

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

1 Files Affected:

  • (modified) clang/lib/Analysis/ThreadSafety.cpp (+2)
diff --git a/clang/lib/Analysis/ThreadSafety.cpp b/clang/lib/Analysis/ThreadSafety.cpp
index 7e86af6b4a317..a963bcda0c2d0 100644
--- a/clang/lib/Analysis/ThreadSafety.cpp
+++ b/clang/lib/Analysis/ThreadSafety.cpp
@@ -2026,6 +2026,8 @@ void BuildLockset::handleCall(const Expr *Exp, const NamedDecl *D,
       ScopedEntry->addExclusiveUnlock(M);
     for (const auto &M : SharedLocksToRemove)
       ScopedEntry->addSharedUnlock(M);
+    for (const auto &M : GenericLocksToRemove)
+      ScopedEntry->addExclusiveUnlock(M);
     Analyzer->addLock(FSet, std::move(ScopedEntry));
   }
 }

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.

2 participants