27#include "llvm/ADT/STLExtras.h"
28#include "llvm/ADT/SmallString.h"
29#include "llvm/ADT/StringExtras.h"
40struct CritSectionMarker {
41 const Expr *LockExpr{};
42 const MemRegion *LockReg{};
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 {
60 CallDescription LockFn;
61 CallDescription UnlockFn;
64 CallDescriptionBasedMatcher(CallDescription &&LockFn,
65 CallDescription &&UnlockFn)
66 : LockFn(std::move(LockFn)), UnlockFn(std::move(UnlockFn)) {}
67 [[nodiscard]]
bool matches(
const CallEvent &
Call,
bool IsLock)
const {
69 return LockFn.matches(
Call);
71 return UnlockFn.matches(
Call);
75class FirstArgMutexDescriptor :
public CallDescriptionBasedMatcher {
77 FirstArgMutexDescriptor(CallDescription &&LockFn, CallDescription &&UnlockFn)
78 : CallDescriptionBasedMatcher(std::move(LockFn), std::move(UnlockFn)) {}
80 [[nodiscard]]
const MemRegion *
getRegion(
const CallEvent &
Call,
bool)
const {
81 return Call.getArgSVal(0).getAsRegion();
85class MemberMutexDescriptor :
public CallDescriptionBasedMatcher {
87 MemberMutexDescriptor(CallDescription &&LockFn, CallDescription &&UnlockFn)
88 : CallDescriptionBasedMatcher(std::move(LockFn), std::move(UnlockFn)) {}
90 [[nodiscard]]
const MemRegion *
getRegion(
const CallEvent &
Call,
bool)
const {
95class RAIIMutexDescriptor {
96 mutable const IdentifierInfo *Guard{};
97 mutable bool IdentifierInfoInitialized{};
98 mutable llvm::SmallString<32> GuardName{};
100 void initIdentifierInfo(
const CallEvent &
Call)
const {
101 if (!IdentifierInfoInitialized) {
107 const auto &ASTCtx =
Call.getASTContext();
108 Guard = &ASTCtx.Idents.get(GuardName);
112 template <
typename T>
bool matchesImpl(
const CallEvent &
Call)
const {
113 const T *
C = dyn_cast<T>(&
Call);
116 const IdentifierInfo *II =
123 if constexpr (std::is_same_v<T, CXXConstructorCall>) {
124 if (GuardName ==
"unique_lock" &&
C->getNumArgs() >= 2) {
125 const Expr *SecondArg =
C->getArgExpr(1);
128 RD && RD->
getName() ==
"defer_lock_t" && RD->isInStdNamespace()) {
138 RAIIMutexDescriptor(StringRef GuardName) : GuardName(GuardName) {}
139 [[nodiscard]]
bool matches(
const CallEvent &
Call,
bool IsLock)
const {
140 initIdentifierInfo(
Call);
142 return matchesImpl<CXXConstructorCall>(
Call);
144 return matchesImpl<CXXDestructorCall>(
Call);
146 [[nodiscard]]
const MemRegion *
getRegion(
const CallEvent &
Call,
148 const MemRegion *LockRegion =
nullptr;
150 if (std::optional<SVal>
Object =
Call.getReturnValueUnderConstruction()) {
151 LockRegion =
Object->getAsRegion();
160using MutexDescriptor =
161 std::variant<FirstArgMutexDescriptor, MemberMutexDescriptor,
162 RAIIMutexDescriptor>;
166 const CallDescription OpenFunction{CDM::CLibrary, {
"open"}, 2};
168 const int NonBlockMacroVal;
169 bool Satisfied =
false;
172 SuppressNonBlockingStreams(
SymbolRef StreamSym,
int NonBlockMacroVal)
173 : StreamSym(StreamSym), NonBlockMacroVal(NonBlockMacroVal) {}
175 static void *getTag() {
180 void Profile(llvm::FoldingSetNodeID &ID)
const override {
181 ID.AddPointer(getTag());
185 BugReporterContext &BRC,
186 PathSensitiveBugReport &BR)
override {
190 std::optional<StmtPoint> Point = N->
getLocationAs<StmtPoint>();
194 const auto *CE = Point->getStmtAs<CallExpr>();
195 if (!CE || !OpenFunction.matchesAsWritten(*CE))
208 if ((*FlagVal & NonBlockMacroVal) != 0)
215class BlockInCriticalSectionChecker
216 :
public Checker<check::PostCall, eval::Call> {
218 const std::array<MutexDescriptor, 9> MutexDescriptors{
226 MemberMutexDescriptor(
230 {CDM::CXXMethod, {
"std",
"unlock"}, 0}),
231 FirstArgMutexDescriptor({CDM::CLibrary, {
"pthread_mutex_lock"}, 1},
232 {CDM::CLibrary, {
"pthread_mutex_unlock"}, 1}),
233 FirstArgMutexDescriptor({CDM::CLibrary, {
"mtx_lock"}, 1},
234 {CDM::CLibrary, {
"mtx_unlock"}, 1}),
235 FirstArgMutexDescriptor({CDM::CLibrary, {
"pthread_mutex_trylock"}, 1},
236 {CDM::CLibrary, {
"pthread_mutex_unlock"}, 1}),
237 FirstArgMutexDescriptor({CDM::CLibrary, {
"mtx_trylock"}, 1},
238 {CDM::CLibrary, {
"mtx_unlock"}, 1}),
239 FirstArgMutexDescriptor({CDM::CLibrary, {
"mtx_timedlock"}, 1},
240 {CDM::CLibrary, {
"mtx_unlock"}, 1}),
241 RAIIMutexDescriptor(
"lock_guard"),
242 RAIIMutexDescriptor(
"unique_lock"),
243 RAIIMutexDescriptor(
"scoped_lock")};
245 const CallDescriptionSet BlockingFunctions{{CDM::CLibrary, {
"sleep"}},
246 {CDM::CLibrary, {
"getc"}},
247 {CDM::CLibrary, {
"fgets"}},
248 {CDM::CLibrary, {
"read"}},
249 {CDM::CLibrary, {
"recv"}}};
251 const BugType BlockInCritSectionBugType{
252 this,
"Call to blocking function in critical section",
"Blocking Error"};
254 using O_NONBLOCKValueTy = std::optional<int>;
255 mutable std::optional<O_NONBLOCKValueTy> O_NONBLOCKValue;
257 void reportBlockInCritSection(
const CallEvent &call, CheckerContext &
C)
const;
259 [[nodiscard]]
const NoteTag *createCritSectionNote(CritSectionMarker M,
260 CheckerContext &
C)
const;
262 [[nodiscard]] std::optional<MutexDescriptor>
263 checkDescriptorMatch(
const CallEvent &
Call, CheckerContext &
C,
266 void handleLock(
const MutexDescriptor &Mutex,
const CallEvent &
Call,
269 void handleUnlock(
const MutexDescriptor &Mutex,
const CallEvent &
Call,
270 CheckerContext &
C)
const;
272 [[nodiscard]]
bool isBlockingInCritSection(
const CallEvent &
Call,
273 CheckerContext &
C)
const;
279 void checkPostCall(
const CallEvent &
Call, CheckerContext &
C)
const;
282 bool evalCall(
const CallEvent &
Call, CheckerContext &
C)
const;
289std::optional<MutexDescriptor>
290BlockInCriticalSectionChecker::checkDescriptorMatch(
const CallEvent &
Call,
293 const auto Descriptor =
294 llvm::find_if(MutexDescriptors, [&
Call, IsLock](
auto &&Descriptor) {
296 [&
Call, IsLock](
auto &&DescriptorImpl) {
297 return DescriptorImpl.matches(
Call, IsLock);
301 if (Descriptor != MutexDescriptors.end())
308 const auto *BaseClassRegion = dyn_cast<CXXBaseObjectRegion>(Reg);
311 Reg = BaseClassRegion->getSuperRegion();
317 const MutexDescriptor &Descriptor,
326void BlockInCriticalSectionChecker::handleLock(
327 const MutexDescriptor &LockDescriptor,
const CallEvent &
Call,
329 const MemRegion *MutexRegion =
334 const CritSectionMarker MarkToAdd{
Call.getOriginExpr(), MutexRegion};
336 State->add<ActiveCritSections>(MarkToAdd);
337 C.addTransition(StateWithLockEvent, createCritSectionNote(MarkToAdd,
C));
340void BlockInCriticalSectionChecker::handleUnlock(
341 const MutexDescriptor &UnlockDescriptor,
const CallEvent &
Call,
342 CheckerContext &
C)
const {
343 const MemRegion *MutexRegion =
349 const auto ActiveSections = State->get<ActiveCritSections>();
350 const auto MostRecentLock =
351 llvm::find_if(ActiveSections, [MutexRegion](
auto &&Marker) {
352 return Marker.LockReg == MutexRegion;
354 if (MostRecentLock == ActiveSections.end())
358 auto &Factory = State->get_context<ActiveCritSections>();
359 llvm::ImmutableList<CritSectionMarker> NewList = Factory.getEmptyList();
360 for (
auto It = ActiveSections.begin(), End = ActiveSections.end(); It != End;
362 if (It != MostRecentLock)
363 NewList = Factory.add(*It, NewList);
366 State = State->set<ActiveCritSections>(NewList);
367 C.addTransition(State);
370bool BlockInCriticalSectionChecker::isBlockingInCritSection(
371 const CallEvent &
Call, CheckerContext &
C)
const {
373 !
C.getState()->get<ActiveCritSections>().isEmpty();
376void BlockInCriticalSectionChecker::checkPostCall(
const CallEvent &
Call,
377 CheckerContext &
C)
const {
378 if (isBlockingInCritSection(
Call,
C)) {
379 reportBlockInCritSection(
Call,
C);
383 if (std::optional<MutexDescriptor> LockDesc =
384 checkDescriptorMatch(
Call,
C,
true)) {
385 if (!std::holds_alternative<RAIIMutexDescriptor>(*LockDesc))
386 handleLock(*LockDesc,
Call,
C,
C.getState());
389 if (std::optional<MutexDescriptor> UnlockDesc =
390 checkDescriptorMatch(
Call,
C,
false)) {
391 handleUnlock(*UnlockDesc,
Call,
C);
395bool BlockInCriticalSectionChecker::evalCall(
const CallEvent &
Call,
396 CheckerContext &
C)
const {
397 if (std::optional<MutexDescriptor> LockDesc =
398 checkDescriptorMatch(
Call,
C,
true)) {
399 if (std::holds_alternative<RAIIMutexDescriptor>(*LockDesc)) {
403 if (
const auto *Ctor = dyn_cast<AnyCXXConstructorCall>(&
Call)) {
404 const MemRegion *ObjRegion = Ctor->getCXXThisVal().getAsRegion();
405 State = State->invalidateRegions(ObjRegion,
C.getCFGElementRef(),
406 C.blockCount(),
C.getStackFrame(),
409 handleLock(*LockDesc,
Call,
C, State);
416void BlockInCriticalSectionChecker::reportBlockInCritSection(
417 const CallEvent &
Call, CheckerContext &
C)
const {
418 ExplodedNode *ErrNode =
C.generateNonFatalErrorNode(
C.getState());
423 llvm::raw_string_ostream os(msg);
424 os <<
"Call to blocking function '" <<
Call.getCalleeIdentifier()->getName()
425 <<
"' inside of critical section";
426 auto R = std::make_unique<PathSensitiveBugReport>(BlockInCritSectionBugType,
432 StringRef FuncName =
Call.getCalleeIdentifier()->getName();
433 if (FuncName ==
"read" || FuncName ==
"recv") {
434 SVal SV =
Call.getArgSVal(0);
435 SValBuilder &SVB =
C.getSValBuilder();
437 ConditionTruthVal CTV =
438 state->areEqual(SV, SVB.
makeIntVal(-1,
C.getASTContext().IntTy));
443 if (!O_NONBLOCKValue)
445 "O_NONBLOCK",
C.getBugReporter().getPreprocessor());
446 if (*O_NONBLOCKValue)
447 R->addVisitor<SuppressNonBlockingStreams>(SR, **O_NONBLOCKValue);
450 R->addRange(
Call.getSourceRange());
451 R->markInteresting(
Call.getReturnValue());
452 C.emitReport(std::move(R));
456BlockInCriticalSectionChecker::createCritSectionNote(CritSectionMarker M,
457 CheckerContext &
C)
const {
458 const BugType *BT = &this->BlockInCritSectionBugType;
459 return C.getNoteTag([M, BT](PathSensitiveBugReport &BR,
460 llvm::raw_ostream &
OS) {
465 const auto CritSectionBegins =
467 llvm::SmallVector<CritSectionMarker, 4> LocksForMutex;
469 CritSectionBegins, std::back_inserter(LocksForMutex),
470 [M](
const auto &Marker) {
return Marker.LockReg == M.LockReg; });
471 if (LocksForMutex.empty())
476 std::reverse(LocksForMutex.begin(), LocksForMutex.end());
480 const auto Position =
481 llvm::find_if(std::as_const(LocksForMutex), [M](
const auto &Marker) {
482 return Marker.LockExpr == M.LockExpr;
484 if (Position == LocksForMutex.end())
489 if (LocksForMutex.size() == 1) {
490 OS <<
"Entering critical section here";
494 const auto IndexOfLock =
495 std::distance(std::as_const(LocksForMutex).begin(), Position);
497 const auto OrdinalOfLock = IndexOfLock + 1;
498 OS <<
"Entering critical section for the " << OrdinalOfLock
499 << llvm::getOrdinalSuffix(OrdinalOfLock) <<
" time here";
503void ento::registerBlockInCriticalSectionChecker(CheckerManager &mgr) {
507bool ento::shouldRegisterBlockInCriticalSectionChecker(
508 const CheckerManager &mgr) {
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.
StringRef getName() const
Get the name of identifier for this declaration as a StringRef.
QualType getNonReferenceType() const
If Type is a reference type (e.g., const int&), returns the type that the reference refers to ("const...
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
bool matches(const til::SExpr *E1, const til::SExpr *E2)
The JSON file list parser is used to communicate input to InstallAPI.
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.