1 //===-- BlockInCriticalSectionChecker.cpp -----------------------*- C++ -*-===//
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
7 //===----------------------------------------------------------------------===//
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.
15 //===----------------------------------------------------------------------===//
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"
24 using namespace clang
;
29 class BlockInCriticalSectionChecker
: public Checker
<check::PostCall
> {
31 mutable IdentifierInfo
*IILockGuard
, *IIUniqueLock
;
33 CallDescription LockFn
, UnlockFn
, SleepFn
, GetcFn
, FgetsFn
, ReadFn
, RecvFn
,
34 PthreadLockFn
, PthreadTryLockFn
, PthreadUnlockFn
,
35 MtxLock
, MtxTimedLock
, MtxTryLock
, MtxUnlock
;
37 StringRef ClassLockGuard
, ClassUniqueLock
;
39 mutable bool IdentifierInfoInitialized
;
41 std::unique_ptr
<BugType
> BlockInCritSectionBugType
;
43 void initIdentifierInfo(ASTContext
&Ctx
) const;
45 void reportBlockInCritSection(SymbolRef FileDescSym
,
46 const CallEvent
&call
,
47 CheckerContext
&C
) const;
50 BlockInCriticalSectionChecker();
52 bool isBlockingFunction(const CallEvent
&Call
) const;
53 bool isLockFunction(const CallEvent
&Call
) const;
54 bool isUnlockFunction(const CallEvent
&Call
) const;
58 /// Process blocking functions (sleep, getc, fgets, read, recv)
59 void checkPostCall(const CallEvent
&Call
, CheckerContext
&C
) const;
62 } // end anonymous namespace
64 REGISTER_TRAIT_WITH_PROGRAMSTATE(MutexCounter
, unsigned)
66 BlockInCriticalSectionChecker::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",
82 void 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
89 IILockGuard
= &Ctx
.Idents
.get(ClassLockGuard
);
90 IIUniqueLock
= &Ctx
.Idents
.get(ClassUniqueLock
);
91 IdentifierInfoInitialized
= true;
95 bool BlockInCriticalSectionChecker::isBlockingFunction(const CallEvent
&Call
) const {
96 return matchesAny(Call
, SleepFn
, GetcFn
, FgetsFn
, ReadFn
, RecvFn
);
99 bool 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
)
106 return matchesAny(Call
, LockFn
, PthreadLockFn
, PthreadTryLockFn
, MtxLock
,
107 MtxTimedLock
, MtxTryLock
);
110 bool 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
)
118 return matchesAny(Call
, UnlockFn
, PthreadUnlockFn
, MtxUnlock
);
121 void BlockInCriticalSectionChecker::checkPostCall(const CallEvent
&Call
,
122 CheckerContext
&C
) const {
123 initIdentifierInfo(C
.getASTContext());
125 if (!isBlockingFunction(Call
)
126 && !isLockFunction(Call
)
127 && !isUnlockFunction(Call
))
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
);
144 void BlockInCriticalSectionChecker::reportBlockInCritSection(
145 SymbolRef BlockDescSym
, const CallEvent
&Call
, CheckerContext
&C
) const {
146 ExplodedNode
*ErrNode
= C
.generateNonFatalErrorNode();
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
,
156 R
->addRange(Call
.getSourceRange());
157 R
->markInteresting(BlockDescSym
);
158 C
.emitReport(std::move(R
));
161 void ento::registerBlockInCriticalSectionChecker(CheckerManager
&mgr
) {
162 mgr
.registerChecker
<BlockInCriticalSectionChecker
>();
165 bool ento::shouldRegisterBlockInCriticalSectionChecker(const CheckerManager
&mgr
) {