1//===-- BlockInCriticalSectionChecker.cpp -----------------------*- C++ -*-===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// Defines a checker for blocks in critical sections. This checker should find
10// the calls to blocking functions (for example: sleep, getc, fgets, read,
11// recv etc.) inside a critical section. When sleep(x) is called while a mutex
12// is held, other threades cannot lock the same mutex. This might take some
13// time, leading to bad performance or even deadlock.
14//
15//===----------------------------------------------------------------------===//
16
17#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
18#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
19#include "clang/StaticAnalyzer/Core/Checker.h"
20#include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h"
21#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
22#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
23#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h"
24#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState_Fwd.h"
25#include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h"
26#include "llvm/ADT/STLExtras.h"
27#include "llvm/ADT/SmallString.h"
28#include "llvm/ADT/StringExtras.h"
29
30#include <iterator>
31#include <utility>
32#include <variant>
33
34using namespace clang;
35using namespace ento;
36
37namespace {
38
39struct CritSectionMarker {
40 const Expr *LockExpr{};
41 const MemRegion *LockReg{};
42
43 void Profile(llvm::FoldingSetNodeID &ID) const {
44 ID.Add(x: LockExpr);
45 ID.Add(x: LockReg);
46 }
47
48 [[nodiscard]] constexpr bool
49 operator==(const CritSectionMarker &Other) const noexcept {
50 return LockExpr == Other.LockExpr && LockReg == Other.LockReg;
51 }
52 [[nodiscard]] constexpr bool
53 operator!=(const CritSectionMarker &Other) const noexcept {
54 return !(*this == Other);
55 }
56};
57
58class CallDescriptionBasedMatcher {
59 CallDescription LockFn;
60 CallDescription UnlockFn;
61
62public:
63 CallDescriptionBasedMatcher(CallDescription &&LockFn,
64 CallDescription &&UnlockFn)
65 : LockFn(std::move(LockFn)), UnlockFn(std::move(UnlockFn)) {}
66 [[nodiscard]] bool matches(const CallEvent &Call, bool IsLock) const {
67 if (IsLock) {
68 return LockFn.matches(Call);
69 }
70 return UnlockFn.matches(Call);
71 }
72};
73
74class FirstArgMutexDescriptor : public CallDescriptionBasedMatcher {
75public:
76 FirstArgMutexDescriptor(CallDescription &&LockFn, CallDescription &&UnlockFn)
77 : CallDescriptionBasedMatcher(std::move(LockFn), std::move(UnlockFn)) {}
78
79 [[nodiscard]] const MemRegion *getRegion(const CallEvent &Call, bool) const {
80 return Call.getArgSVal(Index: 0).getAsRegion();
81 }
82};
83
84class MemberMutexDescriptor : public CallDescriptionBasedMatcher {
85public:
86 MemberMutexDescriptor(CallDescription &&LockFn, CallDescription &&UnlockFn)
87 : CallDescriptionBasedMatcher(std::move(LockFn), std::move(UnlockFn)) {}
88
89 [[nodiscard]] const MemRegion *getRegion(const CallEvent &Call, bool) const {
90 return cast<CXXMemberCall>(Val: Call).getCXXThisVal().getAsRegion();
91 }
92};
93
94class RAIIMutexDescriptor {
95 mutable const IdentifierInfo *Guard{};
96 mutable bool IdentifierInfoInitialized{};
97 mutable llvm::SmallString<32> GuardName{};
98
99 void initIdentifierInfo(const CallEvent &Call) const {
100 if (!IdentifierInfoInitialized) {
101 // In case of checking C code, or when the corresponding headers are not
102 // included, we might end up query the identifier table every time when
103 // this function is called instead of early returning it. To avoid this, a
104 // bool variable (IdentifierInfoInitialized) is used and the function will
105 // be run only once.
106 Guard = &Call.getCalleeAnalysisDeclContext()->getASTContext().Idents.get(
107 Name: GuardName);
108 IdentifierInfoInitialized = true;
109 }
110 }
111
112 template <typename T> bool matchesImpl(const CallEvent &Call) const {
113 const T *C = dyn_cast<T>(&Call);
114 if (!C)
115 return false;
116 const IdentifierInfo *II =
117 cast<CXXRecordDecl>(C->getDecl()->getParent())->getIdentifier();
118 return II == Guard;
119 }
120
121public:
122 RAIIMutexDescriptor(StringRef GuardName) : GuardName(GuardName) {}
123 [[nodiscard]] bool matches(const CallEvent &Call, bool IsLock) const {
124 initIdentifierInfo(Call);
125 if (IsLock) {
126 return matchesImpl<CXXConstructorCall>(Call);
127 }
128 return matchesImpl<CXXDestructorCall>(Call);
129 }
130 [[nodiscard]] const MemRegion *getRegion(const CallEvent &Call,
131 bool IsLock) const {
132 const MemRegion *LockRegion = nullptr;
133 if (IsLock) {
134 if (std::optional<SVal> Object = Call.getReturnValueUnderConstruction()) {
135 LockRegion = Object->getAsRegion();
136 }
137 } else {
138 LockRegion = cast<CXXDestructorCall>(Val: Call).getCXXThisVal().getAsRegion();
139 }
140 return LockRegion;
141 }
142};
143
144using MutexDescriptor =
145 std::variant<FirstArgMutexDescriptor, MemberMutexDescriptor,
146 RAIIMutexDescriptor>;
147
148class BlockInCriticalSectionChecker : public Checker<check::PostCall> {
149private:
150 const std::array<MutexDescriptor, 8> MutexDescriptors{
151 MemberMutexDescriptor(
152 CallDescription(/*QualifiedName=*/{"std", "mutex", "lock"},
153 /*RequiredArgs=*/0),
154 CallDescription({"std", "mutex", "unlock"}, 0)),
155 FirstArgMutexDescriptor(CallDescription({"pthread_mutex_lock"}, 1),
156 CallDescription({"pthread_mutex_unlock"}, 1)),
157 FirstArgMutexDescriptor(CallDescription({"mtx_lock"}, 1),
158 CallDescription({"mtx_unlock"}, 1)),
159 FirstArgMutexDescriptor(CallDescription({"pthread_mutex_trylock"}, 1),
160 CallDescription({"pthread_mutex_unlock"}, 1)),
161 FirstArgMutexDescriptor(CallDescription({"mtx_trylock"}, 1),
162 CallDescription({"mtx_unlock"}, 1)),
163 FirstArgMutexDescriptor(CallDescription({"mtx_timedlock"}, 1),
164 CallDescription({"mtx_unlock"}, 1)),
165 RAIIMutexDescriptor("lock_guard"),
166 RAIIMutexDescriptor("unique_lock")};
167
168 const std::array<CallDescription, 5> BlockingFunctions{
169 ArrayRef{StringRef{"sleep"}}, ArrayRef{StringRef{"getc"}},
170 ArrayRef{StringRef{"fgets"}}, ArrayRef{StringRef{"read"}},
171 ArrayRef{StringRef{"recv"}}};
172
173 const BugType BlockInCritSectionBugType{
174 this, "Call to blocking function in critical section", "Blocking Error"};
175
176 void reportBlockInCritSection(const CallEvent &call, CheckerContext &C) const;
177
178 [[nodiscard]] const NoteTag *createCritSectionNote(CritSectionMarker M,
179 CheckerContext &C) const;
180
181 [[nodiscard]] std::optional<MutexDescriptor>
182 checkDescriptorMatch(const CallEvent &Call, CheckerContext &C,
183 bool IsLock) const;
184
185 void handleLock(const MutexDescriptor &Mutex, const CallEvent &Call,
186 CheckerContext &C) const;
187
188 void handleUnlock(const MutexDescriptor &Mutex, const CallEvent &Call,
189 CheckerContext &C) const;
190
191 [[nodiscard]] bool isBlockingInCritSection(const CallEvent &Call,
192 CheckerContext &C) const;
193
194public:
195 /// Process unlock.
196 /// Process lock.
197 /// Process blocking functions (sleep, getc, fgets, read, recv)
198 void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
199};
200
201} // end anonymous namespace
202
203REGISTER_LIST_WITH_PROGRAMSTATE(ActiveCritSections, CritSectionMarker)
204
205namespace std {
206// Iterator traits for ImmutableList data structure
207// that enable the use of STL algorithms.
208// TODO: Move these to llvm::ImmutableList when overhauling immutable data
209// structures for proper iterator concept support.
210template <>
211struct iterator_traits<
212 typename llvm::ImmutableList<CritSectionMarker>::iterator> {
213 using iterator_category = std::forward_iterator_tag;
214 using value_type = CritSectionMarker;
215 using difference_type = std::ptrdiff_t;
216 using reference = CritSectionMarker &;
217 using pointer = CritSectionMarker *;
218};
219} // namespace std
220
221std::optional<MutexDescriptor>
222BlockInCriticalSectionChecker::checkDescriptorMatch(const CallEvent &Call,
223 CheckerContext &C,
224 bool IsLock) const {
225 const auto Descriptor =
226 llvm::find_if(Range: MutexDescriptors, P: [&Call, IsLock](auto &&Descriptor) {
227 return std::visit(
228 [&Call, IsLock](auto &&DescriptorImpl) {
229 return DescriptorImpl.matches(Call, IsLock);
230 },
231 Descriptor);
232 });
233 if (Descriptor != MutexDescriptors.end())
234 return *Descriptor;
235 return std::nullopt;
236}
237
238static const MemRegion *getRegion(const CallEvent &Call,
239 const MutexDescriptor &Descriptor,
240 bool IsLock) {
241 return std::visit(
242 visitor: [&Call, IsLock](auto &&Descriptor) {
243 return Descriptor.getRegion(Call, IsLock);
244 },
245 variants: Descriptor);
246}
247
248void BlockInCriticalSectionChecker::handleLock(
249 const MutexDescriptor &LockDescriptor, const CallEvent &Call,
250 CheckerContext &C) const {
251 const MemRegion *MutexRegion =
252 getRegion(Call, Descriptor: LockDescriptor, /*IsLock=*/true);
253 if (!MutexRegion)
254 return;
255
256 const CritSectionMarker MarkToAdd{.LockExpr: Call.getOriginExpr(), .LockReg: MutexRegion};
257 ProgramStateRef StateWithLockEvent =
258 C.getState()->add<ActiveCritSections>(K: MarkToAdd);
259 C.addTransition(State: StateWithLockEvent, Tag: createCritSectionNote(M: MarkToAdd, C));
260}
261
262void BlockInCriticalSectionChecker::handleUnlock(
263 const MutexDescriptor &UnlockDescriptor, const CallEvent &Call,
264 CheckerContext &C) const {
265 const MemRegion *MutexRegion =
266 getRegion(Call, Descriptor: UnlockDescriptor, /*IsLock=*/false);
267 if (!MutexRegion)
268 return;
269
270 ProgramStateRef State = C.getState();
271 const auto ActiveSections = State->get<ActiveCritSections>();
272 const auto MostRecentLock =
273 llvm::find_if(Range: ActiveSections, P: [MutexRegion](auto &&Marker) {
274 return Marker.LockReg == MutexRegion;
275 });
276 if (MostRecentLock == ActiveSections.end())
277 return;
278
279 // Build a new ImmutableList without this element.
280 auto &Factory = State->get_context<ActiveCritSections>();
281 llvm::ImmutableList<CritSectionMarker> NewList = Factory.getEmptyList();
282 for (auto It = ActiveSections.begin(), End = ActiveSections.end(); It != End;
283 ++It) {
284 if (It != MostRecentLock)
285 NewList = Factory.add(Data: *It, L: NewList);
286 }
287
288 State = State->set<ActiveCritSections>(NewList);
289 C.addTransition(State);
290}
291
292bool BlockInCriticalSectionChecker::isBlockingInCritSection(
293 const CallEvent &Call, CheckerContext &C) const {
294 return llvm::any_of(Range: BlockingFunctions,
295 P: [&Call](auto &&Fn) { return Fn.matches(Call); }) &&
296 !C.getState()->get<ActiveCritSections>().isEmpty();
297}
298
299void BlockInCriticalSectionChecker::checkPostCall(const CallEvent &Call,
300 CheckerContext &C) const {
301 if (isBlockingInCritSection(Call, C)) {
302 reportBlockInCritSection(call: Call, C);
303 } else if (std::optional<MutexDescriptor> LockDesc =
304 checkDescriptorMatch(Call, C, /*IsLock=*/true)) {
305 handleLock(LockDescriptor: *LockDesc, Call, C);
306 } else if (std::optional<MutexDescriptor> UnlockDesc =
307 checkDescriptorMatch(Call, C, /*IsLock=*/false)) {
308 handleUnlock(UnlockDescriptor: *UnlockDesc, Call, C);
309 }
310}
311
312void BlockInCriticalSectionChecker::reportBlockInCritSection(
313 const CallEvent &Call, CheckerContext &C) const {
314 ExplodedNode *ErrNode = C.generateNonFatalErrorNode(State: C.getState());
315 if (!ErrNode)
316 return;
317
318 std::string msg;
319 llvm::raw_string_ostream os(msg);
320 os << "Call to blocking function '" << Call.getCalleeIdentifier()->getName()
321 << "' inside of critical section";
322 auto R = std::make_unique<PathSensitiveBugReport>(args: BlockInCritSectionBugType,
323 args&: os.str(), args&: ErrNode);
324 R->addRange(R: Call.getSourceRange());
325 R->markInteresting(V: Call.getReturnValue());
326 C.emitReport(R: std::move(R));
327}
328
329const NoteTag *
330BlockInCriticalSectionChecker::createCritSectionNote(CritSectionMarker M,
331 CheckerContext &C) const {
332 const BugType *BT = &this->BlockInCritSectionBugType;
333 return C.getNoteTag(Cb: [M, BT](PathSensitiveBugReport &BR,
334 llvm::raw_ostream &OS) {
335 if (&BR.getBugType() != BT)
336 return;
337
338 // Get the lock events for the mutex of the current line's lock event.
339 const auto CritSectionBegins =
340 BR.getErrorNode()->getState()->get<ActiveCritSections>();
341 llvm::SmallVector<CritSectionMarker, 4> LocksForMutex;
342 llvm::copy_if(
343 Range: CritSectionBegins, Out: std::back_inserter(x&: LocksForMutex),
344 P: [M](const auto &Marker) { return Marker.LockReg == M.LockReg; });
345 if (LocksForMutex.empty())
346 return;
347
348 // As the ImmutableList builds the locks by prepending them, we
349 // reverse the list to get the correct order.
350 std::reverse(first: LocksForMutex.begin(), last: LocksForMutex.end());
351
352 // Find the index of the lock expression in the list of all locks for a
353 // given mutex (in acquisition order).
354 const auto Position =
355 llvm::find_if(Range: std::as_const(t&: LocksForMutex), P: [M](const auto &Marker) {
356 return Marker.LockExpr == M.LockExpr;
357 });
358 if (Position == LocksForMutex.end())
359 return;
360
361 // If there is only one lock event, we don't need to specify how many times
362 // the critical section was entered.
363 if (LocksForMutex.size() == 1) {
364 OS << "Entering critical section here";
365 return;
366 }
367
368 const auto IndexOfLock =
369 std::distance(first: std::as_const(t&: LocksForMutex).begin(), last: Position);
370
371 const auto OrdinalOfLock = IndexOfLock + 1;
372 OS << "Entering critical section for the " << OrdinalOfLock
373 << llvm::getOrdinalSuffix(Val: OrdinalOfLock) << " time here";
374 });
375}
376
377void ento::registerBlockInCriticalSectionChecker(CheckerManager &mgr) {
378 mgr.registerChecker<BlockInCriticalSectionChecker>();
379}
380
381bool ento::shouldRegisterBlockInCriticalSectionChecker(
382 const CheckerManager &mgr) {
383 return true;
384}
385

source code of clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp