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