xref: /freebsd/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp (revision 5f4c09dd85bff675e0ca63c55ea3c517e0fddfcc)
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     : 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",
79                         "Blocking Error"));
80 }
81 
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
88      * only once. */
89     IILockGuard  = &Ctx.Idents.get(ClassLockGuard);
90     IIUniqueLock = &Ctx.Idents.get(ClassUniqueLock);
91     IdentifierInfoInitialized = true;
92   }
93 }
94 
95 bool BlockInCriticalSectionChecker::isBlockingFunction(const CallEvent &Call) const {
96   return matchesAny(Call, SleepFn, GetcFn, FgetsFn, ReadFn, RecvFn);
97 }
98 
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)
103       return true;
104   }
105 
106   return matchesAny(Call, LockFn, PthreadLockFn, PthreadTryLockFn, MtxLock,
107                     MtxTimedLock, MtxTryLock);
108 }
109 
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)
115       return true;
116   }
117 
118   return matchesAny(Call, UnlockFn, PthreadUnlockFn, MtxUnlock);
119 }
120 
121 void BlockInCriticalSectionChecker::checkPostCall(const CallEvent &Call,
122                                                   CheckerContext &C) const {
123   initIdentifierInfo(C.getASTContext());
124 
125   if (!isBlockingFunction(Call)
126       && !isLockFunction(Call)
127       && !isUnlockFunction(Call))
128     return;
129 
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);
141   }
142 }
143 
144 void BlockInCriticalSectionChecker::reportBlockInCritSection(
145     SymbolRef BlockDescSym, const CallEvent &Call, CheckerContext &C) const {
146   ExplodedNode *ErrNode = C.generateNonFatalErrorNode();
147   if (!ErrNode)
148     return;
149 
150   std::string msg;
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,
155                                                     os.str(), ErrNode);
156   R->addRange(Call.getSourceRange());
157   R->markInteresting(BlockDescSym);
158   C.emitReport(std::move(R));
159 }
160 
161 void ento::registerBlockInCriticalSectionChecker(CheckerManager &mgr) {
162   mgr.registerChecker<BlockInCriticalSectionChecker>();
163 }
164 
165 bool ento::shouldRegisterBlockInCriticalSectionChecker(const CheckerManager &mgr) {
166   return true;
167 }
168