27#include "llvm/ADT/STLExtras.h"
28#include "llvm/ADT/StringExtras.h"
37 return Call.getArgSVal(0).getAsRegion();
45 if (std::optional<SVal>
Object =
Call.getReturnValueUnderConstruction())
46 return Object->getAsRegion();
53 const auto *Instance = dyn_cast<CXXInstanceCall>(&
Call);
54 if (Instance && isa_and_nonnull<CXXDestructorDecl>(Instance->getDecl()))
55 return Instance->getCXXThisVal().getAsRegion();
63 if (Params.size() < 2)
65 QualType ParamType = Params[1]->getType().getNonReferenceType();
67 return !(RD && RD->getName() ==
"defer_lock_t" && RD->isInStdNamespace());
72struct CritSectionMarker {
73 const Expr *LockExpr{};
74 const MemRegion *LockReg{};
76 void Profile(llvm::FoldingSetNodeID &ID)
const {
81 [[nodiscard]]
constexpr bool
83 return LockExpr ==
Other.LockExpr && LockReg ==
Other.LockReg;
85 [[nodiscard]]
constexpr bool
87 return !(*
this ==
Other);
103struct ThreadingCallDescription {
106 FilterFn Filter = [](
const CallEvent &) {
return true; };
111 const CallDescription OpenFunction{CDM::CLibrary, {
"open"}, 2};
113 const int NonBlockMacroVal;
114 bool Satisfied =
false;
117 SuppressNonBlockingStreams(
SymbolRef StreamSym,
int NonBlockMacroVal)
118 : StreamSym(StreamSym), NonBlockMacroVal(NonBlockMacroVal) {}
120 static void *getTag() {
125 void Profile(llvm::FoldingSetNodeID &ID)
const override {
126 ID.AddPointer(getTag());
130 BugReporterContext &BRC,
131 PathSensitiveBugReport &BR)
override {
135 std::optional<StmtPoint> Point = N->
getLocationAs<StmtPoint>();
139 const auto *CE = Point->getStmtAs<CallExpr>();
140 if (!CE || !OpenFunction.matchesAsWritten(*CE))
153 if ((*FlagVal & NonBlockMacroVal) != 0)
160class BlockInCriticalSectionChecker
161 :
public Checker<check::PostCall, eval::Call> {
163 const CallDescriptionMap<ThreadingCallDescription> ThreadingCalls{
171 {{CDM::CXXMethod, {
"std",
"lock"}, 0},
173 {{CDM::CXXMethod, {
"std",
"unlock"}, 0},
175 {{CDM::CLibrary, {
"pthread_mutex_lock"}, 1}, {RoleKind::Lock}},
176 {{CDM::CLibrary, {
"pthread_mutex_unlock"}, 1}, {RoleKind::Unlock}},
177 {{CDM::CLibrary, {
"mtx_lock"}, 1}, {RoleKind::Lock}},
178 {{CDM::CLibrary, {
"mtx_unlock"}, 1}, {RoleKind::Unlock}},
179 {{CDM::CLibrary, {
"pthread_mutex_trylock"}, 1}, {RoleKind::Lock}},
180 {{CDM::CLibrary, {
"mtx_trylock"}, 1}, {RoleKind::Lock}},
181 {{CDM::CLibrary, {
"mtx_timedlock"}, 1}, {RoleKind::Lock}},
182 {{CDM::CXXMethod, {
"lock_guard",
"lock_guard"}},
184 {{CDM::CXXMethod, {
"lock_guard",
"~lock_guard"}},
186 {{CDM::CXXMethod, {
"unique_lock",
"unique_lock"}},
188 {{CDM::CXXMethod, {
"unique_lock",
"~unique_lock"}},
190 {{CDM::CXXMethod, {
"scoped_lock",
"scoped_lock"}},
192 {{CDM::CXXMethod, {
"scoped_lock",
"~scoped_lock"}},
196 const CallDescriptionSet BlockingFunctions{{CDM::CLibrary, {
"sleep"}},
197 {CDM::CLibrary, {
"getc"}},
198 {CDM::CLibrary, {
"fgets"}},
199 {CDM::CLibrary, {
"read"}},
200 {CDM::CLibrary, {
"recv"}}};
202 const BugType BlockInCritSectionBugType{
203 this,
"Call to blocking function in critical section",
"Blocking Error"};
205 using O_NONBLOCKValueTy = std::optional<int>;
206 mutable std::optional<O_NONBLOCKValueTy> O_NONBLOCKValue;
208 void reportBlockInCritSection(
const CallEvent &call, CheckerContext &
C)
const;
210 [[nodiscard]]
const NoteTag *createCritSectionNote(CritSectionMarker M,
211 CheckerContext &
C)
const;
213 [[nodiscard]]
const ThreadingCallDescription *
214 lookupThreadingCall(
const CallEvent &
Call)
const;
216 void handleLock(
const ThreadingCallDescription &Desc,
const CallEvent &
Call,
219 void handleUnlock(
const ThreadingCallDescription &Desc,
const CallEvent &
Call,
220 CheckerContext &
C)
const;
222 [[nodiscard]]
bool isBlockingInCritSection(
const CallEvent &
Call,
223 CheckerContext &
C)
const;
229 void checkPostCall(
const CallEvent &
Call, CheckerContext &
C)
const;
233 bool evalCall(
const CallEvent &
Call, CheckerContext &
C)
const;
240const ThreadingCallDescription *
241BlockInCriticalSectionChecker::lookupThreadingCall(
243 const ThreadingCallDescription *Desc = ThreadingCalls.lookup(
Call);
246 if (!Desc->Filter(
Call))
253 const auto *BaseClassRegion = dyn_cast<CXXBaseObjectRegion>(Reg);
256 Reg = BaseClassRegion->getSuperRegion();
262 const ThreadingCallDescription &Desc) {
266void BlockInCriticalSectionChecker::handleLock(
267 const ThreadingCallDescription &Desc,
const CallEvent &
Call,
273 const CritSectionMarker MarkToAdd{
Call.getOriginExpr(), MutexRegion};
275 State->add<ActiveCritSections>(MarkToAdd);
276 C.addTransition(StateWithLockEvent, createCritSectionNote(MarkToAdd,
C));
279void BlockInCriticalSectionChecker::handleUnlock(
280 const ThreadingCallDescription &Desc,
const CallEvent &
Call,
281 CheckerContext &
C)
const {
287 const auto ActiveSections = State->get<ActiveCritSections>();
288 const auto MostRecentLock =
289 llvm::find_if(ActiveSections, [MutexRegion](
auto &&Marker) {
290 return Marker.LockReg == MutexRegion;
292 if (MostRecentLock == ActiveSections.end())
296 auto &Factory = State->get_context<ActiveCritSections>();
297 llvm::ImmutableList<CritSectionMarker> NewList = Factory.getEmptyList();
298 for (
auto It = ActiveSections.begin(), End = ActiveSections.end(); It != End;
300 if (It != MostRecentLock)
301 NewList = Factory.add(*It, NewList);
304 State = State->set<ActiveCritSections>(NewList);
305 C.addTransition(State);
308bool BlockInCriticalSectionChecker::isBlockingInCritSection(
309 const CallEvent &
Call, CheckerContext &
C)
const {
311 !
C.getState()->get<ActiveCritSections>().isEmpty();
314void BlockInCriticalSectionChecker::checkPostCall(
const CallEvent &
Call,
315 CheckerContext &
C)
const {
316 if (isBlockingInCritSection(
Call,
C)) {
317 reportBlockInCritSection(
Call,
C);
325 const ThreadingCallDescription *Desc = lookupThreadingCall(
Call);
329 switch (Desc->Role) {
331 handleLock(*Desc,
Call,
C,
C.getState());
333 case RoleKind::Unlock:
334 handleUnlock(*Desc,
Call,
C);
339bool BlockInCriticalSectionChecker::evalCall(
const CallEvent &
Call,
340 CheckerContext &
C)
const {
346 const ThreadingCallDescription *Desc = lookupThreadingCall(
Call);
353 assert(Desc->Role == RoleKind::Lock &&
354 "Constructor threading call must be a lock");
359 if (
const auto *Ctor = dyn_cast<AnyCXXConstructorCall>(&
Call)) {
360 const MemRegion *ObjRegion = Ctor->getCXXThisVal().getAsRegion();
361 State = State->invalidateRegions(ObjRegion,
C.getCFGElementRef(),
362 C.blockCount(),
C.getStackFrame(),
365 handleLock(*Desc,
Call,
C, State);
369void BlockInCriticalSectionChecker::reportBlockInCritSection(
370 const CallEvent &
Call, CheckerContext &
C)
const {
371 ExplodedNode *ErrNode =
C.generateNonFatalErrorNode(
C.getState());
376 llvm::raw_string_ostream os(msg);
377 os <<
"Call to blocking function '" <<
Call.getCalleeIdentifier()->getName()
378 <<
"' inside of critical section";
379 auto R = std::make_unique<PathSensitiveBugReport>(BlockInCritSectionBugType,
385 StringRef FuncName =
Call.getCalleeIdentifier()->getName();
386 if (FuncName ==
"read" || FuncName ==
"recv") {
387 SVal SV =
Call.getArgSVal(0);
388 SValBuilder &SVB =
C.getSValBuilder();
390 ConditionTruthVal CTV =
391 state->areEqual(SV, SVB.
makeIntVal(-1,
C.getASTContext().IntTy));
396 if (!O_NONBLOCKValue)
398 "O_NONBLOCK",
C.getBugReporter().getPreprocessor());
399 if (*O_NONBLOCKValue)
400 R->addVisitor<SuppressNonBlockingStreams>(SR, **O_NONBLOCKValue);
403 R->addRange(
Call.getSourceRange());
404 R->markInteresting(
Call.getReturnValue());
405 C.emitReport(std::move(R));
409BlockInCriticalSectionChecker::createCritSectionNote(CritSectionMarker M,
410 CheckerContext &
C)
const {
411 const BugType *BT = &this->BlockInCritSectionBugType;
412 return C.getNoteTag([M, BT](PathSensitiveBugReport &BR,
413 llvm::raw_ostream &
OS) {
418 const auto CritSectionBegins =
420 llvm::SmallVector<CritSectionMarker, 4> LocksForMutex;
422 CritSectionBegins, std::back_inserter(LocksForMutex),
423 [M](
const auto &Marker) {
return Marker.LockReg == M.LockReg; });
424 if (LocksForMutex.empty())
429 std::reverse(LocksForMutex.begin(), LocksForMutex.end());
433 const auto Position =
434 llvm::find_if(std::as_const(LocksForMutex), [M](
const auto &Marker) {
435 return Marker.LockExpr == M.LockExpr;
437 if (Position == LocksForMutex.end())
442 if (LocksForMutex.size() == 1) {
443 OS <<
"Entering critical section here";
447 const auto IndexOfLock =
448 std::distance(std::as_const(LocksForMutex).begin(), Position);
450 const auto OrdinalOfLock = IndexOfLock + 1;
451 OS <<
"Entering critical section for the " << OrdinalOfLock
452 << llvm::getOrdinalSuffix(OrdinalOfLock) <<
" time here";
456void ento::registerBlockInCriticalSectionChecker(CheckerManager &mgr) {
460bool ento::shouldRegisterBlockInCriticalSectionChecker(
461 const CheckerManager &mgr) {
static const MemRegion * getCXXThisRegion(const CallEvent &Call)
static const MemRegion * skipStdBaseClassRegion(const MemRegion *Reg)
static const MemRegion * getFirstArgRegion(const CallEvent &Call)
static const MemRegion * getCXXDestructorThisRegion(const CallEvent &Call)
static bool isNotDeferLockUniqueLock(const CallEvent &Call)
static const MemRegion * getObjectUnderConstruction(const CallEvent &Call)
static const MemRegion * getMutexRegion(const CallEvent &Call, const ThreadingCallDescription &Desc)
#define REGISTER_LIST_WITH_PROGRAMSTATE(Name, Elem)
Declares an immutable list type NameTy, suitable for placement into the ProgramState.
A (possibly-)qualified type.
RecordDecl * getAsRecordDecl() const
Retrieves the RecordDecl this type refers to.
const BugType & getBugType() const
BugReporterVisitors are used to add custom diagnostics along a path.
bool contains(const CallEvent &Call) const
Represents an abstract call to a function or method along a particular path.
CHECKER * registerChecker(AT &&...Args)
Register a single-part checker (derived from Checker): construct its singleton instance,...
Simple checker classes that implement one frontend (i.e.
bool isConstrainedTrue() const
Return true if the constraint is perfectly constrained to 'true'.
const ProgramStateRef & getState() const
SVal getSVal(const Expr *E) const
Get the value of an arbitrary expression at this node.
std::optional< T > getLocationAs() const &
MemRegion - The root abstract class for all memory regions.
const ExplodedNode * getErrorNode() const
void markInvalid(const void *Tag, const void *Data)
Marks the current report as invalid, meaning that it is probably a false positive and should not be r...
nonloc::ConcreteInt makeIntVal(const IntegerLiteral *integer)
SymbolRef getAsSymbol(bool IncludeBaseRegions=false) const
If this SVal wraps a symbol return that SymbolRef.
const llvm::APSInt * getAsInteger() const
If this SVal is loc::ConcreteInt or nonloc::ConcreteInt, return a pointer to APSInt which is held in ...
bool isWithinStdNamespace(const Decl *D)
Returns true if declaration D is in std namespace or any nested namespace or class scope.
IntrusiveRefCntPtr< const ProgramState > ProgramStateRef
const SymExpr * SymbolRef
@ OS
Indicates that the tracking object is a descendant of a referenced-counted OSObject,...
std::optional< int > tryExpandAsInteger(StringRef Macro, const Preprocessor &PP)
Try to parse the value of a defined preprocessor macro.
std::shared_ptr< PathDiagnosticPiece > PathDiagnosticPieceRef
Top level wrappers for InstallAPI frontend operations.
bool isa(CodeGen::Address addr)
bool operator==(const CallGraphNode::CallRecord &LHS, const CallGraphNode::CallRecord &RHS)
bool operator!=(CanQual< T > x, CanQual< U > y)
U cast(CodeGen::Address addr)
@ Other
Other implicit parameter.