segoon updated this revision to Diff 314093.
segoon retitled this revision from "Add a check for blocking types and 
functions." to "[clang-tidy] Add a check for blocking types and functions.".
segoon added a comment.
Herald added a reviewer: jfb.
Herald added subscribers: xazax.hun, mgorny.
Herald added a reviewer: jfb.

fix the mess


CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D93940/new/

https://reviews.llvm.org/D93940

Files:
  clang-tools-extra/clang-tidy/concurrency/AsyncBlockingCheck.cpp
  clang-tools-extra/clang-tidy/concurrency/AsyncBlockingCheck.h
  clang-tools-extra/clang-tidy/concurrency/CMakeLists.txt
  clang-tools-extra/clang-tidy/concurrency/ConcurrencyTidyModule.cpp
  clang-tools-extra/docs/ReleaseNotes.rst
  clang-tools-extra/docs/clang-tidy/checks/concurrency-async-blocking.rst
  clang-tools-extra/docs/clang-tidy/checks/list.rst
  clang-tools-extra/test/clang-tidy/checkers/concurrency-async-blocking.c
  clang-tools-extra/test/clang-tidy/checkers/concurrency-async-blocking.cpp

Index: clang-tools-extra/test/clang-tidy/checkers/concurrency-async-blocking.cpp
===================================================================
--- /dev/null
+++ clang-tools-extra/test/clang-tidy/checkers/concurrency-async-blocking.cpp
@@ -0,0 +1,450 @@
+// RUN: %check_clang_tidy %s concurrency-async-blocking %t -- \
+// RUN: -config='{CheckOptions: [{key: "concurrency-async-blocking.LockableExtra", value: "my::mutex;my::shared_mutex"}, {key: "concurrency-async-blocking.WaitableExtra", value: "my::Future;my::cv"}, {key: "concurrency-async-blocking.LockableExtra", value: "my::mutex;my::shared_mutex"}, {key: "concurrency-async-blocking.FunctionsExtra", value: "my_sleep;my::sleep"}, {key: "concurrency-async-blocking.TypesExtra", value: "my::big_lock;my::other_lock"}]}'
+
+/* Poor man's declaration of std::mutex and friends */
+namespace std {
+namespace chrono {
+class seconds {
+public:
+  seconds(int);
+};
+} // namespace chrono
+
+class mutex {
+public:
+  void lock();
+
+  // non-std methods
+  void lock_suffix();
+  void prefix_lock();
+
+  template <typename Duration>
+  void try_lock_for(Duration);
+};
+class recursive_mutex {};
+class recursive_timed_mutex {};
+class shared_mutex {};
+class shared_timed_mutex {};
+class mutex_suffix {};
+class prefix_mutex {};
+
+template <typename Lock>
+class unique_lock {
+public:
+  unique_lock(Lock &);
+
+  void lock();
+  template <typename Duration>
+  void try_lock_for(Duration);
+
+  // non-std methods
+  void lock_suffix();
+  void prefix_lock();
+};
+
+} // namespace std
+
+namespace ns {
+class mutex {};
+} // namespace ns
+
+class mutex {};
+
+template <typename T>
+class nonlock {};
+
+namespace my {
+class mutex {
+public:
+  void lock();
+};
+class shared_mutex {};
+class non_mutex {
+public:
+  void lock();
+};
+} // namespace my
+
+void test_lockable() {
+  std::mutex m;
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: type std::mutex may sleep and is not coroutine-safe [concurrency-async-blocking]
+  ::std::mutex mns;
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: type ::std::mutex may sleep and is not coroutine-safe [concurrency-async-blocking]
+  std::shared_mutex sm;
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: type std::shared_mutex may sleep and is not coroutine-safe [concurrency-async-blocking]
+  std::recursive_mutex rm;
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: type std::recursive_mutex may sleep and is not coroutine-safe [concurrency-async-blocking]
+  std::recursive_timed_mutex rtm;
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: type std::recursive_timed_mutex may sleep and is not coroutine-safe [concurrency-async-blocking]
+  my::mutex mym;
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: type my::mutex may sleep and is not coroutine-safe [concurrency-async-blocking]
+
+  std::mutex_suffix m1;
+  std::prefix_mutex m2;
+  ns::mutex m3;
+  mutex m4;
+  my::non_mutex myn;
+
+  m.lock();
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: method lock may sleep and is not coroutine-safe [concurrency-async-blocking]
+  mns.lock();
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: method lock may sleep and is not coroutine-safe [concurrency-async-blocking]
+  mym.lock();
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: method lock may sleep and is not coroutine-safe [concurrency-async-blocking]
+  myn.lock();
+
+  m.lock_suffix();
+  m.prefix_lock();
+
+  std::unique_lock<std::mutex> lock(m);
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: type std::unique_lock<std::mutex> may sleep and is not coroutine-safe [concurrency-async-blocking]
+
+  std::unique_lock<std::mutex_suffix> l1(m1);
+  std::unique_lock<std::prefix_mutex> l2(m2);
+  std::unique_lock<ns::mutex> l3(m3);
+  std::unique_lock<mutex> l4(m4);
+
+  nonlock<std::mutex> nonlock;
+}
+
+void sleep(int);
+void nanosleep(int);
+void usleep(int);
+void xsleep(int);
+void sleepx(int);
+void system(const char *);
+int wait(int *);
+int waitpid(int, int *, int);
+int waitid(int idtype, int id, int *infop, int options);
+
+struct rusage {};
+using pid_t = int;
+pid_t wait3(int *status, int options,
+            struct rusage *rusage);
+
+pid_t wait4(pid_t pid, int *status, int options,
+            struct rusage *rusage);
+
+namespace std {
+namespace this_thread {
+void yield();
+
+template <typename Duration>
+void sleep_for(Duration);
+
+template <typename Duration>
+void sleep_until(Duration);
+} // namespace this_thread
+} // namespace std
+
+void test_std_thread() {
+  std::this_thread::yield();
+
+  std::chrono::seconds s(1);
+
+  std::this_thread::sleep_for(s);
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: function sleep_for may sleep and is not coroutine-safe [concurrency-async-blocking]
+  std::this_thread::sleep_until(s);
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: function sleep_until may sleep and is not coroutine-safe [concurrency-async-blocking]
+}
+
+void my_sleep();
+
+namespace my {
+void sleep();
+}
+
+void test_posix() {
+  sleep(1);
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: function sleep may sleep and is not coroutine-safe [concurrency-async-blocking]
+  ::sleep(1);
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: function sleep may sleep and is not coroutine-safe [concurrency-async-blocking]
+
+  ::xsleep(1);
+  xsleep(1);
+  ::sleepx(1);
+  sleepx(1);
+
+  my_sleep();
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: function my_sleep may sleep and is not coroutine-safe [concurrency-async-blocking]
+  my::sleep();
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: function sleep may sleep and is not coroutine-safe [concurrency-async-blocking]
+
+  system("ls");
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: function system may sleep and is not coroutine-safe [concurrency-async-blocking]
+
+  wait(0);
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: function wait may sleep and is not coroutine-safe [concurrency-async-blocking]
+  waitpid(0, 0, 0);
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: function waitpid may sleep and is not coroutine-safe [concurrency-async-blocking]
+  wait3(0, 0, 0);
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: function wait3 may sleep and is not coroutine-safe [concurrency-async-blocking]
+  wait4(0, 0, 0, 0);
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: function wait4 may sleep and is not coroutine-safe [concurrency-async-blocking]
+}
+
+namespace std {
+template <typename T>
+class atomic {
+public:
+  void wait(const T &);
+
+  static constexpr bool is_always_lock_free = false;
+};
+
+class atomic_flag {
+public:
+  void wait(bool);
+};
+
+template <>
+class atomic<short> {
+public:
+  void wait(const short &);
+
+  static constexpr bool is_always_lock_free = false;
+};
+
+template <>
+class atomic<char> {
+public:
+  void wait(const char &);
+
+  static constexpr bool is_always_lock_free = true;
+};
+
+template <>
+class atomic<long> {
+public:
+  void wait(const long &);
+
+  static constexpr bool is_always_lock_free{true};
+};
+
+template <>
+class atomic<long long> {
+public:
+  void wait(const char &);
+
+  static constexpr bool is_always_lock_free{false};
+};
+
+template <typename T, typename U>
+void atomic_wait(T *, U);
+
+template <typename T, typename U, typename V>
+void atomic_wait_explicit(T *, U, V);
+
+template <typename T>
+void atomic_flag_wait(T *, bool);
+
+template <typename T, typename V>
+void atomic_flag_wait_explicit(T *, bool, V);
+} // namespace std
+
+namespace boost {
+template <typename T>
+class atomic {
+public:
+  void wait(const T &);
+
+  static constexpr bool is_always_lock_free = true;
+};
+} // namespace boost
+
+void test_atomic() {
+  // TODO: std::atomic<int> ai;
+  // CHECKT-MESSAGES: :[[@LINE-1]]:3: warning: atomic is not always lockfree, may sleep and is not coroutine-safe [concurrency-async-blocking]
+  std::atomic<short> as;
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: atomic is not always lockfree, may sleep and is not coroutine-safe [concurrency-async-blocking]
+  std::atomic<char> ac;
+  std::atomic<long> al;
+  std::atomic<long long> all;
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: atomic is not always lockfree, may sleep and is not coroutine-safe [concurrency-async-blocking]
+
+  // TODO: ai.wait(0);
+  // CHECKT-MESSAGES: :[[@LINE-1]]:3: warning: method wait may sleep and is not coroutine-safe [concurrency-async-blocking]
+
+  ac.wait(0);
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: method wait may sleep and is not coroutine-safe [concurrency-async-blocking]
+
+  std::atomic_flag flag;
+  flag.wait(false);
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: function wait may sleep and is not coroutine-safe [concurrency-async-blocking]
+  std::atomic_flag_wait(&flag, false);
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: function atomic_flag_wait may sleep and is not coroutine-safe [concurrency-async-blocking]
+  std::atomic_flag_wait_explicit(&flag, false, 0);
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: function atomic_flag_wait_explicit may sleep and is not coroutine-safe [concurrency-async-blocking]
+
+  std::atomic_wait(&ac, 1);
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: function atomic_wait may sleep and is not coroutine-safe [concurrency-async-blocking]
+  std::atomic_wait_explicit(&ac, 1, 0);
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: function atomic_wait_explicit may sleep and is not coroutine-safe [concurrency-async-blocking]
+
+  boost::atomic<int> ba;
+  ba.wait(0);
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: method wait may sleep and is not coroutine-safe [concurrency-async-blocking]
+}
+
+namespace std {
+class thread {
+public:
+  void join();
+};
+
+class jthread {
+public:
+  ~jthread();
+  void join();
+};
+} // namespace std
+
+void test_thread() {
+  std::thread t;
+  t.join();
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: function join may sleep and is not coroutine-safe [concurrency-async-blocking]
+
+  std::jthread j;
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: type std::jthread may sleep and is not coroutine-safe [concurrency-async-blocking]
+  j.join();
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: function join may sleep and is not coroutine-safe [concurrency-async-blocking]
+}
+
+namespace std {
+class condition_variable {
+public:
+  void wait(unique_lock<std::mutex> &);
+
+  template <typename Duration>
+  void wait_for(unique_lock<std::mutex> &, Duration);
+
+  template <typename Duration>
+  void wait_until(unique_lock<std::mutex> &, Duration);
+};
+class barrier {
+public:
+  void wait();
+  void arrive_and_wait();
+};
+class latch {
+public:
+  void wait();
+  void arrive_and_wait();
+};
+
+template <typename T>
+class future {
+public:
+  void wait();
+
+  void get();
+};
+} // namespace std
+
+namespace my {
+class Future {
+public:
+  void wait();
+  void get();
+};
+class Cv {
+  void wait();
+};
+} // namespace my
+
+void test_waitable() {
+  std::mutex m;
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: type std::mutex may sleep and is not coroutine-safe [concurrency-async-blocking]
+  std::unique_lock<std::mutex> lock(m);
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: type std::unique_lock<std::mutex> may sleep and is not coroutine-safe [concurrency-async-blocking]
+
+  std::condition_variable cv;
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: type std::condition_variable may sleep and is not coroutine-safe [concurrency-async-blocking]
+  cv.wait(lock);
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: method wait may sleep and is not coroutine-safe [concurrency-async-blocking]
+  cv.wait_for(lock, std::chrono::seconds(1));
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: method wait_for may sleep and is not coroutine-safe [concurrency-async-blocking]
+  cv.wait_until(lock, std::chrono::seconds(1));
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: method wait_until may sleep and is not coroutine-safe [concurrency-async-blocking]
+
+  my::Future myf;
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: type my::Future may sleep and is not coroutine-safe [concurrency-async-blocking]
+  myf.wait();
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: method wait may sleep and is not coroutine-safe [concurrency-async-blocking]
+  myf.get();
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: method get may sleep and is not coroutine-safe [concurrency-async-blocking]
+
+  std::mutex_suffix ms;
+  std::unique_lock<std::mutex_suffix> mslock(ms);
+
+  std::latch l;
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: type std::latch may sleep and is not coroutine-safe [concurrency-async-blocking]
+  l.wait();
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: method wait may sleep and is not coroutine-safe [concurrency-async-blocking]
+  l.arrive_and_wait();
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: method arrive_and_wait may sleep and is not coroutine-safe [concurrency-async-blocking]
+
+  std::barrier b;
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: type std::barrier may sleep and is not coroutine-safe [concurrency-async-blocking]
+  b.wait();
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: method wait may sleep and is not coroutine-safe [concurrency-async-blocking]
+  b.arrive_and_wait();
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: method arrive_and_wait may sleep and is not coroutine-safe [concurrency-async-blocking]
+
+  std::future<int> f;
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: type std::future<int> may sleep and is not coroutine-safe [concurrency-async-blocking]
+  f.wait();
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: method wait may sleep and is not coroutine-safe [concurrency-async-blocking]
+  f.get();
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: method get may sleep and is not coroutine-safe [concurrency-async-blocking]
+}
+
+class X {
+  std::mutex m;
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: type std::mutex may sleep and is not coroutine-safe [concurrency-async-blocking]
+
+  std::unique_lock<std::mutex> lock;
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: type std::unique_lock<std::mutex> may sleep and is not coroutine-safe [concurrency-async-blocking]
+};
+
+void mtx_lock(void *);
+void mtx_timedlock(void *, void *);
+int thrd_sleep(void *, void *);
+int thrd_join(void *, int *);
+int cnd_wait(void *, void *);
+int cnd_timedwait(void *, void *, void *);
+
+void test_c11() {
+  mtx_lock(0);
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: function mtx_lock may sleep and is not coroutine-safe [concurrency-async-blocking]
+  mtx_timedlock(0, 0);
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: function mtx_timedlock may sleep and is not coroutine-safe [concurrency-async-blocking]
+
+  thrd_sleep(0, 0);
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: function thrd_sleep may sleep and is not coroutine-safe [concurrency-async-blocking]
+  thrd_join(0, 0);
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: function thrd_join may sleep and is not coroutine-safe [concurrency-async-blocking]
+
+  cnd_wait(0, 0);
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: function cnd_wait may sleep and is not coroutine-safe [concurrency-async-blocking]
+  cnd_timedwait(0, 0, 0);
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: function cnd_timedwait may sleep and is not coroutine-safe [concurrency-async-blocking]
+}
+
+namespace my {
+class big_lock {};
+
+class other_lock {};
+
+class other {};
+} // namespace my
+
+void test_types() {
+  my::big_lock lock;
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: type my::big_lock may sleep and is not coroutine-safe [concurrency-async-blocking]
+  my::other_lock other_lock;
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: type my::other_lock may sleep and is not coroutine-safe [concurrency-async-blocking]
+  my::other other;
+}
+
+// TODO: remove CHECKT-MESSAGES
Index: clang-tools-extra/test/clang-tidy/checkers/concurrency-async-blocking.c
===================================================================
--- /dev/null
+++ clang-tools-extra/test/clang-tidy/checkers/concurrency-async-blocking.c
@@ -0,0 +1,38 @@
+// RUN: %check_clang_tidy %s concurrency-async-blocking %t
+
+void test_c11() {
+  mtx_lock(0);
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: function mtx_lock may sleep and is not coroutine-safe [concurrency-async-blocking]
+  mtx_timedlock(0, 0);
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: function mtx_timedlock may sleep and is not coroutine-safe [concurrency-async-blocking]
+
+  thrd_sleep(0, 0);
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: function thrd_sleep may sleep and is not coroutine-safe [concurrency-async-blocking]
+  thrd_join(0, 0);
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: function thrd_join may sleep and is not coroutine-safe [concurrency-async-blocking]
+
+  cnd_wait(0, 0);
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: function cnd_wait may sleep and is not coroutine-safe [concurrency-async-blocking]
+  cnd_timedwait(0, 0, 0);
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: function cnd_timedwait may sleep and is not coroutine-safe [concurrency-async-blocking]
+}
+
+void test_posix() {
+  sleep(1);
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: function sleep may sleep and is not coroutine-safe [concurrency-async-blocking]
+
+  xsleep(1);
+  sleepx(1);
+
+  system("ls");
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: function system may sleep and is not coroutine-safe [concurrency-async-blocking]
+
+  wait(0);
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: function wait may sleep and is not coroutine-safe [concurrency-async-blocking]
+  waitpid(0, 0, 0);
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: function waitpid may sleep and is not coroutine-safe [concurrency-async-blocking]
+  wait3(0, 0, 0);
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: function wait3 may sleep and is not coroutine-safe [concurrency-async-blocking]
+  wait4(0, 0, 0, 0);
+  // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: function wait4 may sleep and is not coroutine-safe [concurrency-async-blocking]
+}
Index: clang-tools-extra/docs/clang-tidy/checks/list.rst
===================================================================
--- clang-tools-extra/docs/clang-tidy/checks/list.rst
+++ clang-tools-extra/docs/clang-tidy/checks/list.rst
@@ -139,6 +139,7 @@
    `clang-analyzer-valist.CopyToSelf <clang-analyzer-valist.CopyToSelf.html>`_,
    `clang-analyzer-valist.Uninitialized <clang-analyzer-valist.Uninitialized.html>`_,
    `clang-analyzer-valist.Unterminated <clang-analyzer-valist.Unterminated.html>`_,
