xref: /freebsd/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp (revision 1db9f3b21e39176dd5b67cf8ac378633b172463e)
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 
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 
88 bool BlockInCriticalSectionChecker::isBlockingFunction(const CallEvent &Call) const {
89   return matchesAny(Call, SleepFn, GetcFn, FgetsFn, ReadFn, RecvFn);
90 }
91 
92 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 
103 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 
114 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 
137 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 
154 void ento::registerBlockInCriticalSectionChecker(CheckerManager &mgr) {
155   mgr.registerChecker<BlockInCriticalSectionChecker>();
156 }
157 
158 bool ento::shouldRegisterBlockInCriticalSectionChecker(const CheckerManager &mgr) {
159   return true;
160 }
161