10b57cec5SDimitry Andric //===-- BlockInCriticalSectionChecker.cpp -----------------------*- C++ -*-===// 20b57cec5SDimitry Andric // 30b57cec5SDimitry Andric // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 40b57cec5SDimitry Andric // See https://llvm.org/LICENSE.txt for license information. 50b57cec5SDimitry Andric // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 60b57cec5SDimitry Andric // 70b57cec5SDimitry Andric //===----------------------------------------------------------------------===// 80b57cec5SDimitry Andric // 90b57cec5SDimitry Andric // Defines a checker for blocks in critical sections. This checker should find 100b57cec5SDimitry Andric // the calls to blocking functions (for example: sleep, getc, fgets, read, 110b57cec5SDimitry Andric // recv etc.) inside a critical section. When sleep(x) is called while a mutex 120b57cec5SDimitry Andric // is held, other threades cannot lock the same mutex. This might take some 130b57cec5SDimitry Andric // time, leading to bad performance or even deadlock. 140b57cec5SDimitry Andric // 150b57cec5SDimitry Andric //===----------------------------------------------------------------------===// 160b57cec5SDimitry Andric 170b57cec5SDimitry Andric #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" 180b57cec5SDimitry Andric #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" 190b57cec5SDimitry Andric #include "clang/StaticAnalyzer/Core/Checker.h" 20349cc55cSDimitry Andric #include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h" 210b57cec5SDimitry Andric #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" 220b57cec5SDimitry Andric #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" 230b57cec5SDimitry Andric 240b57cec5SDimitry Andric using namespace clang; 250b57cec5SDimitry Andric using namespace ento; 260b57cec5SDimitry Andric 270b57cec5SDimitry Andric namespace { 280b57cec5SDimitry Andric class BlockInCriticalSectionChecker : public Checker<check::PostCall> { 29*647cbc5dSDimitry Andric mutable IdentifierInfo *IILockGuard = nullptr; 30*647cbc5dSDimitry Andric mutable IdentifierInfo *IIUniqueLock = nullptr; 31*647cbc5dSDimitry Andric mutable bool IdentifierInfoInitialized = false; 320b57cec5SDimitry Andric 33*647cbc5dSDimitry Andric const CallDescription LockFn{{"lock"}}; 34*647cbc5dSDimitry Andric const CallDescription UnlockFn{{"unlock"}}; 35*647cbc5dSDimitry Andric const CallDescription SleepFn{{"sleep"}}; 36*647cbc5dSDimitry Andric const CallDescription GetcFn{{"getc"}}; 37*647cbc5dSDimitry Andric const CallDescription FgetsFn{{"fgets"}}; 38*647cbc5dSDimitry Andric const CallDescription ReadFn{{"read"}}; 39*647cbc5dSDimitry Andric const CallDescription RecvFn{{"recv"}}; 40*647cbc5dSDimitry Andric const CallDescription PthreadLockFn{{"pthread_mutex_lock"}}; 41*647cbc5dSDimitry Andric const CallDescription PthreadTryLockFn{{"pthread_mutex_trylock"}}; 42*647cbc5dSDimitry Andric const CallDescription PthreadUnlockFn{{"pthread_mutex_unlock"}}; 43*647cbc5dSDimitry Andric const CallDescription MtxLock{{"mtx_lock"}}; 44*647cbc5dSDimitry Andric const CallDescription MtxTimedLock{{"mtx_timedlock"}}; 45*647cbc5dSDimitry Andric const CallDescription MtxTryLock{{"mtx_trylock"}}; 46*647cbc5dSDimitry Andric const CallDescription MtxUnlock{{"mtx_unlock"}}; 470b57cec5SDimitry Andric 48*647cbc5dSDimitry Andric const llvm::StringLiteral ClassLockGuard{"lock_guard"}; 49*647cbc5dSDimitry Andric const llvm::StringLiteral ClassUniqueLock{"unique_lock"}; 500b57cec5SDimitry Andric 51*647cbc5dSDimitry Andric const BugType BlockInCritSectionBugType{ 52*647cbc5dSDimitry Andric this, "Call to blocking function in critical section", "Blocking Error"}; 530b57cec5SDimitry Andric 540b57cec5SDimitry Andric void initIdentifierInfo(ASTContext &Ctx) const; 550b57cec5SDimitry Andric 560b57cec5SDimitry Andric void reportBlockInCritSection(SymbolRef FileDescSym, 570b57cec5SDimitry Andric const CallEvent &call, 580b57cec5SDimitry Andric CheckerContext &C) const; 590b57cec5SDimitry Andric 600b57cec5SDimitry Andric public: 610b57cec5SDimitry Andric bool isBlockingFunction(const CallEvent &Call) const; 620b57cec5SDimitry Andric bool isLockFunction(const CallEvent &Call) const; 630b57cec5SDimitry Andric bool isUnlockFunction(const CallEvent &Call) const; 640b57cec5SDimitry Andric 650b57cec5SDimitry Andric /// Process unlock. 660b57cec5SDimitry Andric /// Process lock. 670b57cec5SDimitry Andric /// Process blocking functions (sleep, getc, fgets, read, recv) 680b57cec5SDimitry Andric void checkPostCall(const CallEvent &Call, CheckerContext &C) const; 690b57cec5SDimitry Andric }; 700b57cec5SDimitry Andric 710b57cec5SDimitry Andric } // end anonymous namespace 720b57cec5SDimitry Andric 730b57cec5SDimitry Andric REGISTER_TRAIT_WITH_PROGRAMSTATE(MutexCounter, unsigned) 740b57cec5SDimitry Andric 750b57cec5SDimitry Andric void BlockInCriticalSectionChecker::initIdentifierInfo(ASTContext &Ctx) const { 760b57cec5SDimitry Andric if (!IdentifierInfoInitialized) { 770b57cec5SDimitry Andric /* In case of checking C code, or when the corresponding headers are not 780b57cec5SDimitry Andric * included, we might end up query the identifier table every time when this 790b57cec5SDimitry Andric * function is called instead of early returning it. To avoid this, a bool 800b57cec5SDimitry Andric * variable (IdentifierInfoInitialized) is used and the function will be run 810b57cec5SDimitry Andric * only once. */ 820b57cec5SDimitry Andric IILockGuard = &Ctx.Idents.get(ClassLockGuard); 830b57cec5SDimitry Andric IIUniqueLock = &Ctx.Idents.get(ClassUniqueLock); 840b57cec5SDimitry Andric IdentifierInfoInitialized = true; 850b57cec5SDimitry Andric } 860b57cec5SDimitry Andric } 870b57cec5SDimitry Andric 880b57cec5SDimitry Andric bool BlockInCriticalSectionChecker::isBlockingFunction(const CallEvent &Call) const { 89349cc55cSDimitry Andric return matchesAny(Call, SleepFn, GetcFn, FgetsFn, ReadFn, RecvFn); 900b57cec5SDimitry Andric } 910b57cec5SDimitry Andric 920b57cec5SDimitry Andric bool BlockInCriticalSectionChecker::isLockFunction(const CallEvent &Call) const { 930b57cec5SDimitry Andric if (const auto *Ctor = dyn_cast<CXXConstructorCall>(&Call)) { 940b57cec5SDimitry Andric auto IdentifierInfo = Ctor->getDecl()->getParent()->getIdentifier(); 950b57cec5SDimitry Andric if (IdentifierInfo == IILockGuard || IdentifierInfo == IIUniqueLock) 960b57cec5SDimitry Andric return true; 970b57cec5SDimitry Andric } 980b57cec5SDimitry Andric 99349cc55cSDimitry Andric return matchesAny(Call, LockFn, PthreadLockFn, PthreadTryLockFn, MtxLock, 100349cc55cSDimitry Andric MtxTimedLock, MtxTryLock); 1010b57cec5SDimitry Andric } 1020b57cec5SDimitry Andric 1030b57cec5SDimitry Andric bool BlockInCriticalSectionChecker::isUnlockFunction(const CallEvent &Call) const { 1040b57cec5SDimitry Andric if (const auto *Dtor = dyn_cast<CXXDestructorCall>(&Call)) { 105a7dea167SDimitry Andric const auto *DRecordDecl = cast<CXXRecordDecl>(Dtor->getDecl()->getParent()); 1060b57cec5SDimitry Andric auto IdentifierInfo = DRecordDecl->getIdentifier(); 1070b57cec5SDimitry Andric if (IdentifierInfo == IILockGuard || IdentifierInfo == IIUniqueLock) 1080b57cec5SDimitry Andric return true; 1090b57cec5SDimitry Andric } 1100b57cec5SDimitry Andric 111349cc55cSDimitry Andric return matchesAny(Call, UnlockFn, PthreadUnlockFn, MtxUnlock); 1120b57cec5SDimitry Andric } 1130b57cec5SDimitry Andric 1140b57cec5SDimitry Andric void BlockInCriticalSectionChecker::checkPostCall(const CallEvent &Call, 1150b57cec5SDimitry Andric CheckerContext &C) const { 1160b57cec5SDimitry Andric initIdentifierInfo(C.getASTContext()); 1170b57cec5SDimitry Andric 1180b57cec5SDimitry Andric if (!isBlockingFunction(Call) 1190b57cec5SDimitry Andric && !isLockFunction(Call) 1200b57cec5SDimitry Andric && !isUnlockFunction(Call)) 1210b57cec5SDimitry Andric return; 1220b57cec5SDimitry Andric 1230b57cec5SDimitry Andric ProgramStateRef State = C.getState(); 1240b57cec5SDimitry Andric unsigned mutexCount = State->get<MutexCounter>(); 1250b57cec5SDimitry Andric if (isUnlockFunction(Call) && mutexCount > 0) { 1260b57cec5SDimitry Andric State = State->set<MutexCounter>(--mutexCount); 1270b57cec5SDimitry Andric C.addTransition(State); 1280b57cec5SDimitry Andric } else if (isLockFunction(Call)) { 1290b57cec5SDimitry Andric State = State->set<MutexCounter>(++mutexCount); 1300b57cec5SDimitry Andric C.addTransition(State); 1310b57cec5SDimitry Andric } else if (mutexCount > 0) { 1320b57cec5SDimitry Andric SymbolRef BlockDesc = Call.getReturnValue().getAsSymbol(); 1330b57cec5SDimitry Andric reportBlockInCritSection(BlockDesc, Call, C); 1340b57cec5SDimitry Andric } 1350b57cec5SDimitry Andric } 1360b57cec5SDimitry Andric 1370b57cec5SDimitry Andric void BlockInCriticalSectionChecker::reportBlockInCritSection( 1380b57cec5SDimitry Andric SymbolRef BlockDescSym, const CallEvent &Call, CheckerContext &C) const { 1390b57cec5SDimitry Andric ExplodedNode *ErrNode = C.generateNonFatalErrorNode(); 1400b57cec5SDimitry Andric if (!ErrNode) 1410b57cec5SDimitry Andric return; 1420b57cec5SDimitry Andric 1430b57cec5SDimitry Andric std::string msg; 1440b57cec5SDimitry Andric llvm::raw_string_ostream os(msg); 1450b57cec5SDimitry Andric os << "Call to blocking function '" << Call.getCalleeIdentifier()->getName() 1460b57cec5SDimitry Andric << "' inside of critical section"; 147*647cbc5dSDimitry Andric auto R = std::make_unique<PathSensitiveBugReport>(BlockInCritSectionBugType, 148a7dea167SDimitry Andric os.str(), ErrNode); 1490b57cec5SDimitry Andric R->addRange(Call.getSourceRange()); 1500b57cec5SDimitry Andric R->markInteresting(BlockDescSym); 1510b57cec5SDimitry Andric C.emitReport(std::move(R)); 1520b57cec5SDimitry Andric } 1530b57cec5SDimitry Andric 1540b57cec5SDimitry Andric void ento::registerBlockInCriticalSectionChecker(CheckerManager &mgr) { 1550b57cec5SDimitry Andric mgr.registerChecker<BlockInCriticalSectionChecker>(); 1560b57cec5SDimitry Andric } 1570b57cec5SDimitry Andric 1585ffd83dbSDimitry Andric bool ento::shouldRegisterBlockInCriticalSectionChecker(const CheckerManager &mgr) { 1590b57cec5SDimitry Andric return true; 1600b57cec5SDimitry Andric } 161