+   `concurrency-async-blocking <concurrency-async-blocking.html>`_,
    `concurrency-mt-unsafe <concurrency-mt-unsafe.html>`_,
    `cppcoreguidelines-avoid-goto <cppcoreguidelines-avoid-goto.html>`_,
    `cppcoreguidelines-avoid-non-const-global-variables <cppcoreguidelines-avoid-non-const-global-variables.html>`_,
Index: clang-tools-extra/docs/clang-tidy/checks/concurrency-async-blocking.rst
===================================================================
--- /dev/null
+++ clang-tools-extra/docs/clang-tidy/checks/concurrency-async-blocking.rst
@@ -0,0 +1,92 @@
+.. title:: clang-tidy - concurrency-async-blocking
+
+concurrency-async-blocking
+==========================
+
+Checks for some synchronous functions and types that volunteerly preempt system thread.
+Volunteer preemption of a system thread in asynchronous code
+(e.g. in coroutines/fibers/green threads) is a bug that prevents the current
+thread from executing other coroutines/etc. and negatively affects overall
+process performance.
+
+The preemptive functions/types can be separated into the following categories:
+ - explicit sleep(3)-like functions
+ - sleeping/waiting synchronization primitives
+ - io/filesystem stuff (not implemented yet)
+
+The check searches for:
+ - C++ synchronization primitives
+ - C11 synchronization primitives
+ - POSIX synchronization primitives
+ - some POSIX blocking functions
+ - some blocking Linux syscalls
+ - some Boost.Thread synchronization primitives
+
+.. option:: LockableExtra
+
+  Specifies additional lock type names separated with semicolon. Usually they
+  implement C++17 BasicLockable, Lockable, TimedLockable, Mutex, or TimedMutex
+  requirement or has one or several methods from the following list:
+    - `lock`
+    - `try_lock_for`
+    - `try_lock_until`
+    - `lock_shared`
+    - `try_lock_shared_for`
+    - `try_lock_shared_until`
+
+  The check searches for explicit method calls from the list above and implicit
+  locking using std::lock_guard and other RAII sychronization primitive
+  ownership wrappers.
+
+  The list of classes which are already handled (and their boost:: twins):
+    - `std::mutex`
+    - `std::timed_mutex`
+    - `std::recursive_mutex`
+    - `std::recursive_timed_mutex`
+    - `std::shared_mutex`
+    - `std::shared_timed_mutex`
+
+.. option:: WaitableExtra
+
+  Specifies additional future-like types separated with semicolon.
+  The type must implement one or several methods from the following list:
+    - `get`
+    - `wait`
+    - `wait_for`
+    - `wait_until`
+    - `arrive_and_wait`
+
+  The list of classes which are already handled (and their boost:: twins):
+    - `std::condition_variable`
+    - `std::latch`
+    - `std::barrier`
+    - `std::future<T>`
+    - `std::shared_future`
+
+.. option:: AtomicNonLockFree
+
+  Specifies whether search for std::atomic types which are not always lock-free.
+  Non-lockfree atomics use std synchronization primitives (e.g. std::mutex), so
+  they may block current system thread for a while. If such accesses are
+  frequent, too much execution time might be spent on waiting due to mutex
+  contention.
+
+  If set to true, checks `std::atomic<T>::is_always_lock_free` and warns about
+  `std::atomic<T>`.
+
+.. option:: FunctionsExtra
+
+  Extra functions that may block current system thread, separated with semicolon.
+  Usually the function list should be compiled for third-party libraries or
+  user-defined known-to-be-blocking functions.
+
+  Already handled:
+    - C11 thread/mutex/condition variable functions
+    - some POSIX functions
+    - some Linux syscalls
+
+.. option:: TypesExtra
+
+  Extra types that may block current system thread, separated with semicolon.
+  Usually the type list should be compiled for third-party libraries or
+  user-defined known-to-be-blocking types.
Index: clang-tools-extra/docs/ReleaseNotes.rst
===================================================================
--- clang-tools-extra/docs/ReleaseNotes.rst
+++ clang-tools-extra/docs/ReleaseNotes.rst
@@ -121,6 +121,12 @@
   Finds structs that are inefficiently packed or aligned, and recommends
   packing and/or aligning of said structs as needed.
 
