clang  15.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 
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  : IILockGuard(nullptr), IIUniqueLock(nullptr),
68  LockFn("lock"), 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"),
73  MtxLock("mtx_lock"),
74  MtxTimedLock("mtx_timedlock"),
75  MtxTryLock("mtx_trylock"),
76  MtxUnlock("mtx_unlock"),
77  ClassLockGuard("lock_guard"),
78  ClassUniqueLock("unique_lock"),
79  IdentifierInfoInitialized(false) {
80  // Initialize the bug type.
81  BlockInCritSectionBugType.reset(
82  new BugType(this, "Call to blocking function in critical section",
83  "Blocking Error"));
84 }
85 
86 void BlockInCriticalSectionChecker::initIdentifierInfo(ASTContext &Ctx) const {
87  if (!IdentifierInfoInitialized) {
88  /* In case of checking C code, or when the corresponding headers are not
89  * included, we might end up query the identifier table every time when this
90  * function is called instead of early returning it. To avoid this, a bool
91  * variable (IdentifierInfoInitialized) is used and the function will be run
92  * only once. */
93  IILockGuard = &Ctx.Idents.get(ClassLockGuard);
94  IIUniqueLock = &Ctx.Idents.get(ClassUniqueLock);
95  IdentifierInfoInitialized = true;
96  }
97 }
98 
99 bool BlockInCriticalSectionChecker::isBlockingFunction(const CallEvent &Call) const {
100  return matchesAny(Call, SleepFn, GetcFn, FgetsFn, ReadFn, RecvFn);
101 }
102 
103 bool BlockInCriticalSectionChecker::isLockFunction(const CallEvent &Call) const {
104  if (const auto *Ctor = dyn_cast<CXXConstructorCall>(&Call)) {
105  auto IdentifierInfo = Ctor->getDecl()->getParent()->getIdentifier();
106  if (IdentifierInfo == IILockGuard || IdentifierInfo == IIUniqueLock)
107  return true;
108  }
109 
110  return matchesAny(Call, LockFn, PthreadLockFn, PthreadTryLockFn, MtxLock,
111  MtxTimedLock, MtxTryLock);
112 }
113 
114 bool BlockInCriticalSectionChecker::isUnlockFunction(const CallEvent &Call) const {
115  if (const auto *Dtor = dyn_cast<CXXDestructorCall>(&Call)) {
116  const auto *DRecordDecl = cast<CXXRecordDecl>(Dtor->getDecl()->getParent());
117  auto IdentifierInfo = DRecordDecl->getIdentifier();
118  if (IdentifierInfo == IILockGuard || IdentifierInfo == IIUniqueLock)
119  return true;
120  }
121 
122  return matchesAny(Call, UnlockFn, PthreadUnlockFn, MtxUnlock);
123 }
124 
125 void BlockInCriticalSectionChecker::checkPostCall(const CallEvent &Call,
126  CheckerContext &C) const {
127  initIdentifierInfo(C.getASTContext());
128 
129  if (!isBlockingFunction(Call)
130  && !isLockFunction(Call)
131  && !isUnlockFunction(Call))
132  return;
133 
134  ProgramStateRef State = C.getState();
135  unsigned mutexCount = State->get<MutexCounter>();
136  if (isUnlockFunction(Call) && mutexCount > 0) {
137  State = State->set<MutexCounter>(--mutexCount);
138  C.addTransition(State);
139  } else if (isLockFunction(Call)) {
140  State = State->set<MutexCounter>(++mutexCount);
141  C.addTransition(State);
142  } else if (mutexCount > 0) {
143  SymbolRef BlockDesc = Call.getReturnValue().getAsSymbol();
144  reportBlockInCritSection(BlockDesc, Call, C);
145  }
146 }
147 
148 void BlockInCriticalSectionChecker::reportBlockInCritSection(
149  SymbolRef BlockDescSym, const CallEvent &Call, CheckerContext &C) const {
150  ExplodedNode *ErrNode = C.generateNonFatalErrorNode();
151  if (!ErrNode)
152  return;
153 
154  std::string msg;
155  llvm::raw_string_ostream os(msg);
156  os << "Call to blocking function '" << Call.getCalleeIdentifier()->getName()
157  << "' inside of critical section";
158  auto R = std::make_unique<PathSensitiveBugReport>(*BlockInCritSectionBugType,
159  os.str(), ErrNode);
160  R->addRange(Call.getSourceRange());
161  R->markInteresting(BlockDescSym);
162  C.emitReport(std::move(R));
163 }
164 
165 void ento::registerBlockInCriticalSectionChecker(CheckerManager &mgr) {
166  mgr.registerChecker<BlockInCriticalSectionChecker>();
167 }
168 
169 bool ento::shouldRegisterBlockInCriticalSectionChecker(const CheckerManager &mgr) {
170  return true;
171 }
CallDescription.h
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:596
clang::ento::ProgramStateRef
IntrusiveRefCntPtr< const ProgramState > ProgramStateRef
Definition: ProgramState_Fwd.h:37
AttributeLangSupport::C
@ C
Definition: SemaDeclAttr.cpp:55
clang::ento::SymbolRef
const SymExpr * SymbolRef
Definition: SymExpr.h:111
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:34
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:208
false
#define false
Definition: stdbool.h:22
BugType.h
clang::ASTContext::Idents
IdentifierTable & Idents
Definition: ASTContext.h:655
State
LineState State
Definition: UnwrappedLineFormatter.cpp:1126
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