Index: clang-tools-extra/clang-tidy/misc/CMakeLists.txt =================================================================== --- clang-tools-extra/clang-tidy/misc/CMakeLists.txt +++ clang-tools-extra/clang-tidy/misc/CMakeLists.txt @@ -7,6 +7,7 @@ DefinitionsInHeadersCheck.cpp MiscTidyModule.cpp MisplacedConstCheck.cpp + MtUnsafeCheck.cpp NewDeleteOverloadsCheck.cpp NoRecursionCheck.cpp NonCopyableObjects.cpp Index: clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp =================================================================== --- clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp +++ clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp @@ -11,6 +11,7 @@ #include "../ClangTidyModuleRegistry.h" #include "DefinitionsInHeadersCheck.h" #include "MisplacedConstCheck.h" +#include "MtUnsafeCheck.h" #include "NewDeleteOverloadsCheck.h" #include "NoRecursionCheck.h" #include "NonCopyableObjects.h" @@ -34,6 +35,8 @@ CheckFactories.registerCheck( "misc-definitions-in-headers"); CheckFactories.registerCheck("misc-misplaced-const"); + CheckFactories.registerCheck( + "misc-mt-unsafe"); CheckFactories.registerCheck( "misc-new-delete-overloads"); CheckFactories.registerCheck("misc-no-recursion"); Index: clang-tools-extra/clang-tidy/misc/MtUnsafeCheck.h =================================================================== --- /dev/null +++ clang-tools-extra/clang-tidy/misc/MtUnsafeCheck.h @@ -0,0 +1,43 @@ +//===--- MtUnsafeCheck.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_MISC_MTUNSAFECHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_MTUNSAFECHECK_H + +#include "../ClangTidyCheck.h" + +namespace clang { +namespace tidy { +namespace misc { + +/// Checks that non-thread-safe functions are not used. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/misc-mt-unsafe.html +class MtUnsafeCheck : public ClangTidyCheck { +public: + MtUnsafeCheck(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; + + enum class LibcType { + Posix, + Glibc, + Any, + }; + +private: + const LibcType Libc; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_MTUNSAFECHECK_H Index: clang-tools-extra/clang-tidy/misc/MtUnsafeCheck.cpp =================================================================== --- /dev/null +++ clang-tools-extra/clang-tidy/misc/MtUnsafeCheck.cpp @@ -0,0 +1,323 @@ +//===--- MtUnsafeCheck.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 "MtUnsafeCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "llvm/ADT/DenseSet.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { + +template <> struct OptionEnumMapping { + static llvm::ArrayRef> + getEnumMapping() { + static constexpr std::pair + Mapping[] = {{misc::MtUnsafeCheck::LibcType::Posix, "posix"}, + {misc::MtUnsafeCheck::LibcType::Glibc, "glibc"}, + {misc::MtUnsafeCheck::LibcType::Any, "any"}}; + return makeArrayRef(Mapping); + } +}; + +namespace misc { + +// Initial list was extracted from gcc documentation +static const StringRef glibcFunctions[] = { + "::argp_error", + "::argp_help", + "::argp_parse", + "::argp_state_help", + "::argp_usage", + "::asctime", + "::clearenv", + "::crypt", + "::ctime", + "::cuserid", + "::drand48", + "::ecvt", + "::encrypt", + "::endfsent", + "::endgrent", + "::endhostent", + "::endnetent", + "::endnetgrent", + "::endprotoent", + "::endpwent", + "::endservent", + "::endutent", + "::endutxent", + "::erand48", + "::error_at_line", + "::exit", + "::fcloseall", + "::fcvt", + "::fgetgrent", + "::fgetpwent", + "::gammal", + "::getchar_unlocked", + "::getdate", + "::getfsent", + "::getfsfile", + "::getfsspec", + "::getgrent", + "::getgrent_r", + "::getgrgid", + "::getgrnam", + "::gethostbyaddr", + "::gethostbyname", + "::gethostbyname2", + "::gethostent", + "::getlogin", + "::getmntent", + "::getnetbyaddr", + "::getnetbyname", + "::getnetent", + "::getnetgrent", + "::getnetgrent_r", + "::getopt", + "::getopt_long", + "::getopt_long_only", + "::getpass", + "::getprotobyname", + "::getprotobynumber", + "::getprotoent", + "::getpwent", + "::getpwent_r", + "::getpwnam", + "::getpwuid", + "::getservbyname", + "::getservbyport", + "::getservent", + "::getutent", + "::getutent_r", + "::getutid", + "::getutid_r", + "::getutline", + "::getutline_r", + "::getutxent", + "::getutxid", + "::getutxline", + "::getwchar_unlocked", + "::glob", + "::glob64", + "::gmtime", + "::hcreate", + "::hdestroy", + "::hsearch", + "::innetgr", + "::jrand48", + "::l64a", + "::lcong48", + "::lgammafNx", + "::localeconv", + "::localtime", + "::login", + "::login_tty", + "::logout", + "::logwtmp", + "::lrand48", + "::mallinfo", + "::mallopt", + "::mblen", + "::mbrlen", + "::mbrtowc", + "::mbsnrtowcs", + "::mbsrtowcs", + "::mbtowc", + "::mcheck", + "::mprobe", + "::mrand48", + "::mtrace", + "::muntrace", + "::nrand48", + "::__ppc_get_timebase_freq", + "::ptsname", + "::putchar_unlocked", + "::putenv", + "::pututline", + "::pututxline", + "::putwchar_unlocked", + "::qecvt", + "::qfcvt", + "::register_printf_function", + "::seed48", + "::setenv", + "::setfsent", + "::setgrent", + "::sethostent", + "::sethostid", + "::setkey", + "::setlocale", + "::setlogmask", + "::setnetent", + "::setnetgrent", + "::setprotoent", + "::setpwent", + "::setservent", + "::setutent", + "::setutxent", + "::siginterrupt", + "::sigpause", + "::sigprocmask", + "::sigsuspend", + "::sleep", + "::srand48", + "::strerror", + "::strsignal", + "::strtok", + "::tcflow", + "::tcsendbreak", + "::tmpnam", + "::ttyname", + "::unsetenv", + "::updwtmp", + "::utmpname", + "::utmpxname", + "::valloc", + "::vlimit", + "::wcrtomb", + "::wcsnrtombs", + "::wcsrtombs", + "::wctomb", + "::wordexp", +}; + +static const StringRef posixFunctions[] = { + "::asctime", + "::basename", + "::catgets", + "::crypt", + "::ctime", + "::dbm_clearerr", + "::dbm_close", + "::dbm_delete", + "::dbm_error", + "::dbm_fetch", + "::dbm_firstkey", + "::dbm_nextkey", + "::dbm_open", + "::dbm_store", + "::dirname", + "::dlerror", + "::drand48", + "::encrypt", + "::endgrent", + "::endpwent", + "::endutxent", + "::ftw", + "::getc_unlocked", + "::getchar_unlocked", + "::getdate", + "::getenv", + "::getgrent", + "::getgrgid", + "::getgrnam", + "::gethostent", + "::getlogin", + "::getnetbyaddr", + "::getnetbyname", + "::getnetent", + "::getopt", + "::getprotobyname", + "::getprotobynumber", + "::getprotoent", + "::getpwent", + "::getpwnam", + "::getpwuid", + "::getservbyname", + "::getservbyport", + "::getservent", + "::getutxent", + "::getutxid", + "::getutxline", + "::gmtime", + "::hcreate", + "::hdestroy", + "::hsearch", + "::inet_ntoa", + "::l64a", + "::lgamma", + "::lgammaf", + "::lgammal", + "::localeconv", + "::localtime", + "::lrand48", + "::mrand48", + "::nftw", + "::nl_langinfo", + "::ptsname", + "::putc_unlocked", + "::putchar_unlocked", + "::putenv", + "::pututxline", + "::rand", + "::readdir", + "::setenv", + "::setgrent", + "::setkey", + "::setpwent", + "::setutxent", + "::strerror", + "::strsignal", + "::strtok", + "::system", + "::ttyname", + "::unsetenv", + "::wcstombs", + "::wctomb", +}; + +static std::vector compileAny() { + llvm::DenseSet result; + result.insert(std::begin(posixFunctions), std::end(posixFunctions)); + result.insert(std::begin(glibcFunctions), std::end(glibcFunctions)); + return {result.begin(), result.end()}; +} + +static const std::vector anyFunctions = compileAny(); + +static ast_matchers::internal::Matcher +hasAnyMtUnsafeNames(MtUnsafeCheck::LibcType libc) { + switch (libc) { + case MtUnsafeCheck::LibcType::Posix: + return hasAnyName(posixFunctions); + case MtUnsafeCheck::LibcType::Glibc: + return hasAnyName(glibcFunctions); + case MtUnsafeCheck::LibcType::Any: + return hasAnyName(anyFunctions); + } + llvm_unreachable("invalid libc type"); +} + +MtUnsafeCheck::MtUnsafeCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + Libc(Options.get("Libc", MtUnsafeCheck::LibcType::Any)) {} + +void MtUnsafeCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "Libc", Libc); +} + +void MtUnsafeCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher(callExpr(callee(functionDecl(hasAnyMtUnsafeNames(Libc)))) + .bind("mt-unsafe"), + this); +} + +void MtUnsafeCheck::check(const MatchFinder::MatchResult &Result) { + const auto *Call = Result.Nodes.getNodeAs("mt-unsafe"); + assert(Call && "Unhandled binding in the Matcher"); + + diag(Call->getBeginLoc(), "function is not thread safe"); +} + +} // namespace misc +} // namespace tidy +} // namespace clang Index: clang-tools-extra/docs/ReleaseNotes.rst =================================================================== --- clang-tools-extra/docs/ReleaseNotes.rst +++ clang-tools-extra/docs/ReleaseNotes.rst @@ -106,6 +106,10 @@ Finds condition variables in nested ``if`` statements that were also checked in the outer ``if`` statement and were not changed. +- New :doc:`misc-mt-unsafe ` check. + + Finds thread-unsafe functions usage. + - New :doc:`readability-function-cognitive-complexity ` check. 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 @@ -198,6 +198,7 @@ `llvmlibc-restrict-system-libc-headers `_, "Yes" `misc-definitions-in-headers `_, "Yes" `misc-misplaced-const `_, + `misc-mt-unsafe `_, `misc-new-delete-overloads `_, `misc-no-recursion `_, `misc-non-copyable-objects `_, Index: clang-tools-extra/docs/clang-tidy/checks/misc-mt-unsafe.rst =================================================================== --- /dev/null +++ clang-tools-extra/docs/clang-tidy/checks/misc-mt-unsafe.rst @@ -0,0 +1,36 @@ +.. title:: clang-tidy - misc-mt-unsafe + +misc-mt-unsafe +============== + +Checks for some thread-unsafe functions against a black list of +known-to-be-unsafe functions. Usually they access static variables without +synchronization (e.g. gmtime(3)) or utilize signals in a racy way. +The set of functions to check is specified with the `Libc` option. + +Examples: + +.. code-block:: c++ + + tm = gmtime(timep); // uses a global buffer + + sleep(1); // implementation may use SIGALRM + +.. option:: Libc + + Specifies which functions in libc should be considered thread-safe, + possible values are `posix`, `glibc`, or `any`. + + `posix` means POSIX defined thread-unsafe functions. POSIX.1-2001 + in "2.9.1 Thread-Safety" defines that all functions specified in the + standard are thread-safe except a predefined list of thread-unsafe + functions. + + Glibc defines some of them as thread-safe (e.g. dirname(3)), but adds + non-POSIX thread-unsafe ones (e.g. getopt_long(3)). Glibc's list is + compiled from GNU web documentation with a search for MT-Safe tag: + https://www.gnu.org/software/libc/manual/html_node/POSIX-Safety-Concepts.html + + If you want to identify thread-unsafe API for at least one libc or + unsure which libc will be used, use `any` (default). + Index: clang-tools-extra/test/clang-tidy/checkers/misc-mt-unsafe-any.cpp =================================================================== --- /dev/null +++ clang-tools-extra/test/clang-tidy/checkers/misc-mt-unsafe-any.cpp @@ -0,0 +1,24 @@ +// RUN: %check_clang_tidy %s misc-mt-unsafe %t + +extern unsigned int sleep (unsigned int __seconds); +extern int *gmtime (const int *__timer); +extern int *gmtime_r (const int *__timer, char*); +extern char *dirname (char *__path); + +void foo() { + sleep(2); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: function is not thread safe [misc-mt-unsafe] + ::sleep(2); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: function is not thread safe [misc-mt-unsafe] + + auto tm = gmtime(nullptr); + // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: function is not thread safe [misc-mt-unsafe] + tm = ::gmtime(nullptr); + // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: function is not thread safe [misc-mt-unsafe] + + tm = gmtime_r(nullptr, nullptr); + tm = ::gmtime_r(nullptr, nullptr); + + dirname(nullptr); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: function is not thread safe [misc-mt-unsafe] +} Index: clang-tools-extra/test/clang-tidy/checkers/misc-mt-unsafe-glibc.cpp =================================================================== --- /dev/null +++ clang-tools-extra/test/clang-tidy/checkers/misc-mt-unsafe-glibc.cpp @@ -0,0 +1,15 @@ +// RUN: %check_clang_tidy %s misc-mt-unsafe %t -- -config='{CheckOptions: [{key: "misc-mt-unsafe.Libc", value: "glibc"}]}' + +extern unsigned int sleep (unsigned int __seconds); +extern int *gmtime (const int *__timer); +extern char *dirname (char *__path); + +void foo() { + sleep(2); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: function is not thread safe [misc-mt-unsafe] + + ::sleep(2); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: function is not thread safe [misc-mt-unsafe] + + dirname(nullptr); +} Index: clang-tools-extra/test/clang-tidy/checkers/misc-mt-unsafe-posix.cpp =================================================================== --- /dev/null +++ clang-tools-extra/test/clang-tidy/checkers/misc-mt-unsafe-posix.cpp @@ -0,0 +1,22 @@ +// RUN: %check_clang_tidy %s misc-mt-unsafe %t -- -config='{CheckOptions: [{key: "misc-mt-unsafe.Libc", value: "posix"}]}' + +extern unsigned int sleep (unsigned int __seconds); +extern int *gmtime (const int *__timer); +extern int *gmtime_r (const int *__timer, char*); +extern char *dirname (char *__path); + +void foo() { + sleep(2); + ::sleep(2); + + auto tm = gmtime(nullptr); + // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: function is not thread safe [misc-mt-unsafe] + tm = ::gmtime(nullptr); + // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: function is not thread safe [misc-mt-unsafe] + + tm = gmtime_r(nullptr, nullptr); + tm = ::gmtime_r(nullptr, nullptr); + + dirname(nullptr); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: function is not thread safe [misc-mt-unsafe] +}