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