BlockInCriticalSectionChecker.cpp revision 360784
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/CallEvent.h"
21#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
22
23using namespace clang;
24using namespace ento;
25
26namespace {
27
28class BlockInCriticalSectionChecker : public Checker<check::PostCall> {
29
30  mutable IdentifierInfo *IILockGuard, *IIUniqueLock;
31
32  CallDescription LockFn, UnlockFn, SleepFn, GetcFn, FgetsFn, ReadFn, RecvFn,
33                  PthreadLockFn, PthreadTryLockFn, PthreadUnlockFn,
34                  MtxLock, MtxTimedLock, MtxTryLock, MtxUnlock;
35
36  StringRef ClassLockGuard, ClassUniqueLock;
37
38  mutable bool IdentifierInfoInitialized;
39
40  std::unique_ptr<BugType> BlockInCritSectionBugType;
41
42  void initIdentifierInfo(ASTContext &Ctx) const;
43
44  void reportBlockInCritSection(SymbolRef FileDescSym,
45                                const CallEvent &call,
46                                CheckerContext &C) const;
47
48public:
49  BlockInCriticalSectionChecker();
50
51  bool isBlockingFunction(const CallEvent &Call) const;
52  bool isLockFunction(const CallEvent &Call) const;
53  bool isUnlockFunction(const CallEvent &Call) const;
54
55  /// Process unlock.
56  /// Process lock.
57  /// Process blocking functions (sleep, getc, fgets, read, recv)
58  void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
59};
60
61} // end anonymous namespace
62
63REGISTER_TRAIT_WITH_PROGRAMSTATE(MutexCounter, unsigned)
64
65BlockInCriticalSectionChecker::BlockInCriticalSectionChecker()
66    : IILockGuard(nullptr), IIUniqueLock(nullptr),
67      LockFn("lock"), UnlockFn("unlock"), SleepFn("sleep"), GetcFn("getc"),
68      FgetsFn("fgets"), ReadFn("read"), RecvFn("recv"),
69      PthreadLockFn("pthread_mutex_lock"),
70      PthreadTryLockFn("pthread_mutex_trylock"),
71      PthreadUnlockFn("pthread_mutex_unlock"),
72      MtxLock("mtx_lock"),
73      MtxTimedLock("mtx_timedlock"),
74      MtxTryLock("mtx_trylock"),
75      MtxUnlock("mtx_unlock"),
76      ClassLockGuard("lock_guard"),
77      ClassUniqueLock("unique_lock"),
78      IdentifierInfoInitialized(false) {
79  // Initialize the bug type.
80  BlockInCritSectionBugType.reset(
81      new BugType(this, "Call to blocking function in critical section",
82                        "Blocking Error"));
83}
84
85void BlockInCriticalSectionChecker::initIdentifierInfo(ASTContext &Ctx) const {
86  if (!IdentifierInfoInitialized) {
87    /* In case of checking C code, or when the corresponding headers are not
88     * included, we might end up query the identifier table every time when this
89     * function is called instead of early returning it. To avoid this, a bool
90     * variable (IdentifierInfoInitialized) is used and the function will be run
91     * only once. */
92    IILockGuard  = &Ctx.Idents.get(ClassLockGuard);
93    IIUniqueLock = &Ctx.Idents.get(ClassUniqueLock);
94    IdentifierInfoInitialized = true;
95  }
96}
97
98bool BlockInCriticalSectionChecker::isBlockingFunction(const CallEvent &Call) const {
99  if (Call.isCalled(SleepFn)
100      || Call.isCalled(GetcFn)
101      || Call.isCalled(FgetsFn)
102      || Call.isCalled(ReadFn)
103      || Call.isCalled(RecvFn)) {
104    return true;
105  }
106  return false;
107}
108
109bool BlockInCriticalSectionChecker::isLockFunction(const CallEvent &Call) const {
110  if (const auto *Ctor = dyn_cast<CXXConstructorCall>(&Call)) {
111    auto IdentifierInfo = Ctor->getDecl()->getParent()->getIdentifier();
112    if (IdentifierInfo == IILockGuard || IdentifierInfo == IIUniqueLock)
113      return true;
114  }
115
116  if (Call.isCalled(LockFn)
117      || Call.isCalled(PthreadLockFn)
118      || Call.isCalled(PthreadTryLockFn)
119      || Call.isCalled(MtxLock)
120      || Call.isCalled(MtxTimedLock)
121      || Call.isCalled(MtxTryLock)) {
122    return true;
123  }
124  return false;
125}
126
127bool BlockInCriticalSectionChecker::isUnlockFunction(const CallEvent &Call) const {
128  if (const auto *Dtor = dyn_cast<CXXDestructorCall>(&Call)) {
129    const auto *DRecordDecl = cast<CXXRecordDecl>(Dtor->getDecl()->getParent());
130    auto IdentifierInfo = DRecordDecl->getIdentifier();
131    if (IdentifierInfo == IILockGuard || IdentifierInfo == IIUniqueLock)
132      return true;
133  }
134
135  if (Call.isCalled(UnlockFn)
136       || Call.isCalled(PthreadUnlockFn)
137       || Call.isCalled(MtxUnlock)) {
138    return true;
139  }
140  return false;
141}
142
143void BlockInCriticalSectionChecker::checkPostCall(const CallEvent &Call,
144                                                  CheckerContext &C) const {
145  initIdentifierInfo(C.getASTContext());
146
147  if (!isBlockingFunction(Call)
148      && !isLockFunction(Call)
149      && !isUnlockFunction(Call))
150    return;
151
152  ProgramStateRef State = C.getState();
153  unsigned mutexCount = State->get<MutexCounter>();
154  if (isUnlockFunction(Call) && mutexCount > 0) {
155    State = State->set<MutexCounter>(--mutexCount);
156    C.addTransition(State);
157  } else if (isLockFunction(Call)) {
158    State = State->set<MutexCounter>(++mutexCount);
159    C.addTransition(State);
160  } else if (mutexCount > 0) {
161    SymbolRef BlockDesc = Call.getReturnValue().getAsSymbol();
162    reportBlockInCritSection(BlockDesc, Call, C);
163  }
164}
165
166void BlockInCriticalSectionChecker::reportBlockInCritSection(
167    SymbolRef BlockDescSym, const CallEvent &Call, CheckerContext &C) const {
168  ExplodedNode *ErrNode = C.generateNonFatalErrorNode();
169  if (!ErrNode)
170    return;
171
172  std::string msg;
173  llvm::raw_string_ostream os(msg);
174  os << "Call to blocking function '" << Call.getCalleeIdentifier()->getName()
175     << "' inside of critical section";
176  auto R = std::make_unique<PathSensitiveBugReport>(*BlockInCritSectionBugType,
177                                                    os.str(), ErrNode);
178  R->addRange(Call.getSourceRange());
179  R->markInteresting(BlockDescSym);
180  C.emitReport(std::move(R));
181}
182
183void ento::registerBlockInCriticalSectionChecker(CheckerManager &mgr) {
184  mgr.registerChecker<BlockInCriticalSectionChecker>();
185}
186
187bool ento::shouldRegisterBlockInCriticalSectionChecker(const LangOptions &LO) {
188  return true;
189}
190