Index: clang-tools-extra/clang-tidy/concurrency/AsyncNoNewThreadsCheck.h =================================================================== --- /dev/null +++ clang-tools-extra/clang-tidy/concurrency/AsyncNoNewThreadsCheck.h @@ -0,0 +1,38 @@ +//===--- AsyncNoNewThreadsCheck.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_ASYNCNONEWTHREADSCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CONCURRENCY_ASYNCNONEWTHREADSCHECK_H + +#include "../ClangTidyCheck.h" + +namespace clang { +namespace tidy { +namespace concurrency { + +/// Checks for types/functions that create new system threads +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/concurrency-async-no-new-threads.html +class AsyncNoNewThreadsCheck : public ClangTidyCheck { +public: + AsyncNoNewThreadsCheck(StringRef Name, ClangTidyContext *Context); + void storeOptions(ClangTidyOptions::OptionMap &Opts); + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + const std::string FunctionsExtra; + const std::string TypesExtra; +}; + +} // namespace concurrency +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CONCURRENCY_ASYNCNONEWTHREADSCHECK_H Index: clang-tools-extra/clang-tidy/concurrency/AsyncNoNewThreadsCheck.cpp =================================================================== --- /dev/null +++ clang-tools-extra/clang-tidy/concurrency/AsyncNoNewThreadsCheck.cpp @@ -0,0 +1,139 @@ +//===--- AsyncNoNewThreadsCheck.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 "AsyncNoNewThreadsCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +static const char Function[] = "func"; +static const char TypeName[] = "type"; +static const char Identifier[] = "id"; +static const char Name[] = "name"; + +static const clang::StringRef ThreadFunctions[] = { + /* C++ std */ + "::std::async", // + + /* Boost.Thread */ + "::boost::async", + + /* POSIX */ + "::pthread_create", // + + /* ะก11 */ + "::thrd_create", // + + /* Linux */ + "::fork", // + "::vfork", // + "::clone", // + "::__clone2", // + "::clone3", // + + /* WinAPI */ + "CreateThread", // + "CreateRemoteThread", // + "CreateRemoteThreadEx", // + "_beginthread", // + "_beginthreadex", // +}; + +static const clang::StringRef ThreadTypes[] = { + /* C++ std */ + "::std::thread", // + "::std::jthread", // + "::std::execution::parallel_policy", // + "::std::execution::parallel_unsequenced_policy", // + "::std::execution::unsequenced_policy", // + + /* Boost.Thread */ + "::boost::thread", // + "::boost::thread_group", // + "::boost::scoped_thread", // + + /* Boost.Compute */ + "::boost::compute::device", // +}; + +static const clang::StringRef ThreadIdentifiers[] = { + /* C++ std */ + "::std::execution::par", // + "::std::execution::par_unseq", // + "::std::execution::unseq", // +}; + +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()}; +} + +namespace clang { +namespace tidy { +namespace concurrency { + +AsyncNoNewThreadsCheck::AsyncNoNewThreadsCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + FunctionsExtra(Options.get("FunctionsExtra", "")), + TypesExtra(Options.get("TypesExtra", "")) {} + +void AsyncNoNewThreadsCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "FunctionsExtra", FunctionsExtra); + Options.store(Opts, "TypesExtra", TypesExtra); +} + +void AsyncNoNewThreadsCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher( + callExpr(callee(functionDecl( + hasAnyName(toVector(ThreadFunctions, FunctionsExtra))) + .bind(Name))) + .bind(Function), + this); + + Finder->addMatcher( + valueDecl( + hasType(cxxRecordDecl(hasAnyName(toVector(ThreadTypes, TypesExtra))) + .bind(Name))) + .bind(TypeName), + this); + + Finder->addMatcher( + declRefExpr( + hasDeclaration(namedDecl(hasAnyName(ThreadIdentifiers)).bind(Name))) + .bind(Identifier), + this); +} + +void AsyncNoNewThreadsCheck::check(const MatchFinder::MatchResult &Result) { + const auto *N = Result.Nodes.getNodeAs(Name); + assert(N); + + if (const auto *CE = Result.Nodes.getNodeAs(Function)) { + diag(CE->getBeginLoc(), "function %0 creates new threads") + << N << CE->getSourceRange(); + } else if (const auto *Decl = Result.Nodes.getNodeAs(TypeName)) { + diag(Decl->getBeginLoc(), "type %0 creates new threads") + << N << Decl->getSourceRange(); + } else if (const auto *Id = Result.Nodes.getNodeAs(Identifier)) { + diag(Id->getBeginLoc(), "use of %0 creates new threads") + << N << Id->getSourceRange(); + } else { + assert(false); + } +} + +} // 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 + AsyncNoNewThreadsCheck.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 "AsyncNoNewThreadsCheck.h" #include "MtUnsafeCheck.h" namespace clang { @@ -18,6 +19,8 @@ class ConcurrencyModule : public ClangTidyModule { public: void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override { + CheckFactories.registerCheck( + "concurrency-async-no-new-threads"); 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,11 @@ Finds structs that are inefficiently packed or aligned, and recommends packing and/or aligning of said structs as needed. +- New :doc:`concurrency-async-no-new-threads + ` check. + + Finds functions and types that create new system threads. + - New :doc:`cppcoreguidelines-prefer-member-initializer ` check. Index: clang-tools-extra/docs/clang-tidy/checks/concurrency-async-no-new-threads.rst =================================================================== --- /dev/null +++ clang-tools-extra/docs/clang-tidy/checks/concurrency-async-no-new-threads.rst @@ -0,0 +1,39 @@ +.. title:: clang-tidy - concurrency-async-no-new-threads + +concurrency-async-no-new-threads +================================ + +Finds functions and types that create new system threads. It is not +a bug per se, but asynchronous code is expected to use only asynchronous +functions and types. E.g. if the code uses C++ coroutines, it is expected +that only new coroutines or coroutine-based primitives are created +instead of heavy system threads. + +.. note:: + + The check doesn't identify synchronous and asynchronous code. Instead, it + assumes that all analyzed code is asynchronous and all blocking calls have to + be found. You should split the sync and async code at the filesystem level + and enable `concurrency-async-*` checks for files with asynchronous code + only. + +The check by default searches for types/functions from the following categories: + +* C++ std +* Boost.Thread, part of Boost.Compute +* C11 threads +* POSIX threads (pthreads) +* Linux syscalls + +Options +------- + +.. option:: FunctionsExtra + + Specifies additional functions to search for, separated with semicolon. + Defaults to empty string (no functions). + +.. option:: TypesExtra + + Specifies additional types to search for, separated with semicolon. + Defaults to empty string (no 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-no-new-threads `_, `concurrency-mt-unsafe `_, `cppcoreguidelines-avoid-goto `_, `cppcoreguidelines-avoid-non-const-global-variables `_, Index: clang-tools-extra/test/clang-tidy/checkers/concurrency-async-no-new-threads.c =================================================================== --- /dev/null +++ clang-tools-extra/test/clang-tidy/checkers/concurrency-async-no-new-threads.c @@ -0,0 +1,35 @@ +// RUN: %check_clang_tidy %s concurrency-async-no-new-threads %t + +int pthread_create(int *thread, const int *attr, + void *(*start_routine)(void *), void *arg); + +int thrd_create(int *thr, int func, void *arg); + +int fork(); + +int vfork(); + +int clone(int (*fn)(void *), void *child_stack, + int flags, void *arg); + +long clone3(int *cl_args, int size); + +void test_threads() { + pthread_create(0, 0, 0, 0); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: function 'pthread_create' creates new threads [concurrency-async-no-new-threads] + + thrd_create(0, 0, 0); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: function 'thrd_create' creates new threads [concurrency-async-no-new-threads] + + fork(); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: function 'fork' creates new threads [concurrency-async-no-new-threads] + + vfork(); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: function 'vfork' creates new threads [concurrency-async-no-new-threads] + + clone(0, 0, 0, 0); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: function 'clone' creates new threads [concurrency-async-no-new-threads] + + clone3(0, 0); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: function 'clone3' creates new threads [concurrency-async-no-new-threads] +} Index: clang-tools-extra/test/clang-tidy/checkers/concurrency-async-no-new-threads.cpp =================================================================== --- /dev/null +++ clang-tools-extra/test/clang-tidy/checkers/concurrency-async-no-new-threads.cpp @@ -0,0 +1,133 @@ +// RUN: %check_clang_tidy %s concurrency-async-no-new-threads %t -- \ +// RUN: -config='{CheckOptions: [{key: "concurrency-async-no-new-threads.FunctionsExtra", value: "::my::create_thread"},{key: "concurrency-async-no-new-threads.TypesExtra", value: "::my::thread"}]}' + +namespace std { + +class thread { +public: + void join() {} +}; + +class jthread {}; + +class nothread {}; + +namespace execution { + +class sequenced_policy {}; + +class parallel_policy {}; + +class parallel_unsequenced_policy {}; + +class unsequenced_policy {}; + +sequenced_policy seq; +parallel_policy par; +// CHECK-MESSAGES: :[[@LINE-1]]:1: warning: type 'parallel_policy' creates new threads [concurrency-async-no-new-threads] +parallel_unsequenced_policy par_unseq; +// CHECK-MESSAGES: :[[@LINE-1]]:1: warning: type 'parallel_unsequenced_policy' creates new threads [concurrency-async-no-new-threads] +unsequenced_policy unseq; +// CHECK-MESSAGES: :[[@LINE-1]]:1: warning: type 'unsequenced_policy' creates new threads [concurrency-async-no-new-threads] + +} // namespace execution + +void async(int (*)()) { +} + +template +void sort(T1 &&, T2, T3, T4); + +} // namespace std + +int pthread_create(int *thread, const int *attr, + void *(*start_routine)(void *), void *arg); + +int thrd_create(int *thr, int func, void *arg); + +int fork(); + +int vfork(); + +int clone(int (*fn)(void *), void *child_stack, + int flags, void *arg); + +long clone3(int *cl_args, int size); + +namespace boost { + +class thread {}; + +class scoped_thread {}; + +void async(int (*)()) {} + +namespace compute { +class device {}; + +} // namespace compute + +} // namespace boost + +void test_threads() { + std::thread t; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: type 'thread' creates new threads [concurrency-async-no-new-threads] + + std::jthread j; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: type 'jthread' creates new threads [concurrency-async-no-new-threads] + + std::execution::parallel_policy pp; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: type 'parallel_policy' creates new threads [concurrency-async-no-new-threads] + + std::execution::sequenced_policy sp; + + std::sort(std::execution::par, 0, 0, 0); + // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: use of 'par' creates new threads [concurrency-async-no-new-threads] + + boost::thread bt; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: type 'thread' creates new threads [concurrency-async-no-new-threads] + boost::scoped_thread bst; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: type 'scoped_thread' creates new threads [concurrency-async-no-new-threads] + + boost::compute::device bcd; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: type 'device' creates new threads [concurrency-async-no-new-threads] + + std::async(fork); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: function 'async' creates new threads [concurrency-async-no-new-threads] + + boost::async(fork); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: function 'async' creates new threads [concurrency-async-no-new-threads] + + pthread_create(0, 0, 0, 0); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: function 'pthread_create' creates new threads [concurrency-async-no-new-threads] + + thrd_create(0, 0, 0); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: function 'thrd_create' creates new threads [concurrency-async-no-new-threads] + + fork(); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: function 'fork' creates new threads [concurrency-async-no-new-threads] + + vfork(); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: function 'vfork' creates new threads [concurrency-async-no-new-threads] + + clone(0, 0, 0, 0); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: function 'clone' creates new threads [concurrency-async-no-new-threads] + + clone3(0, 0); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: function 'clone3' creates new threads [concurrency-async-no-new-threads] +} + +namespace my { + +class thread {}; +void create_thread(); + +} // namespace my + +void test_extra() { + my::thread t; + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: type 'thread' creates new threads [concurrency-async-no-new-threads] + + my::create_thread(); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: function 'create_thread' creates new threads [concurrency-async-no-new-threads] +}