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{};
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 {
62
63public:
66 : LockFn(std::move(LockFn)), UnlockFn(std::move(UnlockFn)) {}
68 if (IsLock) {
70 }
72 }
73};
74
75class FirstArgMutexDescriptor : public CallDescriptionBasedMatcher {
76public:
78 : CallDescriptionBasedMatcher(std::move(LockFn), std::move(UnlockFn)) {}
79
81 return Call.getArgSVal(0).getAsRegion();
82 }
83};
84
85class MemberMutexDescriptor : public CallDescriptionBasedMatcher {
86public:
88 : CallDescriptionBasedMatcher(std::move(LockFn), std::move(UnlockFn)) {}
89
91 return cast(Call).getCXXThisVal().getAsRegion();
92 }
93};
94
95class RAIIMutexDescriptor {
97 mutable bool IdentifierInfoInitialized{};
99
100 void initIdentifierInfo(const CallEvent &Call) const {
101 if (!IdentifierInfoInitialized) {
102
103
104
105
106
107 const auto &ASTCtx = Call.getState()->getStateManager().getContext();
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;
117 cast(C->getDecl()->getParent())->getIdentifier();
118 return II == Guard;
119 }
120
121public:
122 RAIIMutexDescriptor(StringRef GuardName) : GuardName(GuardName) {}
124 initIdentifierInfo(Call);
125 if (IsLock) {
126 return matchesImpl(Call);
127 }
128 return matchesImpl(Call);
129 }
131 bool IsLock) const {
132 const MemRegion *LockRegion = nullptr;
133 if (IsLock) {
134 if (std::optional Object = Call.getReturnValueUnderConstruction()) {
135 LockRegion = Object->getAsRegion();
136 }
137 } else {
138 LockRegion = cast(Call).getCXXThisVal().getAsRegion();
139 }
140 return LockRegion;
141 }
142};
143
144using MutexDescriptor =
145 std::variant<FirstArgMutexDescriptor, MemberMutexDescriptor,
146 RAIIMutexDescriptor>;
147
148class BlockInCriticalSectionChecker : public Checkercheck::PostCall {
149private:
150 const std::array<MutexDescriptor, 8> MutexDescriptors{
151
152
153
154
155
156
157
158 MemberMutexDescriptor(
159 {CDM::CXXMethod,
160 {"std", "lock"},
161 0},
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")};
175
176 const CallDescriptionSet BlockingFunctions{{CDM::CLibrary, {"sleep"}},
177 {CDM::CLibrary, {"getc"}},
178 {CDM::CLibrary, {"fgets"}},
179 {CDM::CLibrary, {"read"}},
180 {CDM::CLibrary, {"recv"}}};
181
182 const BugType BlockInCritSectionBugType{
183 this, "Call to blocking function in critical section", "Blocking Error"};
184
186
187 [[nodiscard]] const NoteTag *createCritSectionNote(CritSectionMarker M,
189
190 [[nodiscard]] std::optional
192 bool IsLock) const;
193
194 void handleLock(const MutexDescriptor &Mutex, const CallEvent &Call,
196
197 void handleUnlock(const MutexDescriptor &Mutex, const CallEvent &Call,
199
200 [[nodiscard]] bool isBlockingInCritSection(const CallEvent &Call,
202
203public:
204
205
206
208};
209
210}
211
213
214
215
216
217
218template <>
219struct std::iterator_traits<
220 typename llvm::ImmutableList::iterator> {
226};
227
228std::optional
229BlockInCriticalSectionChecker::checkDescriptorMatch(const CallEvent &Call,
231 bool IsLock) const {
232 const auto Descriptor =
233 llvm::find_if(MutexDescriptors, [&Call, IsLock](auto &&Descriptor) {
234 return std::visit(
235 [&Call, IsLock](auto &&DescriptorImpl) {
236 return DescriptorImpl.matches(Call, IsLock);
237 },
238 Descriptor);
239 });
240 if (Descriptor != MutexDescriptors.end())
241 return *Descriptor;
242 return std::nullopt;
243}
244
246 while (Reg) {
247 const auto *BaseClassRegion = dyn_cast(Reg);
249 break;
250 Reg = BaseClassRegion->getSuperRegion();
251 }
252 return Reg;
253}
254
256 const MutexDescriptor &Descriptor,
257 bool IsLock) {
258 return std::visit(
259 [&Call, IsLock](auto &Descr) -> const MemRegion * {
261 },
262 Descriptor);
263}
264
265void BlockInCriticalSectionChecker::handleLock(
266 const MutexDescriptor &LockDescriptor, const CallEvent &Call,
269 getRegion(Call, LockDescriptor, true);
270 if (!MutexRegion)
271 return;
272
273 const CritSectionMarker MarkToAdd{Call.getOriginExpr(), MutexRegion};
275 C.getState()->add(MarkToAdd);
276 C.addTransition(StateWithLockEvent, createCritSectionNote(MarkToAdd, C));
277}
278
279void BlockInCriticalSectionChecker::handleUnlock(
280 const MutexDescriptor &UnlockDescriptor, const CallEvent &Call,
283 getRegion(Call, UnlockDescriptor, false);
284 if (!MutexRegion)
285 return;
286
288 const auto ActiveSections = State->get();
289 const auto MostRecentLock =
290 llvm::find_if(ActiveSections, [MutexRegion](auto &&Marker) {
291 return Marker.LockReg == MutexRegion;
292 });
293 if (MostRecentLock == ActiveSections.end())
294 return;
295
296
297 auto &Factory = State->get_context();
298 llvm::ImmutableList NewList = Factory.getEmptyList();
299 for (auto It = ActiveSections.begin(), End = ActiveSections.end(); It != End;
300 ++It) {
301 if (It != MostRecentLock)
302 NewList = Factory.add(*It, NewList);
303 }
304
305 State = State->set(NewList);
306 C.addTransition(State);
307}
308
309bool BlockInCriticalSectionChecker::isBlockingInCritSection(
311 return BlockingFunctions.contains(Call) &&
312 .getState()->get().isEmpty();
313}
314
315void BlockInCriticalSectionChecker::checkPostCall(const CallEvent &Call,
317 if (isBlockingInCritSection(Call, C)) {
318 reportBlockInCritSection(Call, C);
319 } else if (std::optional LockDesc =
320 checkDescriptorMatch(Call, C, true)) {
321 handleLock(*LockDesc, Call, C);
322 } else if (std::optional UnlockDesc =
323 checkDescriptorMatch(Call, C, false)) {
324 handleUnlock(*UnlockDesc, Call, C);
325 }
326}
327
328void BlockInCriticalSectionChecker::reportBlockInCritSection(
330 ExplodedNode *ErrNode = C.generateNonFatalErrorNode(C.getState());
331 if (!ErrNode)
332 return;
333
334 std::string msg;
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(BlockInCritSectionBugType,
339 os.str(), ErrNode);
340 R->addRange(Call.getSourceRange());
341 R->markInteresting(Call.getReturnValue());
342 C.emitReport(std::move(R));
343}
344
346BlockInCriticalSectionChecker::createCritSectionNote(CritSectionMarker M,
348 const BugType *BT = &this->BlockInCritSectionBugType;
350 llvm::raw_ostream &OS) {
352 return;
353
354
355 const auto CritSectionBegins =
358 llvm::copy_if(
359 CritSectionBegins, std::back_inserter(LocksForMutex),
360 [M](const auto &Marker) { return Marker.LockReg == M.LockReg; });
361 if (LocksForMutex.empty())
362 return;
363
364
365
366 std::reverse(LocksForMutex.begin(), LocksForMutex.end());
367
368
369
370 const auto Position =
371 llvm::find_if(std::as_const(LocksForMutex), [M](const auto &Marker) {
372 return Marker.LockExpr == M.LockExpr;
373 });
374 if (Position == LocksForMutex.end())
375 return;
376
377
378
379 if (LocksForMutex.size() == 1) {
380 OS << "Entering critical section here";
381 return;
382 }
383
384 const auto IndexOfLock =
385 std::distance(std::as_const(LocksForMutex).begin(), Position);
386
387 const auto OrdinalOfLock = IndexOfLock + 1;
388 OS << "Entering critical section for the " << OrdinalOfLock
389 << llvm::getOrdinalSuffix(OrdinalOfLock) << " time here";
390 });
391}
392
393void ento::registerBlockInCriticalSectionChecker(CheckerManager &mgr) {
395}
396
397bool ento::shouldRegisterBlockInCriticalSectionChecker(
399 return true;
400}
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