clang: lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp Source File (original) (raw)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
27#include "llvm/ADT/STLExtras.h"
28#include "llvm/ADT/SmallString.h"
29#include "llvm/ADT/StringExtras.h"
30
31#include
32#include
33#include
34
35using namespace clang;
36using namespace ento;
37
38namespace {
39
40struct CritSectionMarker {
41 const Expr *LockExpr{};
42 const MemRegion *LockReg{};
43
44 void Profile(llvm::FoldingSetNodeID &ID) const {
45 ID.Add(LockExpr);
46 ID.Add(LockReg);
47 }
48
49 [[nodiscard]] constexpr bool
50 operator==(const CritSectionMarker &Other) const noexcept {
51 return LockExpr == Other.LockExpr && LockReg == Other.LockReg;
52 }
53 [[nodiscard]] constexpr bool
54 operator!=(const CritSectionMarker &Other) const noexcept {
55 return !(*this == Other);
56 }
57};
58
59class CallDescriptionBasedMatcher {
60 CallDescription LockFn;
61 CallDescription UnlockFn;
62
63public:
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 {
68 if (IsLock) {
69 return LockFn.matches(Call);
70 }
71 return UnlockFn.matches(Call);
72 }
73};
74
75class FirstArgMutexDescriptor : public CallDescriptionBasedMatcher {
76public:
77 FirstArgMutexDescriptor(CallDescription &&LockFn, CallDescription &&UnlockFn)
78 : CallDescriptionBasedMatcher(std::move(LockFn), std::move(UnlockFn)) {}
79
80 [[nodiscard]] const MemRegion *getRegion(const CallEvent &Call, bool) const {
81 return Call.getArgSVal(0).getAsRegion();
82 }
83};
84
85class MemberMutexDescriptor : public CallDescriptionBasedMatcher {
86public:
87 MemberMutexDescriptor(CallDescription &&LockFn, CallDescription &&UnlockFn)
88 : CallDescriptionBasedMatcher(std::move(LockFn), std::move(UnlockFn)) {}
89
90 [[nodiscard]] const MemRegion *getRegion(const CallEvent &Call, bool) const {
92 }
93};
94
95class RAIIMutexDescriptor {
96 mutable const IdentifierInfo *Guard{};
97 mutable bool IdentifierInfoInitialized{};
98 mutable llvm::SmallString<32> GuardName{};
99
100 void initIdentifierInfo(const CallEvent &Call) const {
101 if (!IdentifierInfoInitialized) {
102
103
104
105
106
107 const auto &ASTCtx = Call.getASTContext();
108 Guard = &ASTCtx.Idents.get(GuardName);
109 }
110 }
111
112 template bool matchesImpl(const CallEvent &Call) const {
113 const T *C = dyn_cast(&Call);
114 if ()
115 return false;
116 const IdentifierInfo *II =
118 if (II != Guard)
119 return false;
120
121
122
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()) {
129 return false;
130 }
131 }
132 }
133
134 return true;
135 }
136
137public:
138 RAIIMutexDescriptor(StringRef GuardName) : GuardName(GuardName) {}
139 [[nodiscard]] bool matches(const CallEvent &Call, bool IsLock) const {
140 initIdentifierInfo(Call);
141 if (IsLock) {
142 return matchesImpl(Call);
143 }
144 return matchesImpl(Call);
145 }
146 [[nodiscard]] const MemRegion *getRegion(const CallEvent &Call,
147 bool IsLock) const {
148 const MemRegion *LockRegion = nullptr;
149 if (IsLock) {
150 if (std::optional Object = Call.getReturnValueUnderConstruction()) {
151 LockRegion = Object->getAsRegion();
152 }
153 } else {
155 }
156 return LockRegion;
157 }
158};
159
160using MutexDescriptor =
161 std::variant<FirstArgMutexDescriptor, MemberMutexDescriptor,
162 RAIIMutexDescriptor>;
163
165private:
166 const CallDescription OpenFunction{CDM::CLibrary, {"open"}, 2};
168 const int NonBlockMacroVal;
169 bool Satisfied = false;
170
171public:
172 SuppressNonBlockingStreams(SymbolRef StreamSym, int NonBlockMacroVal)
173 : StreamSym(StreamSym), NonBlockMacroVal(NonBlockMacroVal) {}
174
175 static void *getTag() {
176 static bool Tag;
177 return &Tag;
178 }
179
180 void Profile(llvm::FoldingSetNodeID &ID) const override {
181 ID.AddPointer(getTag());
182 }
183
185 BugReporterContext &BRC,
186 PathSensitiveBugReport &BR) override {
187 if (Satisfied)
188 return nullptr;
189
190 std::optional Point = N->getLocationAs();
191 if (!Point)
192 return nullptr;
193
194 const auto *CE = Point->getStmtAs();
195 if (!CE || !OpenFunction.matchesAsWritten(*CE))
196 return nullptr;
197
199 return nullptr;
200
201 Satisfied = true;
202
203
205 if (!FlagVal)
206 return nullptr;
207
208 if ((*FlagVal & NonBlockMacroVal) != 0)
210
211 return nullptr;
212 }
213};
214
215class BlockInCriticalSectionChecker : public Checkercheck::PostCall {
216private:
217 const std::array<MutexDescriptor, 8> MutexDescriptors{
218
219
220
221
222
223
224
225 MemberMutexDescriptor(
226 {CDM::CXXMethod,
227 {"std", "lock"},
228 0},
229 {CDM::CXXMethod, {"std", "unlock"}, 0}),
230 FirstArgMutexDescriptor({CDM::CLibrary, {"pthread_mutex_lock"}, 1},
231 {CDM::CLibrary, {"pthread_mutex_unlock"}, 1}),
232 FirstArgMutexDescriptor({CDM::CLibrary, {"mtx_lock"}, 1},
233 {CDM::CLibrary, {"mtx_unlock"}, 1}),
234 FirstArgMutexDescriptor({CDM::CLibrary, {"pthread_mutex_trylock"}, 1},
235 {CDM::CLibrary, {"pthread_mutex_unlock"}, 1}),
236 FirstArgMutexDescriptor({CDM::CLibrary, {"mtx_trylock"}, 1},
237 {CDM::CLibrary, {"mtx_unlock"}, 1}),
238 FirstArgMutexDescriptor({CDM::CLibrary, {"mtx_timedlock"}, 1},
239 {CDM::CLibrary, {"mtx_unlock"}, 1}),
240 RAIIMutexDescriptor("lock_guard"),
241 RAIIMutexDescriptor("unique_lock")};
242
243 const CallDescriptionSet BlockingFunctions{{CDM::CLibrary, {"sleep"}},
244 {CDM::CLibrary, {"getc"}},
245 {CDM::CLibrary, {"fgets"}},
246 {CDM::CLibrary, {"read"}},
247 {CDM::CLibrary, {"recv"}}};
248
249 const BugType BlockInCritSectionBugType{
250 this, "Call to blocking function in critical section", "Blocking Error"};
251
252 using O_NONBLOCKValueTy = std::optional;
253 mutable std::optional<O_NONBLOCKValueTy> O_NONBLOCKValue;
254
255 void reportBlockInCritSection(const CallEvent &call, CheckerContext &C) const;
256
257 [[nodiscard]] const NoteTag *createCritSectionNote(CritSectionMarker M,
258 CheckerContext &C) const;
259
260 [[nodiscard]] std::optional
261 checkDescriptorMatch(const CallEvent &Call, CheckerContext &C,
262 bool IsLock) const;
263
264 void handleLock(const MutexDescriptor &Mutex, const CallEvent &Call,
265 CheckerContext &C) const;
266
267 void handleUnlock(const MutexDescriptor &Mutex, const CallEvent &Call,
268 CheckerContext &C) const;
269
270 [[nodiscard]] bool isBlockingInCritSection(const CallEvent &Call,
271 CheckerContext &C) const;
272
273public:
274
275
276
277 void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
278};
279
280}
281
283
284
285
286
287
288template <>
289struct std::iterator_traits<llvm::ImmutableList::iterator> {
295};
296
297std::optional
298BlockInCriticalSectionChecker::checkDescriptorMatch(const CallEvent &Call,
300 bool IsLock) const {
301 const auto Descriptor =
302 llvm::find_if(MutexDescriptors, [&Call, IsLock](auto &&Descriptor) {
303 return std::visit(
304 [&Call, IsLock](auto &&DescriptorImpl) {
305 return DescriptorImpl.matches(Call, IsLock);
306 },
307 Descriptor);
308 });
309 if (Descriptor != MutexDescriptors.end())
310 return *Descriptor;
311 return std::nullopt;
312}
313
315 while (Reg) {
316 const auto *BaseClassRegion = dyn_cast(Reg);
318 break;
319 Reg = BaseClassRegion->getSuperRegion();
320 }
321 return Reg;
322}
323
325 const MutexDescriptor &Descriptor,
326 bool IsLock) {
327 return std::visit(
328 [&Call, IsLock](auto &Descr) -> const MemRegion * {
330 },
331 Descriptor);
332}
333
334void BlockInCriticalSectionChecker::handleLock(
335 const MutexDescriptor &LockDescriptor, const CallEvent &Call,
336 CheckerContext &C) const {
337 const MemRegion *MutexRegion =
338 getRegion(Call, LockDescriptor, true);
339 if (!MutexRegion)
340 return;
341
342 const CritSectionMarker MarkToAdd{Call.getOriginExpr(), MutexRegion};
344 C.getState()->add(MarkToAdd);
345 C.addTransition(StateWithLockEvent, createCritSectionNote(MarkToAdd, C));
346}
347
348void BlockInCriticalSectionChecker::handleUnlock(
349 const MutexDescriptor &UnlockDescriptor, const CallEvent &Call,
350 CheckerContext &C) const {
351 const MemRegion *MutexRegion =
352 getRegion(Call, UnlockDescriptor, false);
353 if (!MutexRegion)
354 return;
355
357 const auto ActiveSections = State->get();
358 const auto MostRecentLock =
359 llvm::find_if(ActiveSections, [MutexRegion](auto &&Marker) {
360 return Marker.LockReg == MutexRegion;
361 });
362 if (MostRecentLock == ActiveSections.end())
363 return;
364
365
366 auto &Factory = State->get_context();
367 llvm::ImmutableList NewList = Factory.getEmptyList();
368 for (auto It = ActiveSections.begin(), End = ActiveSections.end(); It != End;
369 ++It) {
370 if (It != MostRecentLock)
371 NewList = Factory.add(*It, NewList);
372 }
373
374 State = State->set(NewList);
375 C.addTransition(State);
376}
377
378bool BlockInCriticalSectionChecker::isBlockingInCritSection(
379 const CallEvent &Call, CheckerContext &C) const {
381 .getState()->get().isEmpty();
382}
383
384void BlockInCriticalSectionChecker::checkPostCall(const CallEvent &Call,
385 CheckerContext &C) const {
386 if (isBlockingInCritSection(Call, C)) {
387 reportBlockInCritSection(Call, C);
388 } else if (std::optional LockDesc =
389 checkDescriptorMatch(Call, C, true)) {
390 handleLock(*LockDesc, Call, C);
391 } else if (std::optional UnlockDesc =
392 checkDescriptorMatch(Call, C, false)) {
393 handleUnlock(*UnlockDesc, Call, C);
394 }
395}
396
397void BlockInCriticalSectionChecker::reportBlockInCritSection(
398 const CallEvent &Call, CheckerContext &C) const {
399 ExplodedNode *ErrNode = C.generateNonFatalErrorNode(C.getState());
400 if (!ErrNode)
401 return;
402
403 std::string msg;
404 llvm::raw_string_ostream os(msg);
405 os << "Call to blocking function '" << Call.getCalleeIdentifier()->getName()
406 << "' inside of critical section";
407 auto R = std::make_unique(BlockInCritSectionBugType,
408 os.str(), ErrNode);
409
410
411
412
413 StringRef FuncName = Call.getCalleeIdentifier()->getName();
414 if (FuncName == "read" || FuncName == "recv") {
415 SVal SV = Call.getArgSVal(0);
416 SValBuilder &SVB = C.getSValBuilder();
418 ConditionTruthVal CTV =
419 state->areEqual(SV, SVB.makeIntVal(-1, C.getASTContext().IntTy));
421 return;
422
424 if (!O_NONBLOCKValue)
426 "O_NONBLOCK", C.getBugReporter().getPreprocessor());
427 if (*O_NONBLOCKValue)
428 R->addVisitor(SR, **O_NONBLOCKValue);
429 }
430 }
431 R->addRange(Call.getSourceRange());
432 R->markInteresting(Call.getReturnValue());
433 C.emitReport(std::move(R));
434}
435
436const NoteTag *
437BlockInCriticalSectionChecker::createCritSectionNote(CritSectionMarker M,
438 CheckerContext &C) const {
439 const BugType *BT = &this->BlockInCritSectionBugType;
440 return C.getNoteTag([M, BT](PathSensitiveBugReport &BR,
441 llvm::raw_ostream &OS) {
443 return;
444
445
446 const auto CritSectionBegins =
448 llvm::SmallVector<CritSectionMarker, 4> LocksForMutex;
449 llvm::copy_if(
450 CritSectionBegins, std::back_inserter(LocksForMutex),
451 [M](const auto &Marker) { return Marker.LockReg == M.LockReg; });
452 if (LocksForMutex.empty())
453 return;
454
455
456
457 std::reverse(LocksForMutex.begin(), LocksForMutex.end());
458
459
460
461 const auto Position =
462 llvm::find_if(std::as_const(LocksForMutex), [M](const auto &Marker) {
463 return Marker.LockExpr == M.LockExpr;
464 });
465 if (Position == LocksForMutex.end())
466 return;
467
468
469
470 if (LocksForMutex.size() == 1) {
471 OS << "Entering critical section here";
472 return;
473 }
474
475 const auto IndexOfLock =
476 std::distance(std::as_const(LocksForMutex).begin(), Position);
477
478 const auto OrdinalOfLock = IndexOfLock + 1;
479 OS << "Entering critical section for the " << OrdinalOfLock
480 << llvm::getOrdinalSuffix(OrdinalOfLock) << " time here";
481 });
482}
483
484void ento::registerBlockInCriticalSectionChecker(CheckerManager &mgr) {
486}
487
488bool ento::shouldRegisterBlockInCriticalSectionChecker(
489 const CheckerManager &mgr) {
490 return true;
491}
static const MemRegion * skipStdBaseClassRegion(const MemRegion *Reg)
Definition BlockInCriticalSectionChecker.cpp:314
static const MemRegion * getRegion(const CallEvent &Call, const MutexDescriptor &Descriptor, bool IsLock)
Definition BlockInCriticalSectionChecker.cpp:324
#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 Stmt *S) 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)
const FunctionProtoType * T
bool operator!=(CanQual< T > x, CanQual< U > y)
U cast(CodeGen::Address addr)
@ Other
Other implicit parameter.
Diagnostic wrappers for TextAPI types for error reporting.
CritSectionMarker & reference
Definition BlockInCriticalSectionChecker.cpp:293
CritSectionMarker * pointer
Definition BlockInCriticalSectionChecker.cpp:294
std::ptrdiff_t difference_type
Definition BlockInCriticalSectionChecker.cpp:292
CritSectionMarker value_type
Definition BlockInCriticalSectionChecker.cpp:291
std::forward_iterator_tag iterator_category
Definition BlockInCriticalSectionChecker.cpp:290