27#include "llvm/ADT/STLExtras.h"
28#include "llvm/ADT/SmallString.h"
29#include "llvm/ADT/StringExtras.h"
40struct CritSectionMarker {
41 const Expr *LockExpr{};
44 void Profile(llvm::FoldingSetNodeID &ID)
const {
49 [[nodiscard]]
constexpr bool
51 return LockExpr ==
Other.LockExpr && LockReg ==
Other.LockReg;
53 [[nodiscard]]
constexpr bool
55 return !(*
this ==
Other);
59class CallDescriptionBasedMatcher {
66 : LockFn(
std::move(LockFn)), UnlockFn(
std::move(UnlockFn)) {}
75class FirstArgMutexDescriptor :
public CallDescriptionBasedMatcher {
78 : CallDescriptionBasedMatcher(
std::move(LockFn),
std::move(UnlockFn)) {}
81 return Call.getArgSVal(0).getAsRegion();
85class MemberMutexDescriptor :
public CallDescriptionBasedMatcher {
88 : CallDescriptionBasedMatcher(
std::move(LockFn),
std::move(UnlockFn)) {}
91 return cast<CXXMemberCall>(
Call).getCXXThisVal().getAsRegion();
95class RAIIMutexDescriptor {
97 mutable bool IdentifierInfoInitialized{};
101 if (!IdentifierInfoInitialized) {
107 const auto &ASTCtx =
Call.getState()->getStateManager().getContext();
108 Guard = &ASTCtx.Idents.get(GuardName);
112 template <
typename T>
bool matchesImpl(
const CallEvent &
Call)
const {
113 const T *
C = dyn_cast<T>(&
Call);
117 cast<CXXRecordDecl>(
C->getDecl()->getParent())->getIdentifier();
122 RAIIMutexDescriptor(StringRef GuardName) : GuardName(GuardName) {}
124 initIdentifierInfo(
Call);
126 return matchesImpl<CXXConstructorCall>(
Call);
128 return matchesImpl<CXXDestructorCall>(
Call);
134 if (std::optional<SVal> Object =
Call.getReturnValueUnderConstruction()) {
135 LockRegion =
Object->getAsRegion();
138 LockRegion = cast<CXXDestructorCall>(
Call).getCXXThisVal().getAsRegion();
144using MutexDescriptor =
145 std::variant<FirstArgMutexDescriptor, MemberMutexDescriptor,
146 RAIIMutexDescriptor>;
148class BlockInCriticalSectionChecker :
public Checker<check::PostCall> {
150 const std::array<MutexDescriptor, 8> MutexDescriptors{
158 MemberMutexDescriptor(
162 {CDM::CXXMethod, {
"std",
"unlock"}, 0}),
163 FirstArgMutexDescriptor({CDM::CLibrary, {
"pthread_mutex_lock"}, 1},
164 {CDM::CLibrary, {
"pthread_mutex_unlock"}, 1}),
165 FirstArgMutexDescriptor({CDM::CLibrary, {
"mtx_lock"}, 1},
166 {CDM::CLibrary, {
"mtx_unlock"}, 1}),
167 FirstArgMutexDescriptor({CDM::CLibrary, {
"pthread_mutex_trylock"}, 1},
168 {CDM::CLibrary, {
"pthread_mutex_unlock"}, 1}),
169 FirstArgMutexDescriptor({CDM::CLibrary, {
"mtx_trylock"}, 1},
170 {CDM::CLibrary, {
"mtx_unlock"}, 1}),
171 FirstArgMutexDescriptor({CDM::CLibrary, {
"mtx_timedlock"}, 1},
172 {CDM::CLibrary, {
"mtx_unlock"}, 1}),
173 RAIIMutexDescriptor(
"lock_guard"),
174 RAIIMutexDescriptor(
"unique_lock")};
177 {CDM::CLibrary, {
"getc"}},
178 {CDM::CLibrary, {
"fgets"}},
179 {CDM::CLibrary, {
"read"}},
180 {CDM::CLibrary, {
"recv"}}};
182 const BugType BlockInCritSectionBugType{
183 this,
"Call to blocking function in critical section",
"Blocking Error"};
187 [[nodiscard]]
const NoteTag *createCritSectionNote(CritSectionMarker M,
190 [[nodiscard]] std::optional<MutexDescriptor>
194 void handleLock(
const MutexDescriptor &Mutex,
const CallEvent &
Call,
197 void handleUnlock(
const MutexDescriptor &Mutex,
const CallEvent &
Call,
200 [[nodiscard]]
bool isBlockingInCritSection(
const CallEvent &
Call,
219struct std::iterator_traits<
220 typename
llvm::ImmutableList<CritSectionMarker>::iterator> {
228std::optional<MutexDescriptor>
229BlockInCriticalSectionChecker::checkDescriptorMatch(
const CallEvent &
Call,
232 const auto Descriptor =
233 llvm::find_if(MutexDescriptors, [&
Call, IsLock](
auto &&Descriptor) {
235 [&
Call, IsLock](
auto &&DescriptorImpl) {
236 return DescriptorImpl.matches(
Call, IsLock);
240 if (Descriptor != MutexDescriptors.end())
247 const auto *BaseClassRegion = dyn_cast<CXXBaseObjectRegion>(Reg);
250 Reg = BaseClassRegion->getSuperRegion();
256 const MutexDescriptor &Descriptor,
265void BlockInCriticalSectionChecker::handleLock(
266 const MutexDescriptor &LockDescriptor,
const CallEvent &
Call,
273 const CritSectionMarker MarkToAdd{
Call.getOriginExpr(), MutexRegion};
275 C.getState()->add<ActiveCritSections>(MarkToAdd);
276 C.addTransition(StateWithLockEvent, createCritSectionNote(MarkToAdd,
C));
279void BlockInCriticalSectionChecker::handleUnlock(
280 const MutexDescriptor &UnlockDescriptor,
const CallEvent &
Call,
288 const auto ActiveSections = State->get<ActiveCritSections>();
289 const auto MostRecentLock =
290 llvm::find_if(ActiveSections, [MutexRegion](
auto &&Marker) {
291 return Marker.LockReg == MutexRegion;
293 if (MostRecentLock == ActiveSections.end())
297 auto &Factory = State->get_context<ActiveCritSections>();
298 llvm::ImmutableList<CritSectionMarker> NewList = Factory.getEmptyList();
299 for (
auto It = ActiveSections.begin(), End = ActiveSections.end(); It != End;
301 if (It != MostRecentLock)
302 NewList = Factory.add(*It, NewList);
305 State = State->set<ActiveCritSections>(NewList);
306 C.addTransition(State);
309bool BlockInCriticalSectionChecker::isBlockingInCritSection(
311 return BlockingFunctions.contains(
Call) &&
312 !
C.getState()->get<ActiveCritSections>().isEmpty();
315void BlockInCriticalSectionChecker::checkPostCall(
const CallEvent &
Call,
317 if (isBlockingInCritSection(
Call,
C)) {
318 reportBlockInCritSection(
Call,
C);
319 }
else if (std::optional<MutexDescriptor> LockDesc =
320 checkDescriptorMatch(
Call,
C,
true)) {
321 handleLock(*LockDesc,
Call,
C);
322 }
else if (std::optional<MutexDescriptor> UnlockDesc =
323 checkDescriptorMatch(
Call,
C,
false)) {
324 handleUnlock(*UnlockDesc,
Call,
C);
328void BlockInCriticalSectionChecker::reportBlockInCritSection(
330 ExplodedNode *ErrNode =
C.generateNonFatalErrorNode(
C.getState());
335 llvm::raw_string_ostream os(msg);
336 os <<
"Call to blocking function '" <<
Call.getCalleeIdentifier()->getName()
337 <<
"' inside of critical section";
338 auto R = std::make_unique<PathSensitiveBugReport>(BlockInCritSectionBugType,
340 R->addRange(
Call.getSourceRange());
341 R->markInteresting(
Call.getReturnValue());
342 C.emitReport(std::move(R));
346BlockInCriticalSectionChecker::createCritSectionNote(CritSectionMarker M,
348 const BugType *BT = &this->BlockInCritSectionBugType;
350 llvm::raw_ostream &OS) {
355 const auto CritSectionBegins =
359 CritSectionBegins, std::back_inserter(LocksForMutex),
360 [M](
const auto &Marker) { return Marker.LockReg == M.LockReg; });
361 if (LocksForMutex.empty())
366 std::reverse(LocksForMutex.begin(), LocksForMutex.end());
370 const auto Position =
371 llvm::find_if(std::as_const(LocksForMutex), [M](
const auto &Marker) {
372 return Marker.LockExpr == M.LockExpr;
374 if (Position == LocksForMutex.end())
379 if (LocksForMutex.size() == 1) {
380 OS <<
"Entering critical section here";
384 const auto IndexOfLock =
385 std::distance(std::as_const(LocksForMutex).begin(), Position);
387 const auto OrdinalOfLock = IndexOfLock + 1;
388 OS <<
"Entering critical section for the " << OrdinalOfLock
389 << llvm::getOrdinalSuffix(OrdinalOfLock) <<
" time here";
393void ento::registerBlockInCriticalSectionChecker(
CheckerManager &mgr) {
397bool ento::shouldRegisterBlockInCriticalSectionChecker(
static const MemRegion * skipStdBaseClassRegion(const MemRegion *Reg)
static const MemRegion * getRegion(const CallEvent &Call, const MutexDescriptor &Descriptor, bool IsLock)
#define REGISTER_LIST_WITH_PROGRAMSTATE(Name, Elem)
Declares an immutable list type NameTy, suitable for placement into the ProgramState.
This represents one expression.
One of these records is kept for each identifier that is lexed.
const BugType & getBugType() const
An immutable set of CallDescriptions.
A CallDescription is a pattern that can be used to match calls based on the qualified name and the ar...
bool matches(const CallEvent &Call) const
Returns true if the CallEvent is a call to a function that matches the CallDescription.
Represents an abstract call to a function or method along a particular path.
CHECKER * registerChecker(AT &&... Args)
Used to register checkers.
const ProgramStateRef & getState() const
MemRegion - The root abstract class for all memory regions.
The tag upon which the TagVisitor reacts.
const ExplodedNode * getErrorNode() const
bool isWithinStdNamespace(const Decl *D)
Returns true if declaration D is in std namespace or any nested namespace or class scope.
bool matches(const til::SExpr *E1, const til::SExpr *E2)
The JSON file list parser is used to communicate input to InstallAPI.
if(T->getSizeExpr()) TRY_TO(TraverseStmt(const_cast< Expr * >(T -> getSizeExpr())))
bool operator==(const CallGraphNode::CallRecord &LHS, const CallGraphNode::CallRecord &RHS)
bool operator!=(CanQual< T > x, CanQual< U > y)
const FunctionProtoType * T
@ Other
Other implicit parameter.
Diagnostic wrappers for TextAPI types for error reporting.
std::ptrdiff_t difference_type
CritSectionMarker & reference
CritSectionMarker * pointer
CritSectionMarker value_type
std::forward_iterator_tag iterator_category