Index: include/clang/StaticAnalyzer/Checkers/Checkers.td =================================================================== --- include/clang/StaticAnalyzer/Checkers/Checkers.td +++ include/clang/StaticAnalyzer/Checkers/Checkers.td @@ -436,6 +436,10 @@ HelpText<"Check for misuses of stream APIs">, DescFile<"SimpleStreamChecker.cpp">; +def BlockInCriticalSectionChecker : Checker<"BlockInCriticalSection">, + HelpText<"Check for calls to blocking functions inside a critical section">, + DescFile<"BlockInCriticalSectionChecker.cpp">; + } // end "alpha.unix" let ParentPackage = CString in { Index: lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp =================================================================== --- /dev/null +++ lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp @@ -0,0 +1,109 @@ +//===-- BlockInCriticalSectionChecker.cpp -----------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// 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 "ClangSACheckers.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 { + + CallDescription LockFn, UnlockFn, SleepFn, GetcFn, FgetsFn, ReadFn, RecvFn; + + std::unique_ptr BlockInCritSectionBugType; + + void reportBlockInCritSection(SymbolRef FileDescSym, + const CallEvent &call, + CheckerContext &C) const; + +public: + BlockInCriticalSectionChecker(); + + void checkPreCall(const CallEvent &Call, CheckerContext &C) 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() + : LockFn("lock"), UnlockFn("unlock"), SleepFn("sleep"), GetcFn("getc"), + FgetsFn("fgets"), ReadFn("read"), RecvFn("recv") { + // Initialize the bug type. + BlockInCritSectionBugType.reset( + new BugType(this, "Call to blocking function in critical section", + "Blocking Error")); +} + +void BlockInCriticalSectionChecker::checkPreCall(const CallEvent &Call, + CheckerContext &C) const { +} + +void BlockInCriticalSectionChecker::checkPostCall(const CallEvent &Call, + CheckerContext &C) const { + if (!Call.isCalled(LockFn) + && !Call.isCalled(SleepFn) + && !Call.isCalled(GetcFn) + && !Call.isCalled(FgetsFn) + && !Call.isCalled(ReadFn) + && !Call.isCalled(RecvFn) + && !Call.isCalled(UnlockFn)) + return; + + ProgramStateRef State = C.getState(); + unsigned mutexCount = State->get(); + if (Call.isCalled(UnlockFn)) { + State = State->set(--mutexCount); + C.addTransition(State); + } else if (Call.isCalled(LockFn)) { + State = State->set(++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; + + auto R = llvm::make_unique(*BlockInCritSectionBugType, + "A blocking function %s is called inside a critical section.", ErrNode); + R->addRange(Call.getSourceRange()); + R->markInteresting(BlockDescSym); + C.emitReport(std::move(R)); +} + +void ento::registerBlockInCriticalSectionChecker(CheckerManager &mgr) { + mgr.registerChecker(); +} Index: lib/StaticAnalyzer/Checkers/CMakeLists.txt =================================================================== --- lib/StaticAnalyzer/Checkers/CMakeLists.txt +++ lib/StaticAnalyzer/Checkers/CMakeLists.txt @@ -9,6 +9,7 @@ ArrayBoundChecker.cpp ArrayBoundCheckerV2.cpp BasicObjCFoundationChecks.cpp + BlockInCriticalSectionChecker.cpp BoolAssignmentChecker.cpp BuiltinFunctionChecker.cpp CStringChecker.cpp Index: test/Analysis/block-in-critical-section.cpp =================================================================== --- /dev/null +++ test/Analysis/block-in-critical-section.cpp @@ -0,0 +1,42 @@ +// RUN: %clang_cc1 -analyze -analyzer-checker=alpha.unix.BlockInCriticalSection -std=c++11 -verify %s + +void sleep(int x) {} + +namespace std { +struct mutex { + void lock() {} + void unlock() {} +}; +} + +void testBlockInCriticalSection() { + std::mutex m; + m.lock(); + sleep(3); // expected-warning {{A blocking function %s is called inside a critical section}} + m.unlock(); +} + +void testBlockInCriticalSectionWithNestedMutexes() { + std::mutex m, n, k; + m.lock(); + n.lock(); + k.lock(); + sleep(3); // expected-warning {{A blocking function %s is called inside a critical section}} + k.unlock(); + sleep(5); // expected-warning {{A blocking function %s is called inside a critical section}} + n.unlock(); + sleep(3); // expected-warning {{A blocking function %s is called inside a critical section}} + m.unlock(); + sleep(3); // no-warning +} + +void f() { + sleep(1000); // expected-warning {{A blocking function %s is called inside a critical section}} +} + +void testBlockInCriticalSectionInterProcedural() { + std::mutex m; + m.lock(); + f(); + m.unlock(); +}