forked from OSchip/llvm-project
				
			
		
			
				
	
	
		
			190 lines
		
	
	
		
			6.7 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			190 lines
		
	
	
		
			6.7 KiB
		
	
	
	
		
			C++
		
	
	
	
//===-- 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;
 | 
						|
}
 |