Index: clang-tools-extra/clang-tidy/concurrency/AsyncBlockingCheck.h =================================================================== --- /dev/null +++ clang-tools-extra/clang-tidy/concurrency/AsyncBlockingCheck.h @@ -0,0 +1,40 @@ +//===--- 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 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,304 @@ +//===--- 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 char Identifier[] = "id"; +static const char Typename[] = "type"; +static const char Function[] = "function"; +static const char Method[] = "method"; + +static std::vector +toVector(llvm::ArrayRef Base, llvm::StringRef Extra) { + llvm::SmallVector Tmp{Base.begin(), Base.end()}; + if (!Extra.empty()) { + Extra.split(Tmp, ";"); + } + + return {Tmp.begin(), Tmp.end()}; +} + +static constexpr llvm::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 constexpr llvm::StringRef LockableMethods[] = { + "lock", // + "try_lock_for", // + "try_lock_until", // + "lock_shared", // + "try_lock_shared_for", // + "try_lock_shared_until", // +}; + +static constexpr llvm::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 constexpr llvm::StringRef AtomicNames[] = { + "::std::atomic", + "::boost::atomic", +}; + +static constexpr llvm::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 constexpr llvm::StringRef WaitableMethods[] = { + "get", // + "wait", // + "wait_for", // + "wait_until", // + "arrive_and_wait", // +}; + +static constexpr llvm::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 constexpr llvm::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", "")), + 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, "FunctionsExtra", FunctionsExtra); + Options.store(Opts, "TypesExtra", TypesExtra); +} + +void AsyncBlockingCheck::registerMatchers(MatchFinder *Finder) { + // std::mutex + auto Lockable = toVector(LockableBase, LockableExtra); + Finder->addMatcher( + valueDecl(hasType(cxxRecordDecl(hasAnyName(Lockable)).bind(Identifier))) + .bind(Typename), + 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(Identifier)), + on(hasType(cxxRecordDecl(hasAnyName(Lockable))))) + .bind(Method), + this); + + // std::future + auto Waitable = toVector(WaitableBase, StringRef(WaitableExtra)); + Finder->addMatcher( + valueDecl(hasType(cxxRecordDecl(hasAnyName(Waitable)).bind(Identifier))) + .bind(Typename), + this); + + // std::future::wait() + Finder->addMatcher( + cxxMemberCallExpr( + callee(functionDecl(hasAnyName(WaitableMethods)).bind(Identifier)), + on(hasType(cxxRecordDecl( + anyOf(hasAnyName(Waitable), hasAnyName(AtomicNames)))))) + .bind(Method), + this); + + // sleep() + auto SleepingFunctionNames = + toVector(BlockingFunctions, StringRef(FunctionsExtra)); + Finder->addMatcher( + callExpr( + callee( + functionDecl(hasAnyName(SleepingFunctionNames)).bind(Identifier))) + .bind(Function), + this); + + // std::jthread + auto TypeNames = toVector(TypesBase, StringRef(TypesExtra)); + Finder->addMatcher( + valueDecl(hasType(cxxRecordDecl(hasAnyName(TypeNames)).bind(Identifier))) + .bind(Typename), + this); + + // std::unique_lock + Finder->addMatcher( + valueDecl(hasType(qualType(hasDeclaration( + classTemplateSpecializationDecl( + hasAnyName(LockGuardTemplates), + hasTemplateArgument( + 0, refersToType(qualType(hasDeclaration( + cxxRecordDecl(hasAnyName(Lockable))))))) + .bind(Identifier))))) + .bind(Typename), + this); +} + +void AsyncBlockingCheck::check(const MatchFinder::MatchResult &Result) { + const auto *Name = Result.Nodes.getNodeAs(Identifier); + + if (const auto *D = Result.Nodes.getNodeAs(Typename)) { + assert(Name); + diag(D->getBeginLoc(), "type %0 may sleep and is not coroutine-safe") + << Name << D->getSourceRange(); + } + + if (const auto *CE = Result.Nodes.getNodeAs(Method)) { + assert(Name); + diag(CE->getBeginLoc(), "method %0 may sleep and is not coroutine-safe") + << Name << CE->getSourceRange(); + } + + if (const auto *E = Result.Nodes.getNodeAs(Function)) { + assert(Name); + diag(E->getBeginLoc(), "function %0 may sleep and is not coroutine-safe") + << Name << E->getSourceRange(); + } +} + +} // namespace concurrency +} // namespace tidy +} // namespace clang 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/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( + "concurrency-async-blocking"); CheckFactories.registerCheck( "concurrency-mt-unsafe"); } 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 + ` check. + + Checks for some blocking functions and types that volunteerly preempt + system thread. + - New :doc:`cppcoreguidelines-prefer-member-initializer ` check. 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,89 @@ +.. 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 sleeping functions (e.g. ``sleep(3)``) +* sleeping/waiting synchronization primitives (e.g. ``std::mutex``) +* 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`` + * ``std::shared_future`` + +.. 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/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.Uninitialized `_, `clang-analyzer-valist.Unterminated `_, + `concurrency-async-blocking `_, `concurrency-mt-unsafe `_, `cppcoreguidelines-avoid-goto `_, `cppcoreguidelines-avoid-non-const-global-variables `_, 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,62 @@ +// RUN: %check_clang_tidy %s concurrency-async-blocking %t + +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] +} + +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); + +struct rusage {}; +#define 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); + +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/test/clang-tidy/checkers/concurrency-async-blocking.cpp =================================================================== --- /dev/null +++ clang-tools-extra/test/clang-tidy/checkers/concurrency-async-blocking.cpp @@ -0,0 +1,417 @@ +// 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 + 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 +class unique_lock { +public: + unique_lock(Lock &); + + void lock(); + template + 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 +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 'mutex' may sleep and is not coroutine-safe [concurrency-async-blocking] + ::std::mutex mns; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: type 'mutex' may sleep and is not coroutine-safe [concurrency-async-blocking] + std::shared_mutex sm; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: type 'shared_mutex' may sleep and is not coroutine-safe [concurrency-async-blocking] + std::recursive_mutex rm; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: type 'recursive_mutex' may sleep and is not coroutine-safe [concurrency-async-blocking] + std::recursive_timed_mutex rtm; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: type 'recursive_timed_mutex' may sleep and is not coroutine-safe [concurrency-async-blocking] + my::mutex mym; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: type '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 lock(m); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: type 'unique_lock' may sleep and is not coroutine-safe [concurrency-async-blocking] + + std::unique_lock l1(m1); + std::unique_lock l2(m2); + std::unique_lock l3(m3); + std::unique_lock l4(m4); + + nonlock 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 +void sleep_for(Duration); + +template +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 +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 { +public: + void wait(const char &); + + static constexpr bool is_always_lock_free = true; +}; + +template +void atomic_wait(T *, U); + +template +void atomic_wait_explicit(T *, U, V); + +void atomic_flag_wait(atomic_flag *, bool); + +template +void atomic_flag_wait_explicit(atomic_flag *, bool, V); +} // namespace std + +namespace boost { +template +class atomic { +public: + void wait(const T &); + + static constexpr bool is_always_lock_free = true; +}; +} // namespace boost + +void test_atomic() { + std::atomic ai; + std::atomic ac; + + ai.wait(0); + // CHECK-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, int>' 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, int, int>' may sleep and is not coroutine-safe [concurrency-async-blocking] + + boost::atomic 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 '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 &); + + template + void wait_for(unique_lock &, Duration); + + template + void wait_until(unique_lock &, Duration); +}; +class barrier { +public: + void wait(); + void arrive_and_wait(); +}; +class latch { +public: + void wait(); + void arrive_and_wait(); +}; + +template +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 'mutex' may sleep and is not coroutine-safe [concurrency-async-blocking] + std::unique_lock lock(m); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: type 'unique_lock' may sleep and is not coroutine-safe [concurrency-async-blocking] + + std::condition_variable cv; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: type '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 '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 mslock(ms); + + std::latch l; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: type '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 '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 f; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: type 'future' 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 'mutex' may sleep and is not coroutine-safe [concurrency-async-blocking] + + std::unique_lock lock; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: type 'unique_lock' 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 'big_lock' may sleep and is not coroutine-safe [concurrency-async-blocking] + my::other_lock other_lock; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: type 'other_lock' may sleep and is not coroutine-safe [concurrency-async-blocking] + my::other other; +}