[clang] [clang][analyzer] Add note tags to alpha.unix.BlockInCriticalSection (PR #80029)

Endre Fülöp via cfe-commits cfe-commits at lists.llvm.org
Sun Mar 10 13:09:03 PDT 2024


https://github.com/gamesh411 updated https://github.com/llvm/llvm-project/pull/80029

>From 346e2296869e750c7ec5bd75cf05f80a23b70569 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Endre=20F=C3=BCl=C3=B6p?= <endre.fulop at sigmatechnology.com>
Date: Tue, 30 Jan 2024 11:33:30 +0100
Subject: [PATCH] [clang][analyzer] Improve BlockInCriticalSectionsChecker with
 multi-section and recursive mutex support

* Add support for multiple, potentially overlapping critical sections:
  The checker can now simultaneously handle several mutex's critical
  sections without confusing them.
* Implement the handling of recursive mutexes:
  By identifying the lock events, recursive mutexes are now supported.
  A lock event is a pair of a lock expression and the SVal of the mutex
  that it locks, so even multiple locks of the same mutex (and even by
  the same expression) is now supported.
* Refine the note tags generated by the checker:
  The note tags now correctly show just for mutexes those are
  active at point of error, and multiple acqisitions of the same mutex
  are also noted.
---
 .../BlockInCriticalSectionChecker.cpp         | 391 ++++++++++++++----
 .../Analysis/block-in-critical-section.cpp    | 270 +++++++++---
 2 files changed, 515 insertions(+), 146 deletions(-)

diff --git a/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp
index 66e080adb1382b..74ec4b73bd8b5f 100644
--- a/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp
@@ -20,48 +20,180 @@
 #include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h"
 #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
 #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState_Fwd.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallString.h"
+#include "llvm/ADT/StringExtras.h"
+
+#include <iterator>
+#include <utility>
+#include <variant>
 
 using namespace clang;
 using namespace ento;
 
 namespace {
+
+struct CritSectionMarker {
+  const Expr *LockExpr{};
+  const MemRegion *LockReg{};
+
+  void Profile(llvm::FoldingSetNodeID &ID) const {
+    ID.Add(LockExpr);
+    ID.Add(LockReg);
+  }
+
+  [[nodiscard]] constexpr bool
+  operator==(const CritSectionMarker &Other) const noexcept {
+    return LockExpr == Other.LockExpr && LockReg == Other.LockReg;
+  }
+  [[nodiscard]] constexpr bool
+  operator!=(const CritSectionMarker &Other) const noexcept {
+    return !(*this == Other);
+  }
+};
+
+class FirstArgMutexDescriptor {
+  CallDescription LockFn;
+  CallDescription UnlockFn;
+
+public:
+  FirstArgMutexDescriptor(CallDescription &&LockFn, CallDescription &&UnlockFn)
+      : LockFn(std::move(LockFn)), UnlockFn(std::move(UnlockFn)) {}
+  [[nodiscard]] bool matchesLock(const CallEvent &Call) const {
+    return LockFn.matches(Call) && Call.getNumArgs() > 0;
+  }
+  [[nodiscard]] bool matchesUnlock(const CallEvent &Call) const {
+    return UnlockFn.matches(Call) && Call.getNumArgs() > 0;
+  }
+  [[nodiscard]] const MemRegion *getLockRegion(const CallEvent &Call) const {
+    return Call.getArgSVal(0).getAsRegion();
+  }
+  [[nodiscard]] const MemRegion *getUnlockRegion(const CallEvent &Call) const {
+    return Call.getArgSVal(0).getAsRegion();
+  }
+};
+
+class MemberMutexDescriptor {
+  CallDescription LockFn;
+  CallDescription UnlockFn;
+
+public:
+  MemberMutexDescriptor(CallDescription &&LockFn, CallDescription &&UnlockFn)
+      : LockFn(std::move(LockFn)), UnlockFn(std::move(UnlockFn)) {}
+  [[nodiscard]] bool matchesLock(const CallEvent &Call) const {
+    return LockFn.matches(Call);
+  }
+  bool matchesUnlock(const CallEvent &Call) const {
+    return UnlockFn.matches(Call);
+  }
+  [[nodiscard]] const MemRegion *getLockRegion(const CallEvent &Call) const {
+    return cast<CXXMemberCall>(Call).getCXXThisVal().getAsRegion();
+  }
+  [[nodiscard]] const MemRegion *getUnlockRegion(const CallEvent &Call) const {
+    return cast<CXXMemberCall>(Call).getCXXThisVal().getAsRegion();
+  }
+};
+
+class RAIIMutexDescriptor {
+  mutable const IdentifierInfo *Guard{};
+  mutable bool IdentifierInfoInitialized{};
+  mutable llvm::SmallString<32> GuardName{};
+
+  void initIdentifierInfo(const CallEvent &Call) const {
+    if (!IdentifierInfoInitialized) {
+      // In case of checking C code, or when the corresponding headers are not
+      // included, we might end up query the identifier table every time when
+      // this function is called instead of early returning it. To avoid this, a
+      // bool variable (IdentifierInfoInitialized) is used and the function will
+      // be run only once.
+      Guard = &Call.getCalleeAnalysisDeclContext()->getASTContext().Idents.get(
+          GuardName);
+      IdentifierInfoInitialized = true;
+    }
+  }
+
+public:
+  RAIIMutexDescriptor(StringRef GuardName) : GuardName(GuardName) {}
+  [[nodiscard]] bool matchesLock(const CallEvent &Call) const {
+    initIdentifierInfo(Call);
+    const auto *Ctor = dyn_cast<CXXConstructorCall>(&Call);
+    if (!Ctor)
+      return false;
+    auto *IdentifierInfo = Ctor->getDecl()->getParent()->getIdentifier();
+    return IdentifierInfo == Guard;
+  }
+  [[nodiscard]] bool matchesUnlock(const CallEvent &Call) const {
+    initIdentifierInfo(Call);
+    const auto *Dtor = dyn_cast<CXXDestructorCall>(&Call);
+    if (!Dtor)
+      return false;
+    auto *IdentifierInfo =
+        cast<CXXRecordDecl>(Dtor->getDecl()->getParent())->getIdentifier();
+    return IdentifierInfo == Guard;
+  }
+  [[nodiscard]] const MemRegion *getLockRegion(const CallEvent &Call) const {
+    const MemRegion *LockRegion = nullptr;
+    if (std::optional<SVal> Object = Call.getReturnValueUnderConstruction()) {
+      LockRegion = Object->getAsRegion();
+    }
+    return LockRegion;
+  }
+  [[nodiscard]] const MemRegion *getUnlockRegion(const CallEvent &Call) const {
+    return cast<CXXDestructorCall>(Call).getCXXThisVal().getAsRegion();
+  }
+};
+
+using MutexDescriptor =
+    std::variant<FirstArgMutexDescriptor, MemberMutexDescriptor,
+                 RAIIMutexDescriptor>;
+
 class BlockInCriticalSectionChecker : public Checker<check::PostCall> {
-  mutable IdentifierInfo *IILockGuard = nullptr;
-  mutable IdentifierInfo *IIUniqueLock = nullptr;
-  mutable bool IdentifierInfoInitialized = false;
-
-  const CallDescription LockFn{{"lock"}};
-  const CallDescription UnlockFn{{"unlock"}};
-  const CallDescription SleepFn{{"sleep"}};
-  const CallDescription GetcFn{{"getc"}};
-  const CallDescription FgetsFn{{"fgets"}};
-  const CallDescription ReadFn{{"read"}};
-  const CallDescription RecvFn{{"recv"}};
-  const CallDescription PthreadLockFn{{"pthread_mutex_lock"}};
-  const CallDescription PthreadTryLockFn{{"pthread_mutex_trylock"}};
-  const CallDescription PthreadUnlockFn{{"pthread_mutex_unlock"}};
-  const CallDescription MtxLock{{"mtx_lock"}};
-  const CallDescription MtxTimedLock{{"mtx_timedlock"}};
-  const CallDescription MtxTryLock{{"mtx_trylock"}};
-  const CallDescription MtxUnlock{{"mtx_unlock"}};
-
-  const llvm::StringLiteral ClassLockGuard{"lock_guard"};
-  const llvm::StringLiteral ClassUniqueLock{"unique_lock"};
+private:
+  const std::array<MutexDescriptor, 8> MutexDescriptors{
+      MemberMutexDescriptor(
+          CallDescription(/*QualifiedName=*/{"std", "mutex", "lock"},
+                          /*RequiredArgs=*/0),
+          CallDescription({"std", "mutex", "unlock"}, 0)),
+      FirstArgMutexDescriptor(CallDescription({"pthread_mutex_lock"}, 1),
+                              CallDescription({"pthread_mutex_unlock"}, 1)),
+      FirstArgMutexDescriptor(CallDescription({"mtx_lock"}, 1),
+                              CallDescription({"mtx_unlock"}, 1)),
+      FirstArgMutexDescriptor(CallDescription({"pthread_mutex_trylock"}, 1),
+                              CallDescription({"pthread_mutex_unlock"}, 1)),
+      FirstArgMutexDescriptor(CallDescription({"mtx_trylock"}, 1),
+                              CallDescription({"mtx_unlock"}, 1)),
+      FirstArgMutexDescriptor(CallDescription({"mtx_timedlock"}, 1),
+                              CallDescription({"mtx_unlock"}, 1)),
+      RAIIMutexDescriptor("lock_guard"),
+      RAIIMutexDescriptor("unique_lock")};
+
+  const std::array<CallDescription, 5> BlockingFunctions{
+      ArrayRef{StringRef{"sleep"}}, ArrayRef{StringRef{"getc"}},
+      ArrayRef{StringRef{"fgets"}}, ArrayRef{StringRef{"read"}},
+      ArrayRef{StringRef{"recv"}}};
 
   const BugType BlockInCritSectionBugType{
       this, "Call to blocking function in critical section", "Blocking Error"};
 
-  void initIdentifierInfo(ASTContext &Ctx) const;
+  void reportBlockInCritSection(const CallEvent &call, CheckerContext &C) const;
 
-  void reportBlockInCritSection(SymbolRef FileDescSym,
-                                const CallEvent &call,
-                                CheckerContext &C) const;
+  [[nodiscard]] const NoteTag *createCritSectionNote(CritSectionMarker M,
+                                                     CheckerContext &C) const;
+  [[nodiscard]] std::optional<MutexDescriptor>
+  checkLock(const CallEvent &Call, CheckerContext &C) const;
+  void handleLock(const MutexDescriptor &Mutex, const CallEvent &Call,
+                  CheckerContext &C) const;
+  [[nodiscard]] std::optional<MutexDescriptor>
+  checkUnlock(const CallEvent &Call, CheckerContext &C) const;
+  void handleUnlock(const MutexDescriptor &Mutex, const CallEvent &Call,
+                    CheckerContext &C) const;
+  [[nodiscard]] bool isBlockingInCritSection(const CallEvent &Call,
+                                             CheckerContext &C) const;
 
 public:
-  bool isBlockingFunction(const CallEvent &Call) const;
-  bool isLockFunction(const CallEvent &Call) const;
-  bool isUnlockFunction(const CallEvent &Call) const;
-
   /// Process unlock.
   /// Process lock.
   /// Process blocking functions (sleep, getc, fgets, read, recv)
@@ -70,73 +202,121 @@ class BlockInCriticalSectionChecker : public Checker<check::PostCall> {
 
 } // end anonymous namespace
 
-REGISTER_TRAIT_WITH_PROGRAMSTATE(MutexCounter, unsigned)
+REGISTER_LIST_WITH_PROGRAMSTATE(ActiveCritSections, CritSectionMarker)
 
-void BlockInCriticalSectionChecker::initIdentifierInfo(ASTContext &Ctx) const {
-  if (!IdentifierInfoInitialized) {
-    /* In case of checking C code, or when the corresponding headers are not
-     * included, we might end up query the identifier table every time when this
-     * function is called instead of early returning it. To avoid this, a bool
-     * variable (IdentifierInfoInitialized) is used and the function will be run
-     * only once. */
-    IILockGuard  = &Ctx.Idents.get(ClassLockGuard);
-    IIUniqueLock = &Ctx.Idents.get(ClassUniqueLock);
-    IdentifierInfoInitialized = true;
-  }
-}
+namespace std {
+// Iterator traits for ImmutableList data structure
+// that enable the use of STL algorithms.
+// TODO: Move these to llvm::ImmutableList when overhauling immutable data
+// structures for proper iterator concept support.
+template <>
+struct iterator_traits<
+    typename llvm::ImmutableList<CritSectionMarker>::iterator> {
+  using iterator_category = std::forward_iterator_tag;
+  using value_type = CritSectionMarker;
+  using difference_type = std::ptrdiff_t;
+  using reference = CritSectionMarker &;
+  using pointer = CritSectionMarker *;
+};
+} // namespace std
 
-bool BlockInCriticalSectionChecker::isBlockingFunction(const CallEvent &Call) const {
-  return matchesAny(Call, SleepFn, GetcFn, FgetsFn, ReadFn, RecvFn);
+std::optional<MutexDescriptor>
+BlockInCriticalSectionChecker::checkLock(const CallEvent &Call,
+                                         CheckerContext &C) const {
+  const auto *LockDescriptor =
+      llvm::find_if(MutexDescriptors, [&Call](auto &&LockFn) {
+        return std::visit(
+            [&Call](auto &&Descriptor) { return Descriptor.matchesLock(Call); },
+            LockFn);
+      });
+  if (LockDescriptor != MutexDescriptors.end())
+    return *LockDescriptor;
+  return std::nullopt;
 }
 
-bool BlockInCriticalSectionChecker::isLockFunction(const CallEvent &Call) const {
-  if (const auto *Ctor = dyn_cast<CXXConstructorCall>(&Call)) {
-    auto IdentifierInfo = Ctor->getDecl()->getParent()->getIdentifier();
-    if (IdentifierInfo == IILockGuard || IdentifierInfo == IIUniqueLock)
-      return true;
-  }
+void BlockInCriticalSectionChecker::handleLock(
+    const MutexDescriptor &LockDescriptor, const CallEvent &Call,
+    CheckerContext &C) const {
+  const auto *MutexRegion = std::visit(
+      [&Call](auto &&Descriptor) { return Descriptor.getLockRegion(Call); },
+      LockDescriptor);
+  if (!MutexRegion)
+    return;
+
+  const auto MarkToAdd = CritSectionMarker{Call.getOriginExpr(), MutexRegion};
+  ProgramStateRef StateWithLockEvent =
+      C.getState()->add<ActiveCritSections>(MarkToAdd);
+  C.addTransition(StateWithLockEvent, createCritSectionNote(MarkToAdd, C));
+}
 
-  return matchesAny(Call, LockFn, PthreadLockFn, PthreadTryLockFn, MtxLock,
-                    MtxTimedLock, MtxTryLock);
+std::optional<MutexDescriptor>
+BlockInCriticalSectionChecker::checkUnlock(const CallEvent &Call,
+                                           CheckerContext &C) const {
+  const auto *UnlockDescriptor =
+      llvm::find_if(MutexDescriptors, [&Call](auto &&UnlockFn) {
+        return std::visit(
+            [&Call](auto &&Descriptor) {
+              return Descriptor.matchesUnlock(Call);
+            },
+            UnlockFn);
+      });
+  if (UnlockDescriptor != MutexDescriptors.end())
+    return *UnlockDescriptor;
+  return std::nullopt;
 }
 
-bool BlockInCriticalSectionChecker::isUnlockFunction(const CallEvent &Call) const {
-  if (const auto *Dtor = dyn_cast<CXXDestructorCall>(&Call)) {
-    const auto *DRecordDecl = cast<CXXRecordDecl>(Dtor->getDecl()->getParent());
-    auto IdentifierInfo = DRecordDecl->getIdentifier();
-    if (IdentifierInfo == IILockGuard || IdentifierInfo == IIUniqueLock)
-      return true;
+void BlockInCriticalSectionChecker::handleUnlock(
+    const MutexDescriptor &UnlockDescriptor, const CallEvent &Call,
+    CheckerContext &C) const {
+  const auto *MutexRegion = std::visit(
+      [&Call](auto &&Descriptor) { return Descriptor.getUnlockRegion(Call); },
+      UnlockDescriptor);
+  if (!MutexRegion)
+    return;
+
+  ProgramStateRef State = C.getState();
+  const auto ActiveSections = State->get<ActiveCritSections>();
+  const auto MostRecentLock =
+      llvm::find_if(ActiveSections, [MutexRegion](auto &&Marker) {
+        return Marker.LockReg == MutexRegion;
+      });
+  if (MostRecentLock == ActiveSections.end())
+    return;
+
+  // Build a new ImmutableList without this element.
+  auto &Factory = State->get_context<ActiveCritSections>();
+  llvm::ImmutableList<CritSectionMarker> NewList = Factory.getEmptyList();
+  for (auto It = ActiveSections.begin(), End = ActiveSections.end(); It != End;
+       ++It) {
+    if (It != MostRecentLock)
+      NewList = Factory.add(*It, NewList);
   }
 
-  return matchesAny(Call, UnlockFn, PthreadUnlockFn, MtxUnlock);
+  State = State->set<ActiveCritSections>(NewList);
+  C.addTransition(State);
+}
+
+bool BlockInCriticalSectionChecker::isBlockingInCritSection(
+    const CallEvent &Call, CheckerContext &C) const {
+  return llvm::any_of(BlockingFunctions,
+                      [&Call](auto &&Fn) { return Fn.matches(Call); }) &&
+         !C.getState()->get<ActiveCritSections>().isEmpty();
 }
 
 void BlockInCriticalSectionChecker::checkPostCall(const CallEvent &Call,
                                                   CheckerContext &C) const {
-  initIdentifierInfo(C.getASTContext());
-
-  if (!isBlockingFunction(Call)
-      && !isLockFunction(Call)
-      && !isUnlockFunction(Call))
-    return;
-
-  ProgramStateRef State = C.getState();
-  unsigned mutexCount = State->get<MutexCounter>();
-  if (isUnlockFunction(Call) && mutexCount > 0) {
-    State = State->set<MutexCounter>(--mutexCount);
-    C.addTransition(State);
-  } else if (isLockFunction(Call)) {
-    State = State->set<MutexCounter>(++mutexCount);
-    C.addTransition(State);
-  } else if (mutexCount > 0) {
-    SymbolRef BlockDesc = Call.getReturnValue().getAsSymbol();
-    reportBlockInCritSection(BlockDesc, Call, C);
+  if (isBlockingInCritSection(Call, C)) {
+    reportBlockInCritSection(Call, C);
+  } else if (auto Lock = checkLock(Call, C)) {
+    handleLock(*Lock, Call, C);
+  } else if (auto Unlock = checkUnlock(Call, C)) {
+    handleUnlock(*Unlock, Call, C);
   }
 }
 
 void BlockInCriticalSectionChecker::reportBlockInCritSection(
-    SymbolRef BlockDescSym, const CallEvent &Call, CheckerContext &C) const {
-  ExplodedNode *ErrNode = C.generateNonFatalErrorNode();
+    const CallEvent &Call, CheckerContext &C) const {
+  ExplodedNode *ErrNode = C.generateNonFatalErrorNode(C.getState());
   if (!ErrNode)
     return;
 
@@ -147,14 +327,63 @@ void BlockInCriticalSectionChecker::reportBlockInCritSection(
   auto R = std::make_unique<PathSensitiveBugReport>(BlockInCritSectionBugType,
                                                     os.str(), ErrNode);
   R->addRange(Call.getSourceRange());
-  R->markInteresting(BlockDescSym);
+  R->markInteresting(Call.getReturnValue());
   C.emitReport(std::move(R));
 }
 
+const NoteTag *
+BlockInCriticalSectionChecker::createCritSectionNote(CritSectionMarker M,
+                                                     CheckerContext &C) const {
+  const BugType *BT = &this->BlockInCritSectionBugType;
+  return C.getNoteTag([M, BT](PathSensitiveBugReport &BR,
+                              llvm::raw_ostream &OS) {
+    if (&BR.getBugType() != BT)
+      return;
+
+    // Get the lock events for the mutex of the current line's lock event.
+    const auto CritSectionBegins =
+        BR.getErrorNode()->getState()->get<ActiveCritSections>();
+    llvm::SmallVector<CritSectionMarker, 4> LocksForMutex;
+    llvm::copy_if(
+        CritSectionBegins, std::back_inserter(LocksForMutex),
+        [M](const auto &Marker) { return Marker.LockReg == M.LockReg; });
+    if (LocksForMutex.empty())
+      return;
+
+    // As the ImmutableList builds the locks by prepending them, we
+    // reverse the list to get the correct order.
+    std::reverse(LocksForMutex.begin(), LocksForMutex.end());
+
+    // Find the index of the lock expression in the list of all locks for a
+    // given mutex (in acquisition order).
+    const auto *Position =
+        llvm::find_if(std::as_const(LocksForMutex), [M](const auto &Marker) {
+          return Marker.LockExpr == M.LockExpr;
+        });
+    if (Position == LocksForMutex.end())
+      return;
+
+    // If there is only one lock event, we don't need to specify how many times
+    // the critical section was entered.
+    if (LocksForMutex.size() == 1) {
+      OS << "Entering critical section here";
+      return;
+    }
+
+    const auto IndexOfLock =
+        std::distance(std::as_const(LocksForMutex).begin(), Position);
+
+    const auto OrdinalOfLock = IndexOfLock + 1;
+    OS << "Entering critical section for the " << OrdinalOfLock
+       << llvm::getOrdinalSuffix(OrdinalOfLock) << " time here";
+  });
+}
+
 void ento::registerBlockInCriticalSectionChecker(CheckerManager &mgr) {
   mgr.registerChecker<BlockInCriticalSectionChecker>();
 }
 
-bool ento::shouldRegisterBlockInCriticalSectionChecker(const CheckerManager &mgr) {
+bool ento::shouldRegisterBlockInCriticalSectionChecker(
+    const CheckerManager &mgr) {
   return true;
 }
diff --git a/clang/test/Analysis/block-in-critical-section.cpp b/clang/test/Analysis/block-in-critical-section.cpp
index fcf6188fc033ec..87c26b9f1b5209 100644
--- a/clang/test/Analysis/block-in-critical-section.cpp
+++ b/clang/test/Analysis/block-in-critical-section.cpp
@@ -1,4 +1,8 @@
-// RUN: %clang_analyze_cc1 -analyzer-checker=alpha.unix.BlockInCriticalSection -std=c++11 -verify %s
+// RUN: %clang_analyze_cc1 \
+// RUN:   -analyzer-checker=alpha.unix.BlockInCriticalSection \
+// RUN:   -std=c++11 \
+// RUN:   -analyzer-output text \
+// RUN:   -verify %s
 
 void sleep(int x) {}
 
@@ -21,108 +25,242 @@ template<typename T>
 struct not_real_lock {
   not_real_lock<T>(std::mutex) {}
 };
-}
+} // namespace std
+
+struct FILE;
+int getc(FILE *stream);
+char* fgets(char *str, FILE *stream);
+using ssize_t = long long;
+using size_t = unsigned long long;
+ssize_t read(int fd, void *buf, size_t count);
+ssize_t recv(int sockfd, void *buf, size_t len, int flags);
 
-void getc() {}
-void fgets() {}
-void read() {}
-void recv() {}
+struct pthread_mutex_t;
+void pthread_mutex_lock(pthread_mutex_t *mutex);
+void pthread_mutex_trylock(pthread_mutex_t *mutex);
+void pthread_mutex_unlock(pthread_mutex_t *mutex);
 
-void pthread_mutex_lock() {}
-void pthread_mutex_trylock() {}
-void pthread_mutex_unlock() {}
+struct mtx_t;
+void mtx_lock(mtx_t *mutex);
+void mtx_timedlock(mtx_t *mutex);
+void mtx_trylock(mtx_t *mutex);
+void mtx_unlock(mtx_t *mutex);
 
-void mtx_lock() {}
-void mtx_timedlock() {}
-void mtx_trylock() {}
-void mtx_unlock() {}
+// global params for dummy function calls
+FILE *stream;
+char *str;
+int fd;
+void *buf;
+size_t count;
+int sockfd;
+size_t len;
+int flags;
 
 void testBlockInCriticalSectionWithStdMutex() {
   std::mutex m;
-  m.lock();
+  m.lock(); // expected-note 5{{Entering critical section here}}
   sleep(3); // expected-warning {{Call to blocking function 'sleep' inside of critical section}}
-  getc(); // expected-warning {{Call to blocking function 'getc' inside of critical section}}
-  fgets(); // expected-warning {{Call to blocking function 'fgets' inside of critical section}}
-  read(); // expected-warning {{Call to blocking function 'read' inside of critical section}}
-  recv(); // expected-warning {{Call to blocking function 'recv' inside of critical section}}
+            // expected-note at -1 {{Call to blocking function 'sleep' inside of critical section}}
+  getc(stream); // expected-warning {{Call to blocking function 'getc' inside of critical section}}
+          // expected-note at -1 {{Call to blocking function 'getc' inside of critical section}}
+  fgets(str, stream); // expected-warning {{Call to blocking function 'fgets' inside of critical section}}
+           // expected-note at -1 {{Call to blocking function 'fgets' inside of critical section}}
+  read(fd, buf, count); // expected-warning {{Call to blocking function 'read' inside of critical section}}
+          // expected-note at -1 {{Call to blocking function 'read' inside of critical section}}
+  recv(sockfd, buf, count, flags); // expected-warning {{Call to blocking function 'recv' inside of critical section}}
+          // expected-note at -1 {{Call to blocking function 'recv' inside of critical section}}
   m.unlock();
 }
 
-void testBlockInCriticalSectionWithPthreadMutex() {
-  pthread_mutex_lock();
+void testBlockInCriticalSectionWithPthreadMutex(pthread_mutex_t *mutex) {
+  pthread_mutex_lock(mutex); // expected-note 5{{Entering critical section here}}
   sleep(3); // expected-warning {{Call to blocking function 'sleep' inside of critical section}}
-  getc(); // expected-warning {{Call to blocking function 'getc' inside of critical section}}
-  fgets(); // expected-warning {{Call to blocking function 'fgets' inside of critical section}}
-  read(); // expected-warning {{Call to blocking function 'read' inside of critical section}}
-  recv(); // expected-warning {{Call to blocking function 'recv' inside of critical section}}
-  pthread_mutex_unlock();
+            // expected-note at -1 {{Call to blocking function 'sleep' inside of critical section}}
+  getc(stream); // expected-warning {{Call to blocking function 'getc' inside of critical section}}
+          // expected-note at -1 {{Call to blocking function 'getc' inside of critical section}}
+  fgets(str, stream); // expected-warning {{Call to blocking function 'fgets' inside of critical section}}
+           // expected-note at -1 {{Call to blocking function 'fgets' inside of critical section}}
+  read(fd, buf, count); // expected-warning {{Call to blocking function 'read' inside of critical section}}
+          // expected-note at -1 {{Call to blocking function 'read' inside of critical section}}
+  recv(sockfd, buf, count, flags); // expected-warning {{Call to blocking function 'recv' inside of critical section}}
+          // expected-note at -1 {{Call to blocking function 'recv' inside of critical section}}
+  pthread_mutex_unlock(mutex);
 
-  pthread_mutex_trylock();
+  pthread_mutex_trylock(mutex); // expected-note 5{{Entering critical section here}}
   sleep(3); // expected-warning {{Call to blocking function 'sleep' inside of critical section}}
-  getc(); // expected-warning {{Call to blocking function 'getc' inside of critical section}}
-  fgets(); // expected-warning {{Call to blocking function 'fgets' inside of critical section}}
-  read(); // expected-warning {{Call to blocking function 'read' inside of critical section}}
-  recv(); // expected-warning {{Call to blocking function 'recv' inside of critical section}}
-  pthread_mutex_unlock();
+            // expected-note at -1 {{Call to blocking function 'sleep' inside of critical section}}
+  getc(stream); // expected-warning {{Call to blocking function 'getc' inside of critical section}}
+          // expected-note at -1 {{Call to blocking function 'getc' inside of critical section}}
+  fgets(str, stream); // expected-warning {{Call to blocking function 'fgets' inside of critical section}}
+           // expected-note at -1 {{Call to blocking function 'fgets' inside of critical section}}
+  read(fd, buf, count); // expected-warning {{Call to blocking function 'read' inside of critical section}}
+          // expected-note at -1 {{Call to blocking function 'read' inside of critical section}}
+  recv(sockfd, buf, count, flags); // expected-warning {{Call to blocking function 'recv' inside of critical section}}
+          // expected-note at -1 {{Call to blocking function 'recv' inside of critical section}}
+  pthread_mutex_unlock(mutex);
 }
 
-void testBlockInCriticalSectionC11Locks() {
-  mtx_lock();
+void testBlockInCriticalSectionC11Locks(mtx_t *mutex) {
+  mtx_lock(mutex); // expected-note 5{{Entering critical section here}}
   sleep(3); // expected-warning {{Call to blocking function 'sleep' inside of critical section}}
-  getc(); // expected-warning {{Call to blocking function 'getc' inside of critical section}}
-  fgets(); // expected-warning {{Call to blocking function 'fgets' inside of critical section}}
-  read(); // expected-warning {{Call to blocking function 'read' inside of critical section}}
-  recv(); // expected-warning {{Call to blocking function 'recv' inside of critical section}}
-  mtx_unlock();
+            // expected-note at -1 {{Call to blocking function 'sleep' inside of critical section}}
+  getc(stream); // expected-warning {{Call to blocking function 'getc' inside of critical section}}
+          // expected-note at -1 {{Call to blocking function 'getc' inside of critical section}}
+  fgets(str, stream); // expected-warning {{Call to blocking function 'fgets' inside of critical section}}
+           // expected-note at -1 {{Call to blocking function 'fgets' inside of critical section}}
+  read(fd, buf, count); // expected-warning {{Call to blocking function 'read' inside of critical section}}
+          // expected-note at -1 {{Call to blocking function 'read' inside of critical section}}
+  recv(sockfd, buf, count, flags); // expected-warning {{Call to blocking function 'recv' inside of critical section}}
+          // expected-note at -1 {{Call to blocking function 'recv' inside of critical section}}
+  mtx_unlock(mutex);
 
-  mtx_timedlock();
+  mtx_timedlock(mutex); // expected-note 5{{Entering critical section here}}
   sleep(3); // expected-warning {{Call to blocking function 'sleep' inside of critical section}}
-  getc(); // expected-warning {{Call to blocking function 'getc' inside of critical section}}
-  fgets(); // expected-warning {{Call to blocking function 'fgets' inside of critical section}}
-  read(); // expected-warning {{Call to blocking function 'read' inside of critical section}}
-  recv(); // expected-warning {{Call to blocking function 'recv' inside of critical section}}
-  mtx_unlock();
+            // expected-note at -1 {{Call to blocking function 'sleep' inside of critical section}}
+  getc(stream); // expected-warning {{Call to blocking function 'getc' inside of critical section}}
+          // expected-note at -1 {{Call to blocking function 'getc' inside of critical section}}
+  fgets(str, stream); // expected-warning {{Call to blocking function 'fgets' inside of critical section}}
+           // expected-note at -1 {{Call to blocking function 'fgets' inside of critical section}}
+  read(fd, buf, count); // expected-warning {{Call to blocking function 'read' inside of critical section}}
+          // expected-note at -1 {{Call to blocking function 'read' inside of critical section}}
+  recv(sockfd, buf, count, flags); // expected-warning {{Call to blocking function 'recv' inside of critical section}}
+          // expected-note at -1 {{Call to blocking function 'recv' inside of critical section}}
+  mtx_unlock(mutex);
 
-  mtx_trylock();
+  mtx_trylock(mutex); // expected-note 5{{Entering critical section here}}
   sleep(3); // expected-warning {{Call to blocking function 'sleep' inside of critical section}}
-  getc(); // expected-warning {{Call to blocking function 'getc' inside of critical section}}
-  fgets(); // expected-warning {{Call to blocking function 'fgets' inside of critical section}}
-  read(); // expected-warning {{Call to blocking function 'read' inside of critical section}}
-  recv(); // expected-warning {{Call to blocking function 'recv' inside of critical section}}
-  mtx_unlock();
+            // expected-note at -1 {{Call to blocking function 'sleep' inside of critical section}}
+  getc(stream); // expected-warning {{Call to blocking function 'getc' inside of critical section}}
+          // expected-note at -1 {{Call to blocking function 'getc' inside of critical section}}
+  fgets(str, stream); // expected-warning {{Call to blocking function 'fgets' inside of critical section}}
+           // expected-note at -1 {{Call to blocking function 'fgets' inside of critical section}}
+  read(fd, buf, count); // expected-warning {{Call to blocking function 'read' inside of critical section}}
+          // expected-note at -1 {{Call to blocking function 'read' inside of critical section}}
+  recv(sockfd, buf, count, flags); // expected-warning {{Call to blocking function 'recv' inside of critical section}}
+          // expected-note at -1 {{Call to blocking function 'recv' inside of critical section}}
+  mtx_unlock(mutex);
+}
+
+void testMultipleBlockingCalls() {
+  std::mutex m;
+  m.lock(); // expected-note 1{{Entering critical section here}}
+  sleep(1); // expected-warning {{Call to blocking function 'sleep' inside of critical section}}
+            // expected-note at -1 {{Call to blocking function 'sleep' inside of critical section}}
+  m.unlock();
+  sleep(2); // no-warning
 }
 
-void testBlockInCriticalSectionWithNestedMutexes() {
+void testMultipleMutexesMultipleBlockingCalls() {
   std::mutex m, n, k;
-  m.lock();
-  n.lock();
-  k.lock();
-  sleep(3); // expected-warning {{Call to blocking function 'sleep' inside of critical section}}
+  m.lock(); // expected-note 2{{Entering critical section here}}
+  n.lock(); // expected-note 2{{Entering critical section here}}
+  k.lock(); // expected-note 1{{Entering critical section here}}
+  sleep(1); // expected-warning {{Call to blocking function 'sleep' inside of critical section}}
+            // expected-note at -1 {{Call to blocking function 'sleep' inside of critical section}}
   k.unlock();
-  sleep(5); // expected-warning {{Call to blocking function 'sleep' inside of critical section}}
+  sleep(2); // expected-warning {{Call to blocking function 'sleep' inside of critical section}}
+            // expected-note at -1 {{Call to blocking function 'sleep' inside of critical section}}
+}
+
+
+void testRecursiveAcquisition() {
+  std::mutex m;
+  m.lock(); // expected-note {{Entering critical section for the 1st time here}}
+  m.lock(); // expected-note {{Entering critical section for the 2nd time here}}
+  sleep(1); // expected-warning {{Call to blocking function 'sleep' inside of critical section}}
+            // expected-note at -1 {{Call to blocking function 'sleep' inside of critical section}}
+  m.unlock();
+  m.unlock();
+}
+
+void testRecursiveAcquisitionWithMultipleBlockingCalls() {
+  std::mutex m;
+  m.lock(); // expected-note 1{{Entering critical section for the 1st time here}}
+            // expected-note at -1 {{Entering critical section here}}
+  m.lock(); // expected-note 1{{Entering critical section for the 2nd time here}}
+  sleep(1); // expected-warning {{Call to blocking function 'sleep' inside of critical section}}
+            // expected-note at -1 {{Call to blocking function 'sleep' inside of critical section}}
+  m.unlock();
+  // this next 'sleep' call is only in the critical section of the first lock
+  sleep(2); // expected-warning {{Call to blocking function 'sleep' inside of critical section}}
+            // expected-note at -1 {{Call to blocking function 'sleep' inside of critical section}}
+  m.unlock();
+}
+
+void testRecursiveAcquisitionWithMultipleMutexes() {
+  std::mutex m, n;
+  m.lock(); // expected-note 1{{Entering critical section here}}
+  n.lock(); // expected-note 2{{Entering critical section here}}
+  sleep(1); // expected-warning {{Call to blocking function 'sleep' inside of critical section}}
+            // expected-note at -1 {{Call to blocking function 'sleep' inside of critical section}}
+  m.unlock();
+  // this next 'sleep' call is only in the critical section of mutex 'n'
+  sleep(2); // expected-warning {{Call to blocking function 'sleep' inside of critical section}}
+            // expected-note at -1 {{Call to blocking function 'sleep' inside of critical section}}
+  n.unlock();
+}
+
+
+void testNestedMutexes() {
+  std::mutex m, n, k;
+  m.lock(); // expected-note 3{{Entering critical section here}}
+  n.lock(); // expected-note 2{{Entering critical section here}}
+  k.lock(); // expected-note 1{{Entering critical section here}}
+  sleep(1); // expected-warning {{Call to blocking function 'sleep' inside of critical section}}
+            // expected-note at -1 {{Call to blocking function 'sleep' inside of critical section}}
+  k.unlock();
+  sleep(2); // expected-warning {{Call to blocking function 'sleep' inside of critical section}}
+            // expected-note at -1 {{Call to blocking function 'sleep' inside of critical section}}
   n.unlock();
   sleep(3); // expected-warning {{Call to blocking function 'sleep' inside of critical section}}
+            // expected-note at -1 {{Call to blocking function 'sleep' inside of critical section}}
+  m.unlock();
+  sleep(4); // no-warning
+}
+
+void testNonOverlappingMutexes() {
+  std::mutex m;
+  m.lock(); // There should be no warning here
+  m.unlock();
+  m.lock(); // expected-note {{Entering critical section here}}
+  sleep(1); // expected-warning {{Call to blocking function 'sleep' inside of critical section}}
+            // expected-note at -1 {{Call to blocking function 'sleep' inside of critical section}}
+  m.unlock();
+}
+
+void testMixedMutexLocksWithIntermittentUnlock() {
+  std::mutex m, n, k;
+  m.lock(); // expected-note {{Entering critical section here}}
+  n.lock(); // the problem is not is this lock's critical section
+  n.unlock();
+  k.lock(); // same as for n.lock()
+  k.unlock();
+  sleep(1); // expected-warning {{Call to blocking function 'sleep' inside of critical section}}
+            // expected-note at -1 {{Call to blocking function 'sleep' inside of critical section}}
   m.unlock();
-  sleep(3); // no-warning
 }
 
 void f() {
   sleep(1000); // expected-warning {{Call to blocking function 'sleep' inside of critical section}}
+               // expected-note at -1 {{Call to blocking function 'sleep' inside of critical section}}
 }
 
 void testBlockInCriticalSectionInterProcedural() {
   std::mutex m;
-  m.lock();
-  f();
+  m.lock(); // expected-note {{Entering critical section here}}
+  f(); // expected-note {{Calling 'f'}}
   m.unlock();
 }
 
+void unknown_function_that_may_lock(std::mutex &);
 void testBlockInCriticalSectionUnexpectedUnlock() {
   std::mutex m;
+  unknown_function_that_may_lock(m);
   m.unlock();
   sleep(1); // no-warning
-  m.lock();
-  sleep(1); // expected-warning {{Call to blocking function 'sleep' inside of critical section}}
+  m.lock(); // expected-note {{Entering critical section here}}
+  sleep(2); // expected-warning {{Call to blocking function 'sleep' inside of critical section}}
+            // expected-note at -1 {{Call to blocking function 'sleep' inside of critical section}}
 }
 
 void testBlockInCriticalSectionLockGuard() {
@@ -130,12 +268,13 @@ void testBlockInCriticalSectionLockGuard() {
   std::not_real_lock<std::mutex> not_real_lock(g_mutex);
   sleep(1); // no-warning
 
-  std::lock_guard<std::mutex> lock(g_mutex);
+  std::lock_guard<std::mutex> lock(g_mutex); // expected-note {{Entering critical section here}}
   sleep(1); // expected-warning {{Call to blocking function 'sleep' inside of critical section}}
+            // expected-note at -1 {{Call to blocking function 'sleep' inside of critical section}}
 }
 
 void testBlockInCriticalSectionLockGuardNested() {
-  testBlockInCriticalSectionLockGuard();
+  testBlockInCriticalSectionLockGuard(); // expected-note {{Calling 'testBlockInCriticalSectionLockGuard'}}
   sleep(1); // no-warning
 }
 
@@ -144,11 +283,12 @@ void testBlockInCriticalSectionUniqueLock() {
   std::not_real_lock<std::mutex> not_real_lock(g_mutex);
   sleep(1); // no-warning
 
-  std::unique_lock<std::mutex> lock(g_mutex);
+  std::unique_lock<std::mutex> lock(g_mutex); // expected-note {{Entering critical section here}}
   sleep(1); // expected-warning {{Call to blocking function 'sleep' inside of critical section}}
+            // expected-note at -1 {{Call to blocking function 'sleep' inside of critical section}}
 }
 
 void testBlockInCriticalSectionUniqueLockNested() {
-  testBlockInCriticalSectionUniqueLock();
+  testBlockInCriticalSectionUniqueLock(); // expected-note {{Calling 'testBlockInCriticalSectionUniqueLock'}}
   sleep(1); // no-warning
 }



More information about the cfe-commits mailing list