/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 | } |