Coverage Report

Created: 2023-09-30 09:22

/Users/buildslave/jenkins/workspace/coverage/llvm-project/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp
Line
Count
Source (jump to first uncovered line)
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
24
using namespace clang;
25
using namespace ento;
26
27
namespace {
28
29
class 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
49
public:
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
64
REGISTER_TRAIT_WITH_PROGRAMSTATE(MutexCounter, unsigned)
65
66
BlockInCriticalSectionChecker::BlockInCriticalSectionChecker()
67
5
    : IILockGuard(nullptr), IIUniqueLock(nullptr), LockFn({"lock"}),
68
5
      UnlockFn({"unlock"}), SleepFn({"sleep"}), GetcFn({"getc"}),
69
5
      FgetsFn({"fgets"}), ReadFn({"read"}), RecvFn({"recv"}),
70
5
      PthreadLockFn({"pthread_mutex_lock"}),
71
5
      PthreadTryLockFn({"pthread_mutex_trylock"}),
72
5
      PthreadUnlockFn({"pthread_mutex_unlock"}), MtxLock({"mtx_lock"}),
73
5
      MtxTimedLock({"mtx_timedlock"}), MtxTryLock({"mtx_trylock"}),
74
5
      MtxUnlock({"mtx_unlock"}), ClassLockGuard("lock_guard"),
75
5
      ClassUniqueLock("unique_lock"), IdentifierInfoInitialized(false) {
76
  // Initialize the bug type.
77
5
  BlockInCritSectionBugType.reset(
78
5
      new BugType(this, "Call to blocking function in critical section",
79
5
                        "Blocking Error"));
80
5
}
81
82
109
void BlockInCriticalSectionChecker::initIdentifierInfo(ASTContext &Ctx) const {
83
109
  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
5
    IILockGuard  = &Ctx.Idents.get(ClassLockGuard);
90
5
    IIUniqueLock = &Ctx.Idents.get(ClassUniqueLock);
91
5
    IdentifierInfoInitialized = true;
92
5
  }
93
109
}
94
95
109
bool BlockInCriticalSectionChecker::isBlockingFunction(const CallEvent &Call) const {
96
109
  return matchesAny(Call, SleepFn, GetcFn, FgetsFn, ReadFn, RecvFn);
97
109
}
98
99
123
bool BlockInCriticalSectionChecker::isLockFunction(const CallEvent &Call) const {
100
123
  if (const auto *Ctor = dyn_cast<CXXConstructorCall>(&Call)) {
101
18
    auto IdentifierInfo = Ctor->getDecl()->getParent()->getIdentifier();
102
18
    if (IdentifierInfo == IILockGuard || 
IdentifierInfo == IIUniqueLock16
)
103
4
      return true;
104
18
  }
105
106
119
  return matchesAny(Call, LockFn, PthreadLockFn, PthreadTryLockFn, MtxLock,
107
119
                    MtxTimedLock, MtxTryLock);
108
123
}
109
110
122
bool BlockInCriticalSectionChecker::isUnlockFunction(const CallEvent &Call) const {
111
122
  if (const auto *Dtor = dyn_cast<CXXDestructorCall>(&Call)) {
112
4
    const auto *DRecordDecl = cast<CXXRecordDecl>(Dtor->getDecl()->getParent());
113
4
    auto IdentifierInfo = DRecordDecl->getIdentifier();
114
4
    if (IdentifierInfo == IILockGuard || 
IdentifierInfo == IIUniqueLock2
)
115
4
      return true;
116
4
  }
117
118
118
  return matchesAny(Call, UnlockFn, PthreadUnlockFn, MtxUnlock);
119
122
}
120
121
void BlockInCriticalSectionChecker::checkPostCall(const CallEvent &Call,
122
109
                                                  CheckerContext &C) const {
123
109
  initIdentifierInfo(C.getASTContext());
124
125
109
  if (!isBlockingFunction(Call)
126
109
      && 
!isLockFunction(Call)66
127
109
      && 
!isUnlockFunction(Call)53
)
128
40
    return;
129
130
69
  ProgramStateRef State = C.getState();
131
69
  unsigned mutexCount = State->get<MutexCounter>();
132
69
  if (isUnlockFunction(Call) && 
mutexCount > 013
) {
133
12
    State = State->set<MutexCounter>(--mutexCount);
134
12
    C.addTransition(State);
135
57
  } else if (isLockFunction(Call)) {
136
13
    State = State->set<MutexCounter>(++mutexCount);
137
13
    C.addTransition(State);
138
44
  } else if (mutexCount > 0) {
139
37
    SymbolRef BlockDesc = Call.getReturnValue().getAsSymbol();
140
37
    reportBlockInCritSection(BlockDesc, Call, C);
141
37
  }
142
69
}
143
144
void BlockInCriticalSectionChecker::reportBlockInCritSection(
145
37
    SymbolRef BlockDescSym, const CallEvent &Call, CheckerContext &C) const {
146
37
  ExplodedNode *ErrNode = C.generateNonFatalErrorNode();
147
37
  if (!ErrNode)
148
0
    return;
149
150
37
  std::string msg;
151
37
  llvm::raw_string_ostream os(msg);
152
37
  os << "Call to blocking function '" << Call.getCalleeIdentifier()->getName()
153
37
     << "' inside of critical section";
154
37
  auto R = std::make_unique<PathSensitiveBugReport>(*BlockInCritSectionBugType,
155
37
                                                    os.str(), ErrNode);
156
37
  R->addRange(Call.getSourceRange());
157
37
  R->markInteresting(BlockDescSym);
158
37
  C.emitReport(std::move(R));
159
37
}
160
161
5
void ento::registerBlockInCriticalSectionChecker(CheckerManager &mgr) {
162
5
  mgr.registerChecker<BlockInCriticalSectionChecker>();
163
5
}
164
165
10
bool ento::shouldRegisterBlockInCriticalSectionChecker(const CheckerManager &mgr) {
166
10
  return true;
167
10
}