17330f729Sjoerg //===-- BlockInCriticalSectionChecker.cpp -----------------------*- C++ -*-===//
27330f729Sjoerg //
37330f729Sjoerg // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
47330f729Sjoerg // See https://llvm.org/LICENSE.txt for license information.
57330f729Sjoerg // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
67330f729Sjoerg //
77330f729Sjoerg //===----------------------------------------------------------------------===//
87330f729Sjoerg //
97330f729Sjoerg // Defines a checker for blocks in critical sections. This checker should find
107330f729Sjoerg // the calls to blocking functions (for example: sleep, getc, fgets, read,
117330f729Sjoerg // recv etc.) inside a critical section. When sleep(x) is called while a mutex
127330f729Sjoerg // is held, other threades cannot lock the same mutex. This might take some
137330f729Sjoerg // time, leading to bad performance or even deadlock.
147330f729Sjoerg //
157330f729Sjoerg //===----------------------------------------------------------------------===//
167330f729Sjoerg 
177330f729Sjoerg #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
187330f729Sjoerg #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
197330f729Sjoerg #include "clang/StaticAnalyzer/Core/Checker.h"
207330f729Sjoerg #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
217330f729Sjoerg #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
227330f729Sjoerg 
237330f729Sjoerg using namespace clang;
247330f729Sjoerg using namespace ento;
257330f729Sjoerg 
267330f729Sjoerg namespace {
277330f729Sjoerg 
287330f729Sjoerg class BlockInCriticalSectionChecker : public Checker<check::PostCall> {
297330f729Sjoerg 
307330f729Sjoerg   mutable IdentifierInfo *IILockGuard, *IIUniqueLock;
317330f729Sjoerg 
327330f729Sjoerg   CallDescription LockFn, UnlockFn, SleepFn, GetcFn, FgetsFn, ReadFn, RecvFn,
337330f729Sjoerg                   PthreadLockFn, PthreadTryLockFn, PthreadUnlockFn,
347330f729Sjoerg                   MtxLock, MtxTimedLock, MtxTryLock, MtxUnlock;
357330f729Sjoerg 
367330f729Sjoerg   StringRef ClassLockGuard, ClassUniqueLock;
377330f729Sjoerg 
387330f729Sjoerg   mutable bool IdentifierInfoInitialized;
397330f729Sjoerg 
407330f729Sjoerg   std::unique_ptr<BugType> BlockInCritSectionBugType;
417330f729Sjoerg 
427330f729Sjoerg   void initIdentifierInfo(ASTContext &Ctx) const;
437330f729Sjoerg 
447330f729Sjoerg   void reportBlockInCritSection(SymbolRef FileDescSym,
457330f729Sjoerg                                 const CallEvent &call,
467330f729Sjoerg                                 CheckerContext &C) const;
477330f729Sjoerg 
487330f729Sjoerg public:
497330f729Sjoerg   BlockInCriticalSectionChecker();
507330f729Sjoerg 
517330f729Sjoerg   bool isBlockingFunction(const CallEvent &Call) const;
527330f729Sjoerg   bool isLockFunction(const CallEvent &Call) const;
537330f729Sjoerg   bool isUnlockFunction(const CallEvent &Call) const;
547330f729Sjoerg 
557330f729Sjoerg   /// Process unlock.
567330f729Sjoerg   /// Process lock.
577330f729Sjoerg   /// Process blocking functions (sleep, getc, fgets, read, recv)
587330f729Sjoerg   void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
597330f729Sjoerg };
607330f729Sjoerg 
617330f729Sjoerg } // end anonymous namespace
627330f729Sjoerg 
REGISTER_TRAIT_WITH_PROGRAMSTATE(MutexCounter,unsigned)637330f729Sjoerg REGISTER_TRAIT_WITH_PROGRAMSTATE(MutexCounter, unsigned)
647330f729Sjoerg 
657330f729Sjoerg BlockInCriticalSectionChecker::BlockInCriticalSectionChecker()
667330f729Sjoerg     : IILockGuard(nullptr), IIUniqueLock(nullptr),
677330f729Sjoerg       LockFn("lock"), UnlockFn("unlock"), SleepFn("sleep"), GetcFn("getc"),
687330f729Sjoerg       FgetsFn("fgets"), ReadFn("read"), RecvFn("recv"),
697330f729Sjoerg       PthreadLockFn("pthread_mutex_lock"),
707330f729Sjoerg       PthreadTryLockFn("pthread_mutex_trylock"),
717330f729Sjoerg       PthreadUnlockFn("pthread_mutex_unlock"),
727330f729Sjoerg       MtxLock("mtx_lock"),
737330f729Sjoerg       MtxTimedLock("mtx_timedlock"),
747330f729Sjoerg       MtxTryLock("mtx_trylock"),
757330f729Sjoerg       MtxUnlock("mtx_unlock"),
767330f729Sjoerg       ClassLockGuard("lock_guard"),
777330f729Sjoerg       ClassUniqueLock("unique_lock"),
787330f729Sjoerg       IdentifierInfoInitialized(false) {
797330f729Sjoerg   // Initialize the bug type.
807330f729Sjoerg   BlockInCritSectionBugType.reset(
817330f729Sjoerg       new BugType(this, "Call to blocking function in critical section",
827330f729Sjoerg                         "Blocking Error"));
837330f729Sjoerg }
847330f729Sjoerg 
initIdentifierInfo(ASTContext & Ctx) const857330f729Sjoerg void BlockInCriticalSectionChecker::initIdentifierInfo(ASTContext &Ctx) const {
867330f729Sjoerg   if (!IdentifierInfoInitialized) {
877330f729Sjoerg     /* In case of checking C code, or when the corresponding headers are not
887330f729Sjoerg      * included, we might end up query the identifier table every time when this
897330f729Sjoerg      * function is called instead of early returning it. To avoid this, a bool
907330f729Sjoerg      * variable (IdentifierInfoInitialized) is used and the function will be run
917330f729Sjoerg      * only once. */
927330f729Sjoerg     IILockGuard  = &Ctx.Idents.get(ClassLockGuard);
937330f729Sjoerg     IIUniqueLock = &Ctx.Idents.get(ClassUniqueLock);
947330f729Sjoerg     IdentifierInfoInitialized = true;
957330f729Sjoerg   }
967330f729Sjoerg }
977330f729Sjoerg 
isBlockingFunction(const CallEvent & Call) const987330f729Sjoerg bool BlockInCriticalSectionChecker::isBlockingFunction(const CallEvent &Call) const {
997330f729Sjoerg   if (Call.isCalled(SleepFn)
1007330f729Sjoerg       || Call.isCalled(GetcFn)
1017330f729Sjoerg       || Call.isCalled(FgetsFn)
1027330f729Sjoerg       || Call.isCalled(ReadFn)
1037330f729Sjoerg       || Call.isCalled(RecvFn)) {
1047330f729Sjoerg     return true;
1057330f729Sjoerg   }
1067330f729Sjoerg   return false;
1077330f729Sjoerg }
1087330f729Sjoerg 
isLockFunction(const CallEvent & Call) const1097330f729Sjoerg bool BlockInCriticalSectionChecker::isLockFunction(const CallEvent &Call) const {
1107330f729Sjoerg   if (const auto *Ctor = dyn_cast<CXXConstructorCall>(&Call)) {
1117330f729Sjoerg     auto IdentifierInfo = Ctor->getDecl()->getParent()->getIdentifier();
1127330f729Sjoerg     if (IdentifierInfo == IILockGuard || IdentifierInfo == IIUniqueLock)
1137330f729Sjoerg       return true;
1147330f729Sjoerg   }
1157330f729Sjoerg 
1167330f729Sjoerg   if (Call.isCalled(LockFn)
1177330f729Sjoerg       || Call.isCalled(PthreadLockFn)
1187330f729Sjoerg       || Call.isCalled(PthreadTryLockFn)
1197330f729Sjoerg       || Call.isCalled(MtxLock)
1207330f729Sjoerg       || Call.isCalled(MtxTimedLock)
1217330f729Sjoerg       || Call.isCalled(MtxTryLock)) {
1227330f729Sjoerg     return true;
1237330f729Sjoerg   }
1247330f729Sjoerg   return false;
1257330f729Sjoerg }
1267330f729Sjoerg 
isUnlockFunction(const CallEvent & Call) const1277330f729Sjoerg bool BlockInCriticalSectionChecker::isUnlockFunction(const CallEvent &Call) const {
1287330f729Sjoerg   if (const auto *Dtor = dyn_cast<CXXDestructorCall>(&Call)) {
1297330f729Sjoerg     const auto *DRecordDecl = cast<CXXRecordDecl>(Dtor->getDecl()->getParent());
1307330f729Sjoerg     auto IdentifierInfo = DRecordDecl->getIdentifier();
1317330f729Sjoerg     if (IdentifierInfo == IILockGuard || IdentifierInfo == IIUniqueLock)
1327330f729Sjoerg       return true;
1337330f729Sjoerg   }
1347330f729Sjoerg 
1357330f729Sjoerg   if (Call.isCalled(UnlockFn)
1367330f729Sjoerg        || Call.isCalled(PthreadUnlockFn)
1377330f729Sjoerg        || Call.isCalled(MtxUnlock)) {
1387330f729Sjoerg     return true;
1397330f729Sjoerg   }
1407330f729Sjoerg   return false;
1417330f729Sjoerg }
1427330f729Sjoerg 
checkPostCall(const CallEvent & Call,CheckerContext & C) const1437330f729Sjoerg void BlockInCriticalSectionChecker::checkPostCall(const CallEvent &Call,
1447330f729Sjoerg                                                   CheckerContext &C) const {
1457330f729Sjoerg   initIdentifierInfo(C.getASTContext());
1467330f729Sjoerg 
1477330f729Sjoerg   if (!isBlockingFunction(Call)
1487330f729Sjoerg       && !isLockFunction(Call)
1497330f729Sjoerg       && !isUnlockFunction(Call))
1507330f729Sjoerg     return;
1517330f729Sjoerg 
1527330f729Sjoerg   ProgramStateRef State = C.getState();
1537330f729Sjoerg   unsigned mutexCount = State->get<MutexCounter>();
1547330f729Sjoerg   if (isUnlockFunction(Call) && mutexCount > 0) {
1557330f729Sjoerg     State = State->set<MutexCounter>(--mutexCount);
1567330f729Sjoerg     C.addTransition(State);
1577330f729Sjoerg   } else if (isLockFunction(Call)) {
1587330f729Sjoerg     State = State->set<MutexCounter>(++mutexCount);
1597330f729Sjoerg     C.addTransition(State);
1607330f729Sjoerg   } else if (mutexCount > 0) {
1617330f729Sjoerg     SymbolRef BlockDesc = Call.getReturnValue().getAsSymbol();
1627330f729Sjoerg     reportBlockInCritSection(BlockDesc, Call, C);
1637330f729Sjoerg   }
1647330f729Sjoerg }
1657330f729Sjoerg 
reportBlockInCritSection(SymbolRef BlockDescSym,const CallEvent & Call,CheckerContext & C) const1667330f729Sjoerg void BlockInCriticalSectionChecker::reportBlockInCritSection(
1677330f729Sjoerg     SymbolRef BlockDescSym, const CallEvent &Call, CheckerContext &C) const {
1687330f729Sjoerg   ExplodedNode *ErrNode = C.generateNonFatalErrorNode();
1697330f729Sjoerg   if (!ErrNode)
1707330f729Sjoerg     return;
1717330f729Sjoerg 
1727330f729Sjoerg   std::string msg;
1737330f729Sjoerg   llvm::raw_string_ostream os(msg);
1747330f729Sjoerg   os << "Call to blocking function '" << Call.getCalleeIdentifier()->getName()
1757330f729Sjoerg      << "' inside of critical section";
1767330f729Sjoerg   auto R = std::make_unique<PathSensitiveBugReport>(*BlockInCritSectionBugType,
1777330f729Sjoerg                                                     os.str(), ErrNode);
1787330f729Sjoerg   R->addRange(Call.getSourceRange());
1797330f729Sjoerg   R->markInteresting(BlockDescSym);
1807330f729Sjoerg   C.emitReport(std::move(R));
1817330f729Sjoerg }
1827330f729Sjoerg 
registerBlockInCriticalSectionChecker(CheckerManager & mgr)1837330f729Sjoerg void ento::registerBlockInCriticalSectionChecker(CheckerManager &mgr) {
1847330f729Sjoerg   mgr.registerChecker<BlockInCriticalSectionChecker>();
1857330f729Sjoerg }
1867330f729Sjoerg 
shouldRegisterBlockInCriticalSectionChecker(const CheckerManager & mgr)187*e038c9c4Sjoerg bool ento::shouldRegisterBlockInCriticalSectionChecker(const CheckerManager &mgr) {
1887330f729Sjoerg   return true;
1897330f729Sjoerg }
190