clang  14.0.0git
BlockInCriticalSectionChecker.cpp
Go to the documentation of this file.
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 
22 
23 using namespace clang;
24 using namespace ento;
25 
26 namespace {
27 
28 class 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 
48 public:
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 
63 REGISTER_TRAIT_WITH_PROGRAMSTATE(MutexCounter, unsigned)
64 
65 BlockInCriticalSectionChecker::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 
85 void 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 
98 bool 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 
109 bool 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 
127 bool 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 
143 void 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 
166 void 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 
183 void ento::registerBlockInCriticalSectionChecker(CheckerManager &mgr) {
184  mgr.registerChecker<BlockInCriticalSectionChecker>();
185 }
186 
187 bool ento::shouldRegisterBlockInCriticalSectionChecker(const CheckerManager &mgr) {
188  return true;
189 }
string
string(SUBSTRING ${CMAKE_CURRENT_BINARY_DIR} 0 ${PATH_LIB_START} PATH_HEAD) string(SUBSTRING $
Definition: CMakeLists.txt:22
clang::IdentifierTable::get
IdentifierInfo & get(StringRef Name)
Return the identifier token info for the specified named identifier.
Definition: IdentifierTable.h:592
clang::ento::ProgramStateRef
IntrusiveRefCntPtr< const ProgramState > ProgramStateRef
Definition: ProgramState_Fwd.h:37
AttributeLangSupport::C
@ C
Definition: SemaDeclAttr.cpp:54
clang::ento::SymbolRef
const SymExpr * SymbolRef
Definition: SymExpr.h:110
clang::index::SymbolRole::Call
@ Call
REGISTER_TRAIT_WITH_PROGRAMSTATE
#define REGISTER_TRAIT_WITH_PROGRAMSTATE(Name, Type)
Declares a program state trait for type Type called Name, and introduce a type named NameTy.
Definition: ProgramStateTrait.h:33
CallEvent.h
BuiltinCheckerRegistration.h
clang::ASTContext
Holds long-lived AST nodes (such as types and decls) that can be referred to throughout the semantic ...
Definition: ASTContext.h:212
false
#define false
Definition: stdbool.h:17
BugType.h
clang::ASTContext::Idents
IdentifierTable & Idents
Definition: ASTContext.h:648
State
LineState State
Definition: UnwrappedLineFormatter.cpp:986
clang::IdentifierInfo
One of these records is kept for each identifier that is lexed.
Definition: IdentifierTable.h:84
CheckerContext.h
Checker.h
clang
Definition: CalledOnceCheck.h:17