diff options
Diffstat (limited to 'clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp')
| -rw-r--r-- | clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp | 189 | 
1 files changed, 189 insertions, 0 deletions
diff --git a/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp new file mode 100644 index 0000000000000..0eb3c3d1d0e6d --- /dev/null +++ b/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp @@ -0,0 +1,189 @@ +//===-- BlockInCriticalSectionChecker.cpp -----------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// Defines a checker for blocks in critical sections. This checker should find +// the calls to blocking functions (for example: sleep, getc, fgets, read, +// recv etc.) inside a critical section. When sleep(x) is called while a mutex +// is held, other threades cannot lock the same mutex. This might take some +// time, leading to bad performance or even deadlock. +// +//===----------------------------------------------------------------------===// + +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" +#include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" + +using namespace clang; +using namespace ento; + +namespace { + +class BlockInCriticalSectionChecker : public Checker<check::PostCall> { + +  mutable IdentifierInfo *IILockGuard, *IIUniqueLock; + +  CallDescription LockFn, UnlockFn, SleepFn, GetcFn, FgetsFn, ReadFn, RecvFn, +                  PthreadLockFn, PthreadTryLockFn, PthreadUnlockFn, +                  MtxLock, MtxTimedLock, MtxTryLock, MtxUnlock; + +  StringRef ClassLockGuard, ClassUniqueLock; + +  mutable bool IdentifierInfoInitialized; + +  std::unique_ptr<BugType> BlockInCritSectionBugType; + +  void initIdentifierInfo(ASTContext &Ctx) const; + +  void reportBlockInCritSection(SymbolRef FileDescSym, +                                const CallEvent &call, +                                CheckerContext &C) const; + +public: +  BlockInCriticalSectionChecker(); + +  bool isBlockingFunction(const CallEvent &Call) const; +  bool isLockFunction(const CallEvent &Call) const; +  bool isUnlockFunction(const CallEvent &Call) const; + +  /// Process unlock. +  /// Process lock. +  /// Process blocking functions (sleep, getc, fgets, read, recv) +  void checkPostCall(const CallEvent &Call, CheckerContext &C) const; +}; + +} // end anonymous namespace + +REGISTER_TRAIT_WITH_PROGRAMSTATE(MutexCounter, unsigned) + +BlockInCriticalSectionChecker::BlockInCriticalSectionChecker() +    : IILockGuard(nullptr), IIUniqueLock(nullptr), +      LockFn("lock"), UnlockFn("unlock"), SleepFn("sleep"), GetcFn("getc"), +      FgetsFn("fgets"), ReadFn("read"), RecvFn("recv"), +      PthreadLockFn("pthread_mutex_lock"), +      PthreadTryLockFn("pthread_mutex_trylock"), +      PthreadUnlockFn("pthread_mutex_unlock"), +      MtxLock("mtx_lock"), +      MtxTimedLock("mtx_timedlock"), +      MtxTryLock("mtx_trylock"), +      MtxUnlock("mtx_unlock"), +      ClassLockGuard("lock_guard"), +      ClassUniqueLock("unique_lock"), +      IdentifierInfoInitialized(false) { +  // Initialize the bug type. +  BlockInCritSectionBugType.reset( +      new BugType(this, "Call to blocking function in critical section", +                        "Blocking Error")); +} + +void BlockInCriticalSectionChecker::initIdentifierInfo(ASTContext &Ctx) const { +  if (!IdentifierInfoInitialized) { +    /* In case of checking C code, or when the corresponding headers are not +     * included, we might end up query the identifier table every time when this +     * function is called instead of early returning it. To avoid this, a bool +     * variable (IdentifierInfoInitialized) is used and the function will be run +     * only once. */ +    IILockGuard  = &Ctx.Idents.get(ClassLockGuard); +    IIUniqueLock = &Ctx.Idents.get(ClassUniqueLock); +    IdentifierInfoInitialized = true; +  } +} + +bool BlockInCriticalSectionChecker::isBlockingFunction(const CallEvent &Call) const { +  if (Call.isCalled(SleepFn) +      || Call.isCalled(GetcFn) +      || Call.isCalled(FgetsFn) +      || Call.isCalled(ReadFn) +      || Call.isCalled(RecvFn)) { +    return true; +  } +  return false; +} + +bool BlockInCriticalSectionChecker::isLockFunction(const CallEvent &Call) const { +  if (const auto *Ctor = dyn_cast<CXXConstructorCall>(&Call)) { +    auto IdentifierInfo = Ctor->getDecl()->getParent()->getIdentifier(); +    if (IdentifierInfo == IILockGuard || IdentifierInfo == IIUniqueLock) +      return true; +  } + +  if (Call.isCalled(LockFn) +      || Call.isCalled(PthreadLockFn) +      || Call.isCalled(PthreadTryLockFn) +      || Call.isCalled(MtxLock) +      || Call.isCalled(MtxTimedLock) +      || Call.isCalled(MtxTryLock)) { +    return true; +  } +  return false; +} + +bool BlockInCriticalSectionChecker::isUnlockFunction(const CallEvent &Call) const { +  if (const auto *Dtor = dyn_cast<CXXDestructorCall>(&Call)) { +    const auto *DRecordDecl = cast<CXXRecordDecl>(Dtor->getDecl()->getParent()); +    auto IdentifierInfo = DRecordDecl->getIdentifier(); +    if (IdentifierInfo == IILockGuard || IdentifierInfo == IIUniqueLock) +      return true; +  } + +  if (Call.isCalled(UnlockFn) +       || Call.isCalled(PthreadUnlockFn) +       || Call.isCalled(MtxUnlock)) { +    return true; +  } +  return false; +} + +void BlockInCriticalSectionChecker::checkPostCall(const CallEvent &Call, +                                                  CheckerContext &C) const { +  initIdentifierInfo(C.getASTContext()); + +  if (!isBlockingFunction(Call) +      && !isLockFunction(Call) +      && !isUnlockFunction(Call)) +    return; + +  ProgramStateRef State = C.getState(); +  unsigned mutexCount = State->get<MutexCounter>(); +  if (isUnlockFunction(Call) && mutexCount > 0) { +    State = State->set<MutexCounter>(--mutexCount); +    C.addTransition(State); +  } else if (isLockFunction(Call)) { +    State = State->set<MutexCounter>(++mutexCount); +    C.addTransition(State); +  } else if (mutexCount > 0) { +    SymbolRef BlockDesc = Call.getReturnValue().getAsSymbol(); +    reportBlockInCritSection(BlockDesc, Call, C); +  } +} + +void BlockInCriticalSectionChecker::reportBlockInCritSection( +    SymbolRef BlockDescSym, const CallEvent &Call, CheckerContext &C) const { +  ExplodedNode *ErrNode = C.generateNonFatalErrorNode(); +  if (!ErrNode) +    return; + +  std::string msg; +  llvm::raw_string_ostream os(msg); +  os << "Call to blocking function '" << Call.getCalleeIdentifier()->getName() +     << "' inside of critical section"; +  auto R = std::make_unique<PathSensitiveBugReport>(*BlockInCritSectionBugType, +                                                    os.str(), ErrNode); +  R->addRange(Call.getSourceRange()); +  R->markInteresting(BlockDescSym); +  C.emitReport(std::move(R)); +} + +void ento::registerBlockInCriticalSectionChecker(CheckerManager &mgr) { +  mgr.registerChecker<BlockInCriticalSectionChecker>(); +} + +bool ento::shouldRegisterBlockInCriticalSectionChecker(const LangOptions &LO) { +  return true; +}  | 
