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 CheckerManager &mgr) { 188 return true; 189 } 190