Index: clang-tidy/cppcoreguidelines/CMakeLists.txt =================================================================== --- clang-tidy/cppcoreguidelines/CMakeLists.txt +++ clang-tidy/cppcoreguidelines/CMakeLists.txt @@ -20,6 +20,7 @@ ProTypeVarargCheck.cpp SlicingCheck.cpp SpecialMemberFunctionsCheck.cpp + UseRaiiLocksCheck.cpp LINK_LIBS clangAST Index: clang-tidy/cppcoreguidelines/CppCoreGuidelinesTidyModule.cpp =================================================================== --- clang-tidy/cppcoreguidelines/CppCoreGuidelinesTidyModule.cpp +++ clang-tidy/cppcoreguidelines/CppCoreGuidelinesTidyModule.cpp @@ -32,6 +32,7 @@ #include "ProTypeVarargCheck.h" #include "SlicingCheck.h" #include "SpecialMemberFunctionsCheck.h" +#include "UseRaiiLocksCheck.h" namespace clang { namespace tidy { @@ -83,6 +84,8 @@ CheckFactories.registerCheck( "cppcoreguidelines-special-member-functions"); CheckFactories.registerCheck("cppcoreguidelines-slicing"); + CheckFactories.registerCheck( + "cppcoreguidelines-use-raii-locks"); CheckFactories.registerCheck( "cppcoreguidelines-c-copy-assignment-signature"); } Index: clang-tidy/cppcoreguidelines/UseRaiiLocksCheck.h =================================================================== --- /dev/null +++ clang-tidy/cppcoreguidelines/UseRaiiLocksCheck.h @@ -0,0 +1,42 @@ +//===--- UseRaiiLocksCheck.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_CPPCOREGUIDELINES_USERAIILOCKSCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_USERAIILOCKSCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace cppcoreguidelines { + +/// Check for instances of explicit std::mutex lock() and unlock() calls +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/cppcoreguidelines-use-raii-locks.html +class UseRaiiLocksCheck : public ClangTidyCheck { +public: + UseRaiiLocksCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + LockableTypes(Options.get( + "LockableTypes", + "::std::mutex;::std::recursive_mutex;::std::timed_mutex;::std::" + "recursive_timed_mutex;::std::unique_lock")) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + +private: + const std::string LockableTypes; +}; + +} // namespace cppcoreguidelines +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_USERAIILOCKSCHECK_H Index: clang-tidy/cppcoreguidelines/UseRaiiLocksCheck.cpp =================================================================== --- /dev/null +++ clang-tidy/cppcoreguidelines/UseRaiiLocksCheck.cpp @@ -0,0 +1,107 @@ +//===--- UseRaiiLocksCheck.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 "UseRaiiLocksCheck.h" +#include "../utils/Matchers.h" +#include "../utils/OptionsUtils.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; +using namespace clang::ast_matchers::internal; + +namespace clang { +namespace tidy { +namespace cppcoreguidelines { + +namespace { + +Matcher hasAnyListedName(const std::string &TypeNames) { + auto NameList = utils::options::parseStringList(TypeNames); + return hasAnyName(std::vector(NameList.begin(), NameList.end())); +} + +DeclRefExpr *findDeclRefExpr(const CXXMemberCallExpr *MemberCallExpr) { + auto *ObjectExpr = MemberCallExpr->getImplicitObjectArgument(); + if (auto *ObjectCast = dyn_cast(ObjectExpr)) { + ObjectExpr = ObjectCast->getSubExpr(); + } + return dyn_cast(ObjectExpr); +} + +} // namespace + +void UseRaiiLocksCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "LockableTypes", LockableTypes); +} + +void UseRaiiLocksCheck::registerMatchers(MatchFinder *Finder) { + // lock_guards require c++11 or later. + if (!getLangOpts().CPlusPlus11) + return; + + auto MutexType = type(hasUnqualifiedDesugaredType(recordType( + hasDeclaration(cxxRecordDecl(hasAnyListedName(LockableTypes)))))); + // Match expressions of type mutex or mutex pointer. + auto MutexExpr = + expr(anyOf(hasType(MutexType), hasType(qualType(pointsTo(MutexType))))); + + // Match a call to mutex lock() or try_lock(). + auto MutexLockCallExpr = + cxxMemberCallExpr(on(MutexExpr), callee(memberExpr()), + callee(cxxMethodDecl(hasAnyName("lock", "try_lock")))); + + // Match a call to mutex unlock(). + auto MutexUnlockCallExpr = + cxxMemberCallExpr(on(MutexExpr), callee(memberExpr()), + callee(cxxMethodDecl(hasAnyName("unlock")))); + + // Match a scope with a calls to both lock() and unlock(). + auto ScopedProblem = + compoundStmt(forEach(MutexLockCallExpr.bind("lock")), + forEach(MutexUnlockCallExpr.bind("unlock"))); + Finder->addMatcher(ScopedProblem, this); +} + +void UseRaiiLocksCheck::check(const MatchFinder::MatchResult &Result) { + + const auto *LockCallExpr = Result.Nodes.getNodeAs("lock"); + const auto *UnlockCallExpr = + Result.Nodes.getNodeAs("unlock"); + + const auto *LockDeclRef = findDeclRefExpr(LockCallExpr); + const auto *UnlockDeclRef = findDeclRefExpr(UnlockCallExpr); + + assert(LockDeclRef && UnlockDeclRef); + + auto LockObjectName = LockDeclRef->getFoundDecl()->getName(); + auto UnlockObjectName = UnlockDeclRef->getFoundDecl()->getName(); + + // Ignore m.lock(); m2.unlock(). + if (LockObjectName != UnlockObjectName) + return; + + auto LockLocation = + Result.SourceManager->getPresumedLoc(LockCallExpr->getBeginLoc()); + auto UnlockLocation = + Result.SourceManager->getPresumedLoc(UnlockCallExpr->getBeginLoc()); + + // Ignore unlock() before lock(). + if (UnlockLocation.getLine() < LockLocation.getLine() || + (UnlockLocation.getLine() == LockLocation.getLine() && + UnlockLocation.getColumn() < LockLocation.getColumn())) + return; + + diag(LockCallExpr->getBeginLoc(), + "use RAII-style locking mechanisms such as 'std::lock_guard' " + "or 'std::unique_lock'"); +} + +} // namespace cppcoreguidelines +} // namespace tidy +} // namespace clang Index: docs/ReleaseNotes.rst =================================================================== --- docs/ReleaseNotes.rst +++ docs/ReleaseNotes.rst @@ -91,6 +91,12 @@ Finds and fixes ``absl::Time`` subtraction expressions to do subtraction in the Time domain instead of the numeric domain. +- New :doc:`cppcoreguidelines-use-raii-locks + ` check. + + Checks for explicit usage of``std::mutex`` functions ``lock()``, + ``try_lock()`` and ``unlock()``. + - New :doc:`google-readability-avoid-underscore-in-googletest-name ` check. Index: docs/clang-tidy/checks/cppcoreguidelines-use-raii-locks.rst =================================================================== --- /dev/null +++ docs/clang-tidy/checks/cppcoreguidelines-use-raii-locks.rst @@ -0,0 +1,19 @@ +.. title:: clang-tidy - cppcoreguidelines-use-raii-locks + +cppcoreguidelines-use-raii-locks +================================ + +The explicit use of ``std::mutex`` functions ``lock()``, ``try_lock()`` and +``unlock()`` is error prone and should be avoided. +It's recommended to use RAII-style locking mechanisms such as +``std::lock_guard`` or ``std::unique_lock``. + +Options +------- + +.. option:: LockableTypes + + Semicolon-separated list of fully qualified names of types that support + locking and unlocking a mutex. + Defaults to: ``::std::mutex;::std::recursive_mutex;::std::timed_mutex; + ::std::recursive_timed_mutex;::std::unique_lock``. Index: docs/clang-tidy/checks/list.rst =================================================================== --- docs/clang-tidy/checks/list.rst +++ docs/clang-tidy/checks/list.rst @@ -118,6 +118,7 @@ cppcoreguidelines-pro-type-vararg cppcoreguidelines-slicing cppcoreguidelines-special-member-functions + cppcoreguidelines-use-raii-locks fuchsia-default-arguments fuchsia-header-anon-namespaces (redirects to google-build-namespaces) fuchsia-multiple-inheritance Index: test/clang-tidy/cppcoreguidelines-use-raii-locks.cpp =================================================================== --- /dev/null +++ test/clang-tidy/cppcoreguidelines-use-raii-locks.cpp @@ -0,0 +1,194 @@ +// RUN: %check_clang_tidy %s cppcoreguidelines-use-raii-locks %t + +// Mock implementation of std::mutex +namespace std { +struct mutex { + void lock(); + void try_lock(); + void unlock(); +}; +typedef mutex recursive_mutex; +} // namespace std + +void warn_me1_simple() { + std::mutex m; + m.lock(); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use RAII + m.unlock(); +} + +void warn_me2_nested() { + std::mutex m; + m.lock(); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use RAII + { + std::mutex m; + m.lock(); + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: use RAII + m.unlock(); + } + m.unlock(); +} + +void warn_me3_nested_extra() { + std::mutex m1; + std::mutex m2; + { + m1.lock(); + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: use RAII + { + m2.lock(); + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: use RAII + m2.unlock(); + } + m1.unlock(); + } +} + +void warn_me4_multi_mutex() { + std::mutex m1; + std::mutex m2; + + m1.lock(); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use RAII + m1.unlock(); + m2.lock(); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use RAII + m2.unlock(); +} + +void warn_me5_multi_mutex_extra() { + std::mutex m1; + std::mutex m2; + + m1.lock(); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use RAII + m2.lock(); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use RAII + m1.unlock(); + m2.unlock(); +} + +void warn_me6() { + std::mutex m; + m.lock(); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use RAII + m.unlock(); + m.lock(); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use RAII + m.unlock(); + m.try_lock(); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use RAII + m.lock(); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use RAII + m.unlock(); +} + +void warn_me7_loop() { + std::mutex m; + for (auto i = 0; i < 3; i++) { + m.lock(); + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: use RAII + m.unlock(); + } +} + +template +void attempt(M m) { + m.lock(); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use RAII + m.unlock(); +} + +void warn_me8_template_func() { + std::mutex m; + attempt(m); +} + +// clang-format off +#define ATTEMPT(m) {\ + m.lock();\ + m.unlock();\ + } +// clang-format on + +void warn_me9_macro() { + std::mutex m; + ATTEMPT(m); + // CHECK-MESSAGES: :[[@LINE-1]]:11: warning: use RAII +} + +void warn_me10_inline() { + std::mutex m; + // clang-format off + m.lock(); m.unlock(); + // CHECK-MESSAGES: :[[@LINE-1]]:3: warning: use RAII + // clang-format on +} + +void warn_me11_recursive_mutex() { + std::recursive_mutex m; + m.lock(); + // CHECK-MESSAGES: [[@LINE-1]]:3: warning: use RAII + m.unlock(); +} + +void ignore_me1_rev_order() { + std::mutex m; + m.unlock(); + m.lock(); + // CHECK-MESSAGES-NOT: warning: +} + +void ignore_me2_diff_mtx() { + std::mutex m1; + std::mutex m2; + m1.lock(); + // CHECK-MESSAGES-NOT: warning: + m2.unlock(); +} + +void ignore_me3() { + std::recursive_mutex m1; + std::recursive_mutex m2; + m1.lock(); + // CHECK-MESSAGES-NOT: warning: + m2.unlock(); +} + +void ignore_me4() { + std::mutex m; + m.unlock(); + m.lock(); + m.lock(); + // CHECK-MESSAGES-NOT: warning: +} + +void ignore_me5_inline() { + std::mutex m; + m.unlock(); + m.lock(); + // CHECK-MESSAGES-NOT: warning: +} + +void ignore_me6_seperate_scopes() { + std::mutex m; + { + m.lock(); + // CHECK-MESSAGES-NOT: warning: + } + { + m.unlock(); + } +} + +void ignore_me7_seperate_scopes_nested() { + std::mutex m; + { + m.lock(); + // CHECK-MESSAGES-NOT: warning: + { + m.unlock(); + } + } +}