xref: /freebsd/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp (revision a7dea1671b87c07d2d266f836bfa8b58efc7c134)
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/CallEvent.h"
21 #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
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 LangOptions &LO) {
188   return true;
189 }
190