+- New :doc:`concurrency-async-blocking
+  <clang-tidy/checks/concurrency-async-blocking>` check.
+
+  Checks for some blocking functions and types that volunteerly preempt
+  system thread.
+
 - New :doc:`cppcoreguidelines-prefer-member-initializer
   <clang-tidy/checks/cppcoreguidelines-prefer-member-initializer>` check.
 
Index: clang-tools-extra/clang-tidy/concurrency/ConcurrencyTidyModule.cpp
===================================================================
--- clang-tools-extra/clang-tidy/concurrency/ConcurrencyTidyModule.cpp
+++ clang-tools-extra/clang-tidy/concurrency/ConcurrencyTidyModule.cpp
@@ -9,6 +9,7 @@
 #include "../ClangTidy.h"
 #include "../ClangTidyModule.h"
 #include "../ClangTidyModuleRegistry.h"
+#include "AsyncBlockingCheck.h"
 #include "MtUnsafeCheck.h"
 
 namespace clang {
@@ -18,6 +19,8 @@
 class ConcurrencyModule : public ClangTidyModule {
 public:
   void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override {
+    CheckFactories.registerCheck<AsyncBlockingCheck>(
+        "concurrency-async-blocking");
     CheckFactories.registerCheck<concurrency::MtUnsafeCheck>(
         "concurrency-mt-unsafe");
   }
Index: clang-tools-extra/clang-tidy/concurrency/CMakeLists.txt
===================================================================
--- clang-tools-extra/clang-tidy/concurrency/CMakeLists.txt
+++ clang-tools-extra/clang-tidy/concurrency/CMakeLists.txt
@@ -4,6 +4,7 @@
   )
 
 add_clang_library(clangTidyConcurrencyModule
+  AsyncBlockingCheck.cpp
   ConcurrencyTidyModule.cpp
   MtUnsafeCheck.cpp
 
Index: clang-tools-extra/clang-tidy/concurrency/AsyncBlockingCheck.h
===================================================================
--- /dev/null
+++ clang-tools-extra/clang-tidy/concurrency/AsyncBlockingCheck.h
@@ -0,0 +1,45 @@
+//===--- AsyncBlockingCheck.h - clang-tidy ----------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CONCURRENCY_ASYNCBLOCKINGCHECK_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CONCURRENCY_ASYNCBLOCKINGCHECK_H
+
+#include "../ClangTidyCheck.h"
+
+namespace clang {
+namespace tidy {
+namespace concurrency {
+
+/// Checks that non-coroutine-safe functions are not used.
+///
+/// For the user-facing documentation see:
+/// http://clang.llvm.org/extra/clang-tidy/checks/concurrency-async-blocking.html
+class AsyncBlockingCheck : public ClangTidyCheck {
+public:
+  AsyncBlockingCheck(StringRef Name, ClangTidyContext *Context);
+
+  void storeOptions(ClangTidyOptions::OptionMap &Opts) override;
+
+  void registerMatchers(ast_matchers::MatchFinder *Finder) override;
+
+  void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
+
+private:
+  const std::string LockableExtra;
+  const std::string WaitableExtra;
+  const bool AtomicNonLockFree;
+
+  const std::string FunctionsExtra;
+  const std::string TypesExtra;
+};
+
+} // namespace concurrency
+} // namespace tidy
+} // namespace clang
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CONCURRENCY_ASYNCBLOCKINGCHECK_H
Index: clang-tools-extra/clang-tidy/concurrency/AsyncBlockingCheck.cpp
===================================================================
--- /dev/null
+++ clang-tools-extra/clang-tidy/concurrency/AsyncBlockingCheck.cpp
@@ -0,0 +1,334 @@
+//===--- AsyncBlockingCheck.cpp - clang-tidy ----------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "AsyncBlockingCheck.h"
+#include "../utils/OptionsUtils.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+
+using namespace clang::ast_matchers;
+
+static const auto kName = "name";
+static const auto kType = "type";
+static const auto kFunction = "function";
+static const auto kMethod = "method";
+static const auto kAtomic = "atomic";
+static const auto kLockfree = "lockfree";
+
+static std::vector<clang::StringRef>
+toVector(const std::vector<clang::StringRef> Base, clang::StringRef Extra) {
+  llvm::SmallVector<clang::StringRef, 4> Tmp{Base.begin(), Base.end()};
+  if (!Extra.empty()) {
+    Extra.split(Tmp, ";");
+  }
+
+  return {Tmp.begin(), Tmp.end()};
+}
+
+static const std::vector<clang::StringRef> LockableBase = {
+    /* C++ std */
+    "std::mutex",                 //
+    "std::timed_mutex",           //
+    "std::recursive_mutex",       //
+    "std::recursive_timed_mutex", //
+    "std::shared_mutex",          //
+    "std::shared_timed_mutex",    //
+
+    /* Boost.Thread */
+    "boost::mutex",                 //
+    "boost::timed_mutex",           //
+    "boost::recursive_mutex",       //
+    "boost::recursive_timed_mutex", //
+    "boost::shared_mutex",          //
+    "boost::upgrade_mutex",         //
+};
+
+static const std::vector<clang::StringRef> LockableMethods = {
+    "lock",                  //
+    "try_lock_for",          //
+    "try_lock_until",        //
+    "lock_shared",           //
+    "try_lock_shared_for",   //
+    "try_lock_shared_until", //
+};
+
+static const std::vector<clang::StringRef> LockGuardTemplates = {
+    /* C++ std */
+    "std::lock_guard",  //
+    "std::scoped_lock", //
+    "std::unique_lock", //
+    "std::shared_lock", //
+
+    /* Boost.Thread */
+    "boost::unique_lock",            //
+    "boost::shared_lock",            //
+    "boost::upgrade_lock",           //
+    "boost::upgrade_to_unique_lock", //
+};
+
+static const std::vector<clang::StringRef> AtomicNames = {
+    "std::atomic",
+    "boost::atomic",
+};
+
+static const std::vector<clang::StringRef> WaitableBase = {
+    /* C++ std */
+    "std::condition_variable", //
+    "std::latch",         //
+    "std::barrier",       //
+    "std::future",        //
+    "std::shared_future", //
+    // TODO: std::condition_variable_any?
+
+    /* Boost.Thread */
+    "boost::condition_variable", //
+    "boost::latch",              //
+    "boost::barrier",            //
+    "boost::future",             //
+    "boost::shared_future",      //
+};
+
+static const std::vector<clang::StringRef> WaitableMethods = {
+    "get",             //
+    "wait",            //
+    "wait_for",        //
+    "wait_until",      //
+    "arrive_and_wait", //
+};
+
+static const std::vector<clang::StringRef> BlockingFunctions = {
+    /* C++ std */
+    "std::this_thread::sleep_for", //
+    "std::this_thread::sleep_until",
+    // skip std::this_thread::yield()
+
+    "std::thread::join",     //
+    "std::jthread::jthread", //
+    "std::jthread::join",    //
+
+    // std::atomic::wait has a custom matcher
+    "std::atomic_flag::wait", //
+
+    "std::atomic_wait",               //
+    "std::atomic_wait_explicit",      //
+    "std::atomic_flag_wait",          //
+    "std::atomic_flag_wait_explicit", //
+
+    /* C11 */
+    "::thrd_sleep", //
+    "::thrd_join",  //
+    // skip thrd_yield()
+
+    "::mtx_lock",      //
+    "::mtx_timedlock", //
+
+    "::cnd_wait",      //
+    "::cnd_timedwait", //
+
+    /* POSIX (pthread) */
+    "::pthread_barrier_wait",   //
+    "::pthread_cond_timedwait", //
+    "::pthread_cond_wait",      //
+    "::pthread_join",           //
+
+    "::pthread_mutex_lock",      //
+    "::pthread_mutex_timedlock", //
+
+    "::pthread_rwlock_timedrdlock", //
+    "::pthread_rwlock_timedwrlock", //
+    "::pthread_rwlock_rdlock",      //
+    "::pthread_rwlock_wrlock",      //
+
+    "::pthread_spin_lock",    //
+    "::pthread_timedjoin_np", //
+
+    /* POSIX */
+    "::wait",            //
+    "::waitpid",         //
+    "::system",          //
+    "::sleep",           //
+    "::usleep",          //
+    "::nanosleep",       //
+    "::clock_nanosleep", //
+    "::accept",          //
+    "::mq_receive",      //
+    "::mq_timedreceive", //
+    "::msgsnd",          //
+    "::msgrcv",          //
+    "::poll",            //
+    "::pselect",         //
+    "::select",          //
+    "::recv",            //
+    "::recvmsg",         //
+    "::recvfrom",        //
+    "::semop",           //
+    "::semtimedop",      //
+    "::openlog",         //
+    "::syslog",          //
+    "::vsyslog",         //
+    "::waitid",          //
+
+    /* Linux syscalls */
+    "::wait3",       //
+    "::wait4",       //
+    "::epoll_wait",  //
+    "::epoll_pwait", //
+    "::ppoll",       //
+
+    /* Boost.Thread */
+    "boost::this_thread::sleep",     //
+    "boost::this_thread::sleep_for", //
+    "boost::this_thread::sleep_until",
+
+    "boost::thread::join",       //
+    "boost::thread::timed_join", //
+};
+
+static const std::vector<clang::StringRef> TypesBase = {
+    /* C++ std */
+    "std::counting_semaphore", //
+    "std::binary_semaphore",   //
+    "std::jthread",            // due to dtr
+};
+
+namespace clang {
+namespace tidy {
+namespace concurrency {
+
+AsyncBlockingCheck::AsyncBlockingCheck(StringRef Name,
+                                       ClangTidyContext *Context)
+    : ClangTidyCheck(Name, Context),
+      LockableExtra(Options.get("LockableExtra", "")),
+      WaitableExtra(Options.get("WaitableExtra", "")),
+      AtomicNonLockFree(Options.get("AtomicNonLockFree", true)),
+      FunctionsExtra(Options.get("FunctionsExtra", "")),
+      TypesExtra(Options.get("TypesExtra", "")) {}
+
+void AsyncBlockingCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
+  Options.store(Opts, "LockableExtra", LockableExtra);
+  Options.store(Opts, "WaitableExtra", WaitableExtra);
+  Options.store(Opts, "AtomicNonLockFree", AtomicNonLockFree);
+  Options.store(Opts, "FunctionsExtra", FunctionsExtra);
+  Options.store(Opts, "TypesExtra", TypesExtra);
+}
+
+void AsyncBlockingCheck::registerMatchers(MatchFinder *Finder) {
+  // std::mutex
+  auto Lockable = toVector(LockableBase, StringRef(LockableExtra));
+  Finder->addMatcher(
+      valueDecl(hasType(cxxRecordDecl(hasAnyName(Lockable)))).bind(kType),
+      this);
+
+  // User code may use already created std::mutex, catch its usage
+  // std::mutex::lock()
+  // Note: exception for std::atomic as its type is handled separately
+  Finder->addMatcher(
+      cxxMemberCallExpr(
+          callee(functionDecl(hasAnyName(LockableMethods)).bind(kName)),
+          on(hasType(cxxRecordDecl(hasAnyName(Lockable)))))
+          .bind(kMethod),
+      this);
+
+  // std::future<T>
+  auto Waitable = toVector(WaitableBase, StringRef(WaitableExtra));
+  Finder->addMatcher(
+      valueDecl(hasType(cxxRecordDecl(hasAnyName(Waitable)))).bind(kType),
+      this);
+
+  // std::future<T>::wait()
+  Finder->addMatcher(
+      cxxMemberCallExpr(
+          callee(functionDecl(hasAnyName(WaitableMethods)).bind(kName)),
+          on(hasType(cxxRecordDecl(
+              anyOf(hasAnyName(Waitable), hasAnyName(AtomicNames))))))
+          .bind(kMethod),
+      this);
+
+  // sleep()
+  auto SleepingFunctionNames =
+      toVector(BlockingFunctions, StringRef(FunctionsExtra));
+  Finder->addMatcher(
+      callExpr(
+          callee(functionDecl(hasAnyName(SleepingFunctionNames)).bind(kName)))
+          .bind(kFunction),
+      this);
+
+  // std::jthread
+  auto TypeNames = toVector(TypesBase, StringRef(TypesExtra));
+  Finder->addMatcher(
+      valueDecl(hasType(cxxRecordDecl(hasAnyName(TypeNames)))).bind(kType),
+      this);
+
+  // std::atomic<T> which is !is_always_lock_free
+  if (AtomicNonLockFree) {
+    Finder->addMatcher(
+        valueDecl(
+            hasType(cxxRecordDecl(
+                has(varDecl(hasName("is_always_lock_free")).bind(kLockfree)),
+                hasAnyName(AtomicNames))))
+            .bind(kAtomic),
+        this);
+  }
+
+  // std::unique_lock<std::mutex>
+  Finder->addMatcher(
+      valueDecl(hasType(qualType(hasDeclaration(classTemplateSpecializationDecl(
+                    hasAnyName(LockGuardTemplates),
+                    hasTemplateArgument(
+                        0, refersToType(qualType(hasDeclaration(
+                               cxxRecordDecl(hasAnyName(Lockable)))))))))))
+          .bind(kType),
+      this);
+}
+
+void AsyncBlockingCheck::check(const MatchFinder::MatchResult &Result) {
+  auto *Name = Result.Nodes.getNodeAs<NamedDecl>(kName);
+
+  const auto *D = Result.Nodes.getNodeAs<ValueDecl>(kType);
+  if (D) {
+    diag(D->getBeginLoc(), "type " + D->getType().getAsString() +
+                               " may sleep and is not coroutine-safe")
+        << SourceRange(D->getBeginLoc(), D->getEndLoc());
+  }
+
+  const auto *CE = Result.Nodes.getNodeAs<CXXMemberCallExpr>(kMethod);
+  if (CE) {
+    if (Name) {
+      diag(CE->getBeginLoc(), "method " + Name->getNameAsString() +
+                                  " may sleep and is not coroutine-safe")
+          << SourceRange(CE->getBeginLoc(), CE->getEndLoc());
+    }
+  }
+
+  const auto *E = Result.Nodes.getNodeAs<CallExpr>(kFunction);
+  if (E) {
+    if (Name) {
+      diag(E->getBeginLoc(), "function " + Name->getNameAsString() +
+                                 " may sleep and is not coroutine-safe")
+          << SourceRange(E->getBeginLoc(), E->getEndLoc());
+    }
+  }
+
+  const auto *Atomic = Result.Nodes.getNodeAs<ValueDecl>(kAtomic);
+  if (Atomic) {
+    const auto *Lockfree = Result.Nodes.getNodeAs<VarDecl>(kLockfree);
+    if (Lockfree) {
+      const auto *EV = Lockfree->ensureEvaluatedStmt();
+      if (EV && EV->Evaluated.getKind() == APValue::ValueKind::Int &&
+          EV->Evaluated.getInt() == 0) {
+        diag(Atomic->getBeginLoc(), "atomic is not always lockfree, may sleep "
+                                    "and is not coroutine-safe")
+            << SourceRange(Atomic->getBeginLoc(), Atomic->getEndLoc());
+      }
+    }
+  }
+}
+
+} // namespace concurrency
+} // namespace tidy
+} // namespace clang
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to