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" 20*349cc55cSDimitry 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 290b57cec5SDimitry Andric class BlockInCriticalSectionChecker : public Checker<check::PostCall> { 300b57cec5SDimitry Andric 310b57cec5SDimitry Andric mutable IdentifierInfo *IILockGuard, *IIUniqueLock; 320b57cec5SDimitry Andric 330b57cec5SDimitry Andric CallDescription LockFn, UnlockFn, SleepFn, GetcFn, FgetsFn, ReadFn, RecvFn, 340b57cec5SDimitry Andric PthreadLockFn, PthreadTryLockFn, PthreadUnlockFn, 350b57cec5SDimitry Andric MtxLock, MtxTimedLock, MtxTryLock, MtxUnlock; 360b57cec5SDimitry Andric 370b57cec5SDimitry Andric StringRef ClassLockGuard, ClassUniqueLock; 380b57cec5SDimitry Andric 390b57cec5SDimitry Andric mutable bool IdentifierInfoInitialized; 400b57cec5SDimitry Andric 410b57cec5SDimitry Andric std::unique_ptr<BugType> BlockInCritSectionBugType; 420b57cec5SDimitry Andric 430b57cec5SDimitry Andric void initIdentifierInfo(ASTContext &Ctx) const; 440b57cec5SDimitry Andric 450b57cec5SDimitry Andric void reportBlockInCritSection(SymbolRef FileDescSym, 460b57cec5SDimitry Andric const CallEvent &call, 470b57cec5SDimitry Andric CheckerContext &C) const; 480b57cec5SDimitry Andric 490b57cec5SDimitry Andric public: 500b57cec5SDimitry Andric BlockInCriticalSectionChecker(); 510b57cec5SDimitry Andric 520b57cec5SDimitry Andric bool isBlockingFunction(const CallEvent &Call) const; 530b57cec5SDimitry Andric bool isLockFunction(const CallEvent &Call) const; 540b57cec5SDimitry Andric bool isUnlockFunction(const CallEvent &Call) const; 550b57cec5SDimitry Andric 560b57cec5SDimitry Andric /// Process unlock. 570b57cec5SDimitry Andric /// Process lock. 580b57cec5SDimitry Andric /// Process blocking functions (sleep, getc, fgets, read, recv) 590b57cec5SDimitry Andric void checkPostCall(const CallEvent &Call, CheckerContext &C) const; 600b57cec5SDimitry Andric }; 610b57cec5SDimitry Andric 620b57cec5SDimitry Andric } // end anonymous namespace 630b57cec5SDimitry Andric 640b57cec5SDimitry Andric REGISTER_TRAIT_WITH_PROGRAMSTATE(MutexCounter, unsigned) 650b57cec5SDimitry Andric 660b57cec5SDimitry Andric BlockInCriticalSectionChecker::BlockInCriticalSectionChecker() 670b57cec5SDimitry Andric : IILockGuard(nullptr), IIUniqueLock(nullptr), 680b57cec5SDimitry Andric LockFn("lock"), UnlockFn("unlock"), SleepFn("sleep"), GetcFn("getc"), 690b57cec5SDimitry Andric FgetsFn("fgets"), ReadFn("read"), RecvFn("recv"), 700b57cec5SDimitry Andric PthreadLockFn("pthread_mutex_lock"), 710b57cec5SDimitry Andric PthreadTryLockFn("pthread_mutex_trylock"), 720b57cec5SDimitry Andric PthreadUnlockFn("pthread_mutex_unlock"), 730b57cec5SDimitry Andric MtxLock("mtx_lock"), 740b57cec5SDimitry Andric MtxTimedLock("mtx_timedlock"), 750b57cec5SDimitry Andric MtxTryLock("mtx_trylock"), 760b57cec5SDimitry Andric MtxUnlock("mtx_unlock"), 770b57cec5SDimitry Andric ClassLockGuard("lock_guard"), 780b57cec5SDimitry Andric ClassUniqueLock("unique_lock"), 790b57cec5SDimitry Andric IdentifierInfoInitialized(false) { 800b57cec5SDimitry Andric // Initialize the bug type. 810b57cec5SDimitry Andric BlockInCritSectionBugType.reset( 820b57cec5SDimitry Andric new BugType(this, "Call to blocking function in critical section", 830b57cec5SDimitry Andric "Blocking Error")); 840b57cec5SDimitry Andric } 850b57cec5SDimitry Andric 860b57cec5SDimitry Andric void BlockInCriticalSectionChecker::initIdentifierInfo(ASTContext &Ctx) const { 870b57cec5SDimitry Andric if (!IdentifierInfoInitialized) { 880b57cec5SDimitry Andric /* In case of checking C code, or when the corresponding headers are not 890b57cec5SDimitry Andric * included, we might end up query the identifier table every time when this 900b57cec5SDimitry Andric * function is called instead of early returning it. To avoid this, a bool 910b57cec5SDimitry Andric * variable (IdentifierInfoInitialized) is used and the function will be run 920b57cec5SDimitry Andric * only once. */ 930b57cec5SDimitry Andric IILockGuard = &Ctx.Idents.get(ClassLockGuard); 940b57cec5SDimitry Andric IIUniqueLock = &Ctx.Idents.get(ClassUniqueLock); 950b57cec5SDimitry Andric IdentifierInfoInitialized = true; 960b57cec5SDimitry Andric } 970b57cec5SDimitry Andric } 980b57cec5SDimitry Andric 990b57cec5SDimitry Andric bool BlockInCriticalSectionChecker::isBlockingFunction(const CallEvent &Call) const { 100*349cc55cSDimitry Andric return matchesAny(Call, SleepFn, GetcFn, FgetsFn, ReadFn, RecvFn); 1010b57cec5SDimitry Andric } 1020b57cec5SDimitry Andric 1030b57cec5SDimitry Andric bool BlockInCriticalSectionChecker::isLockFunction(const CallEvent &Call) const { 1040b57cec5SDimitry Andric if (const auto *Ctor = dyn_cast<CXXConstructorCall>(&Call)) { 1050b57cec5SDimitry Andric auto IdentifierInfo = Ctor->getDecl()->getParent()->getIdentifier(); 1060b57cec5SDimitry Andric if (IdentifierInfo == IILockGuard || IdentifierInfo == IIUniqueLock) 1070b57cec5SDimitry Andric return true; 1080b57cec5SDimitry Andric } 1090b57cec5SDimitry Andric 110*349cc55cSDimitry Andric return matchesAny(Call, LockFn, PthreadLockFn, PthreadTryLockFn, MtxLock, 111*349cc55cSDimitry Andric MtxTimedLock, MtxTryLock); 1120b57cec5SDimitry Andric } 1130b57cec5SDimitry Andric 1140b57cec5SDimitry Andric bool BlockInCriticalSectionChecker::isUnlockFunction(const CallEvent &Call) const { 1150b57cec5SDimitry Andric if (const auto *Dtor = dyn_cast<CXXDestructorCall>(&Call)) { 116a7dea167SDimitry Andric const auto *DRecordDecl = cast<CXXRecordDecl>(Dtor->getDecl()->getParent()); 1170b57cec5SDimitry Andric auto IdentifierInfo = DRecordDecl->getIdentifier(); 1180b57cec5SDimitry Andric if (IdentifierInfo == IILockGuard || IdentifierInfo == IIUniqueLock) 1190b57cec5SDimitry Andric return true; 1200b57cec5SDimitry Andric } 1210b57cec5SDimitry Andric 122*349cc55cSDimitry Andric return matchesAny(Call, UnlockFn, PthreadUnlockFn, MtxUnlock); 1230b57cec5SDimitry Andric } 1240b57cec5SDimitry Andric 1250b57cec5SDimitry Andric void BlockInCriticalSectionChecker::checkPostCall(const CallEvent &Call, 1260b57cec5SDimitry Andric CheckerContext &C) const { 1270b57cec5SDimitry Andric initIdentifierInfo(C.getASTContext()); 1280b57cec5SDimitry Andric 1290b57cec5SDimitry Andric if (!isBlockingFunction(Call) 1300b57cec5SDimitry Andric && !isLockFunction(Call) 1310b57cec5SDimitry Andric && !isUnlockFunction(Call)) 1320b57cec5SDimitry Andric return; 1330b57cec5SDimitry Andric 1340b57cec5SDimitry Andric ProgramStateRef State = C.getState(); 1350b57cec5SDimitry Andric unsigned mutexCount = State->get<MutexCounter>(); 1360b57cec5SDimitry Andric if (isUnlockFunction(Call) && mutexCount > 0) { 1370b57cec5SDimitry Andric State = State->set<MutexCounter>(--mutexCount); 1380b57cec5SDimitry Andric C.addTransition(State); 1390b57cec5SDimitry Andric } else if (isLockFunction(Call)) { 1400b57cec5SDimitry Andric State = State->set<MutexCounter>(++mutexCount); 1410b57cec5SDimitry Andric C.addTransition(State); 1420b57cec5SDimitry Andric } else if (mutexCount > 0) { 1430b57cec5SDimitry Andric SymbolRef BlockDesc = Call.getReturnValue().getAsSymbol(); 1440b57cec5SDimitry Andric reportBlockInCritSection(BlockDesc, Call, C); 1450b57cec5SDimitry Andric } 1460b57cec5SDimitry Andric } 1470b57cec5SDimitry Andric 1480b57cec5SDimitry Andric void BlockInCriticalSectionChecker::reportBlockInCritSection( 1490b57cec5SDimitry Andric SymbolRef BlockDescSym, const CallEvent &Call, CheckerContext &C) const { 1500b57cec5SDimitry Andric ExplodedNode *ErrNode = C.generateNonFatalErrorNode(); 1510b57cec5SDimitry Andric if (!ErrNode) 1520b57cec5SDimitry Andric return; 1530b57cec5SDimitry Andric 1540b57cec5SDimitry Andric std::string msg; 1550b57cec5SDimitry Andric llvm::raw_string_ostream os(msg); 1560b57cec5SDimitry Andric os << "Call to blocking function '" << Call.getCalleeIdentifier()->getName() 1570b57cec5SDimitry Andric << "' inside of critical section"; 158a7dea167SDimitry Andric auto R = std::make_unique<PathSensitiveBugReport>(*BlockInCritSectionBugType, 159a7dea167SDimitry Andric os.str(), ErrNode); 1600b57cec5SDimitry Andric R->addRange(Call.getSourceRange()); 1610b57cec5SDimitry Andric R->markInteresting(BlockDescSym); 1620b57cec5SDimitry Andric C.emitReport(std::move(R)); 1630b57cec5SDimitry Andric } 1640b57cec5SDimitry Andric 1650b57cec5SDimitry Andric void ento::registerBlockInCriticalSectionChecker(CheckerManager &mgr) { 1660b57cec5SDimitry Andric mgr.registerChecker<BlockInCriticalSectionChecker>(); 1670b57cec5SDimitry Andric } 1680b57cec5SDimitry Andric 1695ffd83dbSDimitry Andric bool ento::shouldRegisterBlockInCriticalSectionChecker(const CheckerManager &mgr) { 1700b57cec5SDimitry Andric return true; 1710b57cec5SDimitry Andric } 172