blob: 0eb3c3d1d0e6da6409fb5555f4932fb9cac0fae9 [file] [log] [blame]
//===-- 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;
}