Path: blob/main/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp
35266 views
//===-- BlockInCriticalSectionChecker.cpp -----------------------*- C++ -*-===//1//2// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.3// See https://llvm.org/LICENSE.txt for license information.4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception5//6//===----------------------------------------------------------------------===//7//8// Defines a checker for blocks in critical sections. This checker should find9// the calls to blocking functions (for example: sleep, getc, fgets, read,10// recv etc.) inside a critical section. When sleep(x) is called while a mutex11// is held, other threades cannot lock the same mutex. This might take some12// time, leading to bad performance or even deadlock.13//14//===----------------------------------------------------------------------===//1516#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"17#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"18#include "clang/StaticAnalyzer/Core/Checker.h"19#include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h"20#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"21#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"22#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h"23#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState_Fwd.h"24#include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h"25#include "llvm/ADT/STLExtras.h"26#include "llvm/ADT/SmallString.h"27#include "llvm/ADT/StringExtras.h"2829#include <iterator>30#include <utility>31#include <variant>3233using namespace clang;34using namespace ento;3536namespace {3738struct CritSectionMarker {39const Expr *LockExpr{};40const MemRegion *LockReg{};4142void Profile(llvm::FoldingSetNodeID &ID) const {43ID.Add(LockExpr);44ID.Add(LockReg);45}4647[[nodiscard]] constexpr bool48operator==(const CritSectionMarker &Other) const noexcept {49return LockExpr == Other.LockExpr && LockReg == Other.LockReg;50}51[[nodiscard]] constexpr bool52operator!=(const CritSectionMarker &Other) const noexcept {53return !(*this == Other);54}55};5657class CallDescriptionBasedMatcher {58CallDescription LockFn;59CallDescription UnlockFn;6061public:62CallDescriptionBasedMatcher(CallDescription &&LockFn,63CallDescription &&UnlockFn)64: LockFn(std::move(LockFn)), UnlockFn(std::move(UnlockFn)) {}65[[nodiscard]] bool matches(const CallEvent &Call, bool IsLock) const {66if (IsLock) {67return LockFn.matches(Call);68}69return UnlockFn.matches(Call);70}71};7273class FirstArgMutexDescriptor : public CallDescriptionBasedMatcher {74public:75FirstArgMutexDescriptor(CallDescription &&LockFn, CallDescription &&UnlockFn)76: CallDescriptionBasedMatcher(std::move(LockFn), std::move(UnlockFn)) {}7778[[nodiscard]] const MemRegion *getRegion(const CallEvent &Call, bool) const {79return Call.getArgSVal(0).getAsRegion();80}81};8283class MemberMutexDescriptor : public CallDescriptionBasedMatcher {84public:85MemberMutexDescriptor(CallDescription &&LockFn, CallDescription &&UnlockFn)86: CallDescriptionBasedMatcher(std::move(LockFn), std::move(UnlockFn)) {}8788[[nodiscard]] const MemRegion *getRegion(const CallEvent &Call, bool) const {89return cast<CXXMemberCall>(Call).getCXXThisVal().getAsRegion();90}91};9293class RAIIMutexDescriptor {94mutable const IdentifierInfo *Guard{};95mutable bool IdentifierInfoInitialized{};96mutable llvm::SmallString<32> GuardName{};9798void initIdentifierInfo(const CallEvent &Call) const {99if (!IdentifierInfoInitialized) {100// In case of checking C code, or when the corresponding headers are not101// included, we might end up query the identifier table every time when102// this function is called instead of early returning it. To avoid this, a103// bool variable (IdentifierInfoInitialized) is used and the function will104// be run only once.105const auto &ASTCtx = Call.getState()->getStateManager().getContext();106Guard = &ASTCtx.Idents.get(GuardName);107}108}109110template <typename T> bool matchesImpl(const CallEvent &Call) const {111const T *C = dyn_cast<T>(&Call);112if (!C)113return false;114const IdentifierInfo *II =115cast<CXXRecordDecl>(C->getDecl()->getParent())->getIdentifier();116return II == Guard;117}118119public:120RAIIMutexDescriptor(StringRef GuardName) : GuardName(GuardName) {}121[[nodiscard]] bool matches(const CallEvent &Call, bool IsLock) const {122initIdentifierInfo(Call);123if (IsLock) {124return matchesImpl<CXXConstructorCall>(Call);125}126return matchesImpl<CXXDestructorCall>(Call);127}128[[nodiscard]] const MemRegion *getRegion(const CallEvent &Call,129bool IsLock) const {130const MemRegion *LockRegion = nullptr;131if (IsLock) {132if (std::optional<SVal> Object = Call.getReturnValueUnderConstruction()) {133LockRegion = Object->getAsRegion();134}135} else {136LockRegion = cast<CXXDestructorCall>(Call).getCXXThisVal().getAsRegion();137}138return LockRegion;139}140};141142using MutexDescriptor =143std::variant<FirstArgMutexDescriptor, MemberMutexDescriptor,144RAIIMutexDescriptor>;145146class BlockInCriticalSectionChecker : public Checker<check::PostCall> {147private:148const std::array<MutexDescriptor, 8> MutexDescriptors{149// NOTE: There are standard library implementations where some methods150// of `std::mutex` are inherited from an implementation detail base151// class, and those aren't matched by the name specification {"std",152// "mutex", "lock"}.153// As a workaround here we omit the class name and only require the154// presence of the name parts "std" and "lock"/"unlock".155// TODO: Ensure that CallDescription understands inherited methods.156MemberMutexDescriptor(157{/*MatchAs=*/CDM::CXXMethod,158/*QualifiedName=*/{"std", /*"mutex",*/ "lock"},159/*RequiredArgs=*/0},160{CDM::CXXMethod, {"std", /*"mutex",*/ "unlock"}, 0}),161FirstArgMutexDescriptor({CDM::CLibrary, {"pthread_mutex_lock"}, 1},162{CDM::CLibrary, {"pthread_mutex_unlock"}, 1}),163FirstArgMutexDescriptor({CDM::CLibrary, {"mtx_lock"}, 1},164{CDM::CLibrary, {"mtx_unlock"}, 1}),165FirstArgMutexDescriptor({CDM::CLibrary, {"pthread_mutex_trylock"}, 1},166{CDM::CLibrary, {"pthread_mutex_unlock"}, 1}),167FirstArgMutexDescriptor({CDM::CLibrary, {"mtx_trylock"}, 1},168{CDM::CLibrary, {"mtx_unlock"}, 1}),169FirstArgMutexDescriptor({CDM::CLibrary, {"mtx_timedlock"}, 1},170{CDM::CLibrary, {"mtx_unlock"}, 1}),171RAIIMutexDescriptor("lock_guard"),172RAIIMutexDescriptor("unique_lock")};173174const CallDescriptionSet BlockingFunctions{{CDM::CLibrary, {"sleep"}},175{CDM::CLibrary, {"getc"}},176{CDM::CLibrary, {"fgets"}},177{CDM::CLibrary, {"read"}},178{CDM::CLibrary, {"recv"}}};179180const BugType BlockInCritSectionBugType{181this, "Call to blocking function in critical section", "Blocking Error"};182183void reportBlockInCritSection(const CallEvent &call, CheckerContext &C) const;184185[[nodiscard]] const NoteTag *createCritSectionNote(CritSectionMarker M,186CheckerContext &C) const;187188[[nodiscard]] std::optional<MutexDescriptor>189checkDescriptorMatch(const CallEvent &Call, CheckerContext &C,190bool IsLock) const;191192void handleLock(const MutexDescriptor &Mutex, const CallEvent &Call,193CheckerContext &C) const;194195void handleUnlock(const MutexDescriptor &Mutex, const CallEvent &Call,196CheckerContext &C) const;197198[[nodiscard]] bool isBlockingInCritSection(const CallEvent &Call,199CheckerContext &C) const;200201public:202/// Process unlock.203/// Process lock.204/// Process blocking functions (sleep, getc, fgets, read, recv)205void checkPostCall(const CallEvent &Call, CheckerContext &C) const;206};207208} // end anonymous namespace209210REGISTER_LIST_WITH_PROGRAMSTATE(ActiveCritSections, CritSectionMarker)211212// Iterator traits for ImmutableList data structure213// that enable the use of STL algorithms.214// TODO: Move these to llvm::ImmutableList when overhauling immutable data215// structures for proper iterator concept support.216template <>217struct std::iterator_traits<218typename llvm::ImmutableList<CritSectionMarker>::iterator> {219using iterator_category = std::forward_iterator_tag;220using value_type = CritSectionMarker;221using difference_type = std::ptrdiff_t;222using reference = CritSectionMarker &;223using pointer = CritSectionMarker *;224};225226std::optional<MutexDescriptor>227BlockInCriticalSectionChecker::checkDescriptorMatch(const CallEvent &Call,228CheckerContext &C,229bool IsLock) const {230const auto Descriptor =231llvm::find_if(MutexDescriptors, [&Call, IsLock](auto &&Descriptor) {232return std::visit(233[&Call, IsLock](auto &&DescriptorImpl) {234return DescriptorImpl.matches(Call, IsLock);235},236Descriptor);237});238if (Descriptor != MutexDescriptors.end())239return *Descriptor;240return std::nullopt;241}242243static const MemRegion *getRegion(const CallEvent &Call,244const MutexDescriptor &Descriptor,245bool IsLock) {246return std::visit(247[&Call, IsLock](auto &&Descriptor) {248return Descriptor.getRegion(Call, IsLock);249},250Descriptor);251}252253void BlockInCriticalSectionChecker::handleLock(254const MutexDescriptor &LockDescriptor, const CallEvent &Call,255CheckerContext &C) const {256const MemRegion *MutexRegion =257getRegion(Call, LockDescriptor, /*IsLock=*/true);258if (!MutexRegion)259return;260261const CritSectionMarker MarkToAdd{Call.getOriginExpr(), MutexRegion};262ProgramStateRef StateWithLockEvent =263C.getState()->add<ActiveCritSections>(MarkToAdd);264C.addTransition(StateWithLockEvent, createCritSectionNote(MarkToAdd, C));265}266267void BlockInCriticalSectionChecker::handleUnlock(268const MutexDescriptor &UnlockDescriptor, const CallEvent &Call,269CheckerContext &C) const {270const MemRegion *MutexRegion =271getRegion(Call, UnlockDescriptor, /*IsLock=*/false);272if (!MutexRegion)273return;274275ProgramStateRef State = C.getState();276const auto ActiveSections = State->get<ActiveCritSections>();277const auto MostRecentLock =278llvm::find_if(ActiveSections, [MutexRegion](auto &&Marker) {279return Marker.LockReg == MutexRegion;280});281if (MostRecentLock == ActiveSections.end())282return;283284// Build a new ImmutableList without this element.285auto &Factory = State->get_context<ActiveCritSections>();286llvm::ImmutableList<CritSectionMarker> NewList = Factory.getEmptyList();287for (auto It = ActiveSections.begin(), End = ActiveSections.end(); It != End;288++It) {289if (It != MostRecentLock)290NewList = Factory.add(*It, NewList);291}292293State = State->set<ActiveCritSections>(NewList);294C.addTransition(State);295}296297bool BlockInCriticalSectionChecker::isBlockingInCritSection(298const CallEvent &Call, CheckerContext &C) const {299return BlockingFunctions.contains(Call) &&300!C.getState()->get<ActiveCritSections>().isEmpty();301}302303void BlockInCriticalSectionChecker::checkPostCall(const CallEvent &Call,304CheckerContext &C) const {305if (isBlockingInCritSection(Call, C)) {306reportBlockInCritSection(Call, C);307} else if (std::optional<MutexDescriptor> LockDesc =308checkDescriptorMatch(Call, C, /*IsLock=*/true)) {309handleLock(*LockDesc, Call, C);310} else if (std::optional<MutexDescriptor> UnlockDesc =311checkDescriptorMatch(Call, C, /*IsLock=*/false)) {312handleUnlock(*UnlockDesc, Call, C);313}314}315316void BlockInCriticalSectionChecker::reportBlockInCritSection(317const CallEvent &Call, CheckerContext &C) const {318ExplodedNode *ErrNode = C.generateNonFatalErrorNode(C.getState());319if (!ErrNode)320return;321322std::string msg;323llvm::raw_string_ostream os(msg);324os << "Call to blocking function '" << Call.getCalleeIdentifier()->getName()325<< "' inside of critical section";326auto R = std::make_unique<PathSensitiveBugReport>(BlockInCritSectionBugType,327os.str(), ErrNode);328R->addRange(Call.getSourceRange());329R->markInteresting(Call.getReturnValue());330C.emitReport(std::move(R));331}332333const NoteTag *334BlockInCriticalSectionChecker::createCritSectionNote(CritSectionMarker M,335CheckerContext &C) const {336const BugType *BT = &this->BlockInCritSectionBugType;337return C.getNoteTag([M, BT](PathSensitiveBugReport &BR,338llvm::raw_ostream &OS) {339if (&BR.getBugType() != BT)340return;341342// Get the lock events for the mutex of the current line's lock event.343const auto CritSectionBegins =344BR.getErrorNode()->getState()->get<ActiveCritSections>();345llvm::SmallVector<CritSectionMarker, 4> LocksForMutex;346llvm::copy_if(347CritSectionBegins, std::back_inserter(LocksForMutex),348[M](const auto &Marker) { return Marker.LockReg == M.LockReg; });349if (LocksForMutex.empty())350return;351352// As the ImmutableList builds the locks by prepending them, we353// reverse the list to get the correct order.354std::reverse(LocksForMutex.begin(), LocksForMutex.end());355356// Find the index of the lock expression in the list of all locks for a357// given mutex (in acquisition order).358const auto Position =359llvm::find_if(std::as_const(LocksForMutex), [M](const auto &Marker) {360return Marker.LockExpr == M.LockExpr;361});362if (Position == LocksForMutex.end())363return;364365// If there is only one lock event, we don't need to specify how many times366// the critical section was entered.367if (LocksForMutex.size() == 1) {368OS << "Entering critical section here";369return;370}371372const auto IndexOfLock =373std::distance(std::as_const(LocksForMutex).begin(), Position);374375const auto OrdinalOfLock = IndexOfLock + 1;376OS << "Entering critical section for the " << OrdinalOfLock377<< llvm::getOrdinalSuffix(OrdinalOfLock) << " time here";378});379}380381void ento::registerBlockInCriticalSectionChecker(CheckerManager &mgr) {382mgr.registerChecker<BlockInCriticalSectionChecker>();383}384385bool ento::shouldRegisterBlockInCriticalSectionChecker(386const CheckerManager &mgr) {387return true;388}389390391