1//===-- BlockInCriticalSectionChecker.cpp -----------------------*- C++ -*-===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// Defines a checker for blocks in critical sections. This checker should find
10// the calls to blocking functions (for example: sleep, getc, fgets, read,
11// recv etc.) inside a critical section. When sleep(x) is called while a mutex
12// is held, other threades cannot lock the same mutex. This might take some
13// time, leading to bad performance or even deadlock.
14//
15//===----------------------------------------------------------------------===//
16
17#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
18#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
19#include "clang/StaticAnalyzer/Core/Checker.h"
20#include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h"
21#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
22#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
23
24using namespace clang;
25using namespace ento;
26
27namespace {
28
29class BlockInCriticalSectionChecker : public Checker<check::PostCall> {
30
31  mutable IdentifierInfo *IILockGuard, *IIUniqueLock;
32
33  CallDescription LockFn, UnlockFn, SleepFn, GetcFn, FgetsFn, ReadFn, RecvFn,
34                  PthreadLockFn, PthreadTryLockFn, PthreadUnlockFn,
35                  MtxLock, MtxTimedLock, MtxTryLock, MtxUnlock;
36
37  StringRef ClassLockGuard, ClassUniqueLock;
38
39  mutable bool IdentifierInfoInitialized;
40
41  std::unique_ptr<BugType> BlockInCritSectionBugType;
42
43  void initIdentifierInfo(ASTContext &Ctx) const;
44
45  void reportBlockInCritSection(SymbolRef FileDescSym,
46                                const CallEvent &call,
47                                CheckerContext &C) const;
48
49public:
50  BlockInCriticalSectionChecker();
51
52  bool isBlockingFunction(const CallEvent &Call) const;
53  bool isLockFunction(const CallEvent &Call) const;
54  bool isUnlockFunction(const CallEvent &Call) const;
55
56  /// Process unlock.
57  /// Process lock.
58  /// Process blocking functions (sleep, getc, fgets, read, recv)
59  void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
60};
61
62} // end anonymous namespace
63
64REGISTER_TRAIT_WITH_PROGRAMSTATE(MutexCounter, unsigned)
65
66BlockInCriticalSectionChecker::BlockInCriticalSectionChecker()
67    : IILockGuard(nullptr), IIUniqueLock(nullptr), LockFn({"lock"}),
68      UnlockFn({"unlock"}), SleepFn({"sleep"}), GetcFn({"getc"}),
69      FgetsFn({"fgets"}), ReadFn({"read"}), RecvFn({"recv"}),
70      PthreadLockFn({"pthread_mutex_lock"}),
71      PthreadTryLockFn({"pthread_mutex_trylock"}),
72      PthreadUnlockFn({"pthread_mutex_unlock"}), MtxLock({"mtx_lock"}),
73      MtxTimedLock({"mtx_timedlock"}), MtxTryLock({"mtx_trylock"}),
74      MtxUnlock({"mtx_unlock"}), ClassLockGuard("lock_guard"),
75      ClassUniqueLock("unique_lock"), IdentifierInfoInitialized(false) {
76  // Initialize the bug type.
77  BlockInCritSectionBugType.reset(
78      new BugType(this, "Call to blocking function in critical section",
79                        "Blocking Error"));
80}
81
82void BlockInCriticalSectionChecker::initIdentifierInfo(ASTContext &Ctx) const {
83  if (!IdentifierInfoInitialized) {
84    /* In case of checking C code, or when the corresponding headers are not
85     * included, we might end up query the identifier table every time when this
86     * function is called instead of early returning it. To avoid this, a bool
87     * variable (IdentifierInfoInitialized) is used and the function will be run
88     * only once. */
89    IILockGuard  = &Ctx.Idents.get(ClassLockGuard);
90    IIUniqueLock = &Ctx.Idents.get(ClassUniqueLock);
91    IdentifierInfoInitialized = true;
92  }
93}
94
95bool BlockInCriticalSectionChecker::isBlockingFunction(const CallEvent &Call) const {
96  return matchesAny(Call, SleepFn, GetcFn, FgetsFn, ReadFn, RecvFn);
97}
98
99bool BlockInCriticalSectionChecker::isLockFunction(const CallEvent &Call) const {
100  if (const auto *Ctor = dyn_cast<CXXConstructorCall>(&Call)) {
101    auto IdentifierInfo = Ctor->getDecl()->getParent()->getIdentifier();
102    if (IdentifierInfo == IILockGuard || IdentifierInfo == IIUniqueLock)
103      return true;
104  }
105
106  return matchesAny(Call, LockFn, PthreadLockFn, PthreadTryLockFn, MtxLock,
107                    MtxTimedLock, MtxTryLock);
108}
109
110bool BlockInCriticalSectionChecker::isUnlockFunction(const CallEvent &Call) const {
111  if (const auto *Dtor = dyn_cast<CXXDestructorCall>(&Call)) {
112    const auto *DRecordDecl = cast<CXXRecordDecl>(Dtor->getDecl()->getParent());
113    auto IdentifierInfo = DRecordDecl->getIdentifier();
114    if (IdentifierInfo == IILockGuard || IdentifierInfo == IIUniqueLock)
115      return true;
116  }
117
118  return matchesAny(Call, UnlockFn, PthreadUnlockFn, MtxUnlock);
119}
120
121void BlockInCriticalSectionChecker::checkPostCall(const CallEvent &Call,
122                                                  CheckerContext &C) const {
123  initIdentifierInfo(C.getASTContext());
124
125  if (!isBlockingFunction(Call)
126      && !isLockFunction(Call)
127      && !isUnlockFunction(Call))
128    return;
129
130  ProgramStateRef State = C.getState();
131  unsigned mutexCount = State->get<MutexCounter>();
132  if (isUnlockFunction(Call) && mutexCount > 0) {
133    State = State->set<MutexCounter>(--mutexCount);
134    C.addTransition(State);
135  } else if (isLockFunction(Call)) {
136    State = State->set<MutexCounter>(++mutexCount);
137    C.addTransition(State);
138  } else if (mutexCount > 0) {
139    SymbolRef BlockDesc = Call.getReturnValue().getAsSymbol();
140    reportBlockInCritSection(BlockDesc, Call, C);
141  }
142}
143
144void BlockInCriticalSectionChecker::reportBlockInCritSection(
145    SymbolRef BlockDescSym, const CallEvent &Call, CheckerContext &C) const {
146  ExplodedNode *ErrNode = C.generateNonFatalErrorNode();
147  if (!ErrNode)
148    return;
149
150  std::string msg;
151  llvm::raw_string_ostream os(msg);
152  os << "Call to blocking function '" << Call.getCalleeIdentifier()->getName()
153     << "' inside of critical section";
154  auto R = std::make_unique<PathSensitiveBugReport>(*BlockInCritSectionBugType,
155                                                    os.str(), ErrNode);
156  R->addRange(Call.getSourceRange());
157  R->markInteresting(BlockDescSym);
158  C.emitReport(std::move(R));
159}
160
161void ento::registerBlockInCriticalSectionChecker(CheckerManager &mgr) {
162  mgr.registerChecker<BlockInCriticalSectionChecker>();
163}
164
165bool ento::shouldRegisterBlockInCriticalSectionChecker(const CheckerManager &mgr) {
166  return true;
167}
168