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