Skip to content

clang thread safety analysis does not take into account assert_exclusive_lock attribute #123512

Open
@krinkinmu

Description

@krinkinmu

It seems like clang version 18 (specifically I tested it on 18.1.8) fails to take into account assert_exclusive_lock/assert_capability annotations.

Here is a minimal example I came up with that reproduces the issue for me:

#define GUARDED_BY(x) __attribute__((guarded_by(x)))
#define EXCLUSIVE_LOCKS_REQUIRED(...) __attribute__((exclusive_locks_required(__VA_ARGS__)))
#define LOCKABLE __attribute__((lockable))
#define EXCLUSIVE_LOCK_FUNCTION(...) __attribute__((exclusive_lock_function(__VA_ARGS__)))
#define UNLOCK_FUNCTION(...) __attribute__((unlock_function(__VA_ARGS__)))
#define ASSERT_EXCLUSIVE_LOCK(...) __attribute__((assert_exclusive_lock(__VA_ARGS__)))

struct LOCKABLE Lock {
  void lock() EXCLUSIVE_LOCK_FUNCTION() {}
  void unlock() UNLOCK_FUNCTION() {}
  void assert() ASSERT_EXCLUSIVE_LOCK(this) {}
};

struct ComplexValue {
};

struct Parent {
  Lock lock;
};

struct Child {
  Child(Parent& p) : p(p) {}
  const ComplexValue& value() ASSERT_EXCLUSIVE_LOCK(p.lock) {
    p.lock.assert();
    return data;
  }

  Parent &p;
  ComplexValue data GUARDED_BY(p.lock);
};

When compiling this code with the following command:

clang++-18 -c -Wthread-safety -Wthread-safety-reference-return -Werror -std=c++20 test.cc

I get an error:

test.cc:25:12: error: returning variable 'data' by reference requires holding mutex 'p.lock' [-Werror,-Wthread-safety-reference-return]
   25 |     return data;
      |            ^
1 error generated.

I understand that returning a reference to a protected variable in general might be incorrect, but in this case before returning the reference I call a function with assert_exclusive_lock annotation, so by the time we get to the return statement, clang should be able to figure out that the lock is being held.

Naturally, replacing assert_exclusive_lock with exclusive_locks_required attribute eliminates the warning. However, in more complex cases it might be hard for clang to figure out what locks are held in what contexts, so I want to help clang with putting asserts like this to let clang know that the lock is actually held, but it does not seem that clang takes those into account or derives anything from exclusive_locks_required, which seems wrong.

Is my understanding of the purpose of exclusive_locks_required annotation somehow incorrect?

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions