1c154f7bcSAnna Zaks //===-- BlockInCriticalSectionChecker.cpp -----------------------*- C++ -*-===//
2c154f7bcSAnna Zaks //
32946cd70SChandler Carruth // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
42946cd70SChandler Carruth // See https://llvm.org/LICENSE.txt for license information.
52946cd70SChandler Carruth // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6c154f7bcSAnna Zaks //
7c154f7bcSAnna Zaks //===----------------------------------------------------------------------===//
8c154f7bcSAnna Zaks //
9c154f7bcSAnna Zaks // Defines a checker for blocks in critical sections. This checker should find
10c154f7bcSAnna Zaks // the calls to blocking functions (for example: sleep, getc, fgets, read,
11c154f7bcSAnna Zaks // recv etc.) inside a critical section. When sleep(x) is called while a mutex
12c154f7bcSAnna Zaks // is held, other threades cannot lock the same mutex. This might take some
13c154f7bcSAnna Zaks // time, leading to bad performance or even deadlock.
14c154f7bcSAnna Zaks //
15c154f7bcSAnna Zaks //===----------------------------------------------------------------------===//
16c154f7bcSAnna Zaks 
1776a21502SKristof Umann #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
18c154f7bcSAnna Zaks #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
19c154f7bcSAnna Zaks #include "clang/StaticAnalyzer/Core/Checker.h"
200b9d3a6eSBalazs Benics #include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h"
21c154f7bcSAnna Zaks #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
22c154f7bcSAnna Zaks #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
23c154f7bcSAnna Zaks 
24c154f7bcSAnna Zaks using namespace clang;
25c154f7bcSAnna Zaks using namespace ento;
26c154f7bcSAnna Zaks 
27c154f7bcSAnna Zaks namespace {
28c154f7bcSAnna Zaks 
299a8c8bf6SGabor Horvath class BlockInCriticalSectionChecker : public Checker<check::PostCall> {
309a8c8bf6SGabor Horvath 
319a8c8bf6SGabor Horvath   mutable IdentifierInfo *IILockGuard, *IIUniqueLock;
32c154f7bcSAnna Zaks 
33829c6bc0SGabor Horvath   CallDescription LockFn, UnlockFn, SleepFn, GetcFn, FgetsFn, ReadFn, RecvFn,
34829c6bc0SGabor Horvath                   PthreadLockFn, PthreadTryLockFn, PthreadUnlockFn,
35829c6bc0SGabor Horvath                   MtxLock, MtxTimedLock, MtxTryLock, MtxUnlock;
36c154f7bcSAnna Zaks 
379a8c8bf6SGabor Horvath   StringRef ClassLockGuard, ClassUniqueLock;
389a8c8bf6SGabor Horvath 
399a8c8bf6SGabor Horvath   mutable bool IdentifierInfoInitialized;
409a8c8bf6SGabor Horvath 
41c154f7bcSAnna Zaks   std::unique_ptr<BugType> BlockInCritSectionBugType;
42c154f7bcSAnna Zaks 
439a8c8bf6SGabor Horvath   void initIdentifierInfo(ASTContext &Ctx) const;
449a8c8bf6SGabor Horvath 
45c154f7bcSAnna Zaks   void reportBlockInCritSection(SymbolRef FileDescSym,
46c154f7bcSAnna Zaks                                 const CallEvent &call,
47c154f7bcSAnna Zaks                                 CheckerContext &C) const;
48c154f7bcSAnna Zaks 
49c154f7bcSAnna Zaks public:
50c154f7bcSAnna Zaks   BlockInCriticalSectionChecker();
51c154f7bcSAnna Zaks 
52829c6bc0SGabor Horvath   bool isBlockingFunction(const CallEvent &Call) const;
53829c6bc0SGabor Horvath   bool isLockFunction(const CallEvent &Call) const;
54829c6bc0SGabor Horvath   bool isUnlockFunction(const CallEvent &Call) const;
55829c6bc0SGabor Horvath 
56c154f7bcSAnna Zaks   /// Process unlock.
57c154f7bcSAnna Zaks   /// Process lock.
58c154f7bcSAnna Zaks   /// Process blocking functions (sleep, getc, fgets, read, recv)
59c154f7bcSAnna Zaks   void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
60c154f7bcSAnna Zaks };
61c154f7bcSAnna Zaks 
62c154f7bcSAnna Zaks } // end anonymous namespace
63c154f7bcSAnna Zaks 
REGISTER_TRAIT_WITH_PROGRAMSTATE(MutexCounter,unsigned)64c154f7bcSAnna Zaks REGISTER_TRAIT_WITH_PROGRAMSTATE(MutexCounter, unsigned)
65c154f7bcSAnna Zaks 
66c154f7bcSAnna Zaks BlockInCriticalSectionChecker::BlockInCriticalSectionChecker()
679a8c8bf6SGabor Horvath     : IILockGuard(nullptr), IIUniqueLock(nullptr),
689a8c8bf6SGabor Horvath       LockFn("lock"), UnlockFn("unlock"), SleepFn("sleep"), GetcFn("getc"),
69829c6bc0SGabor Horvath       FgetsFn("fgets"), ReadFn("read"), RecvFn("recv"),
70829c6bc0SGabor Horvath       PthreadLockFn("pthread_mutex_lock"),
71829c6bc0SGabor Horvath       PthreadTryLockFn("pthread_mutex_trylock"),
72829c6bc0SGabor Horvath       PthreadUnlockFn("pthread_mutex_unlock"),
73829c6bc0SGabor Horvath       MtxLock("mtx_lock"),
74829c6bc0SGabor Horvath       MtxTimedLock("mtx_timedlock"),
75829c6bc0SGabor Horvath       MtxTryLock("mtx_trylock"),
769a8c8bf6SGabor Horvath       MtxUnlock("mtx_unlock"),
779a8c8bf6SGabor Horvath       ClassLockGuard("lock_guard"),
789a8c8bf6SGabor Horvath       ClassUniqueLock("unique_lock"),
799a8c8bf6SGabor Horvath       IdentifierInfoInitialized(false) {
80c154f7bcSAnna Zaks   // Initialize the bug type.
81c154f7bcSAnna Zaks   BlockInCritSectionBugType.reset(
82c154f7bcSAnna Zaks       new BugType(this, "Call to blocking function in critical section",
83c154f7bcSAnna Zaks                         "Blocking Error"));
84c154f7bcSAnna Zaks }
85c154f7bcSAnna Zaks 
initIdentifierInfo(ASTContext & Ctx) const869a8c8bf6SGabor Horvath void BlockInCriticalSectionChecker::initIdentifierInfo(ASTContext &Ctx) const {
879a8c8bf6SGabor Horvath   if (!IdentifierInfoInitialized) {
889a8c8bf6SGabor Horvath     /* In case of checking C code, or when the corresponding headers are not
899a8c8bf6SGabor Horvath      * included, we might end up query the identifier table every time when this
909a8c8bf6SGabor Horvath      * function is called instead of early returning it. To avoid this, a bool
919a8c8bf6SGabor Horvath      * variable (IdentifierInfoInitialized) is used and the function will be run
929a8c8bf6SGabor Horvath      * only once. */
939a8c8bf6SGabor Horvath     IILockGuard  = &Ctx.Idents.get(ClassLockGuard);
949a8c8bf6SGabor Horvath     IIUniqueLock = &Ctx.Idents.get(ClassUniqueLock);
959a8c8bf6SGabor Horvath     IdentifierInfoInitialized = true;
969a8c8bf6SGabor Horvath   }
979a8c8bf6SGabor Horvath }
989a8c8bf6SGabor Horvath 
isBlockingFunction(const CallEvent & Call) const99829c6bc0SGabor Horvath bool BlockInCriticalSectionChecker::isBlockingFunction(const CallEvent &Call) const {
100*f18da190SBalazs Benics   return matchesAny(Call, SleepFn, GetcFn, FgetsFn, ReadFn, RecvFn);
101829c6bc0SGabor Horvath }
102829c6bc0SGabor Horvath 
isLockFunction(const CallEvent & Call) const103829c6bc0SGabor Horvath bool BlockInCriticalSectionChecker::isLockFunction(const CallEvent &Call) const {
1049a8c8bf6SGabor Horvath   if (const auto *Ctor = dyn_cast<CXXConstructorCall>(&Call)) {
1059a8c8bf6SGabor Horvath     auto IdentifierInfo = Ctor->getDecl()->getParent()->getIdentifier();
1069a8c8bf6SGabor Horvath     if (IdentifierInfo == IILockGuard || IdentifierInfo == IIUniqueLock)
1079a8c8bf6SGabor Horvath       return true;
1089a8c8bf6SGabor Horvath   }
1099a8c8bf6SGabor Horvath 
110*f18da190SBalazs Benics   return matchesAny(Call, LockFn, PthreadLockFn, PthreadTryLockFn, MtxLock,
111*f18da190SBalazs Benics                     MtxTimedLock, MtxTryLock);
112829c6bc0SGabor Horvath }
113829c6bc0SGabor Horvath 
isUnlockFunction(const CallEvent & Call) const114829c6bc0SGabor Horvath bool BlockInCriticalSectionChecker::isUnlockFunction(const CallEvent &Call) const {
1159a8c8bf6SGabor Horvath   if (const auto *Dtor = dyn_cast<CXXDestructorCall>(&Call)) {
1162c775709SSimon Pilgrim     const auto *DRecordDecl = cast<CXXRecordDecl>(Dtor->getDecl()->getParent());
1179a8c8bf6SGabor Horvath     auto IdentifierInfo = DRecordDecl->getIdentifier();
1189a8c8bf6SGabor Horvath     if (IdentifierInfo == IILockGuard || IdentifierInfo == IIUniqueLock)
1199a8c8bf6SGabor Horvath       return true;
1209a8c8bf6SGabor Horvath   }
1219a8c8bf6SGabor Horvath 
122*f18da190SBalazs Benics   return matchesAny(Call, UnlockFn, PthreadUnlockFn, MtxUnlock);
123829c6bc0SGabor Horvath }
124829c6bc0SGabor Horvath 
checkPostCall(const CallEvent & Call,CheckerContext & C) const125c154f7bcSAnna Zaks void BlockInCriticalSectionChecker::checkPostCall(const CallEvent &Call,
126c154f7bcSAnna Zaks                                                   CheckerContext &C) const {
1279a8c8bf6SGabor Horvath   initIdentifierInfo(C.getASTContext());
1289a8c8bf6SGabor Horvath 
129829c6bc0SGabor Horvath   if (!isBlockingFunction(Call)
130829c6bc0SGabor Horvath       && !isLockFunction(Call)
131829c6bc0SGabor Horvath       && !isUnlockFunction(Call))
132c154f7bcSAnna Zaks     return;
133c154f7bcSAnna Zaks 
134c154f7bcSAnna Zaks   ProgramStateRef State = C.getState();
135c154f7bcSAnna Zaks   unsigned mutexCount = State->get<MutexCounter>();
136829c6bc0SGabor Horvath   if (isUnlockFunction(Call) && mutexCount > 0) {
137c154f7bcSAnna Zaks     State = State->set<MutexCounter>(--mutexCount);
138c154f7bcSAnna Zaks     C.addTransition(State);
139829c6bc0SGabor Horvath   } else if (isLockFunction(Call)) {
140c154f7bcSAnna Zaks     State = State->set<MutexCounter>(++mutexCount);
141c154f7bcSAnna Zaks     C.addTransition(State);
142c154f7bcSAnna Zaks   } else if (mutexCount > 0) {
143c154f7bcSAnna Zaks     SymbolRef BlockDesc = Call.getReturnValue().getAsSymbol();
144c154f7bcSAnna Zaks     reportBlockInCritSection(BlockDesc, Call, C);
145c154f7bcSAnna Zaks   }
146c154f7bcSAnna Zaks }
147c154f7bcSAnna Zaks 
reportBlockInCritSection(SymbolRef BlockDescSym,const CallEvent & Call,CheckerContext & C) const148c154f7bcSAnna Zaks void BlockInCriticalSectionChecker::reportBlockInCritSection(
149c154f7bcSAnna Zaks     SymbolRef BlockDescSym, const CallEvent &Call, CheckerContext &C) const {
150c154f7bcSAnna Zaks   ExplodedNode *ErrNode = C.generateNonFatalErrorNode();
151c154f7bcSAnna Zaks   if (!ErrNode)
152c154f7bcSAnna Zaks     return;
153c154f7bcSAnna Zaks 
154829c6bc0SGabor Horvath   std::string msg;
155829c6bc0SGabor Horvath   llvm::raw_string_ostream os(msg);
156829c6bc0SGabor Horvath   os << "Call to blocking function '" << Call.getCalleeIdentifier()->getName()
157829c6bc0SGabor Horvath      << "' inside of critical section";
1582f169e7cSArtem Dergachev   auto R = std::make_unique<PathSensitiveBugReport>(*BlockInCritSectionBugType,
1592f169e7cSArtem Dergachev                                                     os.str(), ErrNode);
160c154f7bcSAnna Zaks   R->addRange(Call.getSourceRange());
161c154f7bcSAnna Zaks   R->markInteresting(BlockDescSym);
162c154f7bcSAnna Zaks   C.emitReport(std::move(R));
163c154f7bcSAnna Zaks }
164c154f7bcSAnna Zaks 
registerBlockInCriticalSectionChecker(CheckerManager & mgr)165c154f7bcSAnna Zaks void ento::registerBlockInCriticalSectionChecker(CheckerManager &mgr) {
166c154f7bcSAnna Zaks   mgr.registerChecker<BlockInCriticalSectionChecker>();
167c154f7bcSAnna Zaks }
168058a7a45SKristof Umann 
shouldRegisterBlockInCriticalSectionChecker(const CheckerManager & mgr)169bda3dd0dSKirstóf Umann bool ento::shouldRegisterBlockInCriticalSectionChecker(const CheckerManager &mgr) {
170058a7a45SKristof Umann   return true;
171058a7a45SKristof Umann }
172