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