Skip to content

Commit c916dad

Browse files
committed
[clang][analyzer] fix false positive of BlockInCriticalSectionChecker
1 parent cf69b4c commit c916dad

File tree

2 files changed

+115
-1
lines changed

2 files changed

+115
-1
lines changed

clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,8 @@ using MutexDescriptor =
145145
std::variant<FirstArgMutexDescriptor, MemberMutexDescriptor,
146146
RAIIMutexDescriptor>;
147147

148-
class BlockInCriticalSectionChecker : public Checker<check::PostCall> {
148+
class BlockInCriticalSectionChecker
149+
: public Checker<check::PostCall, check::DeadSymbols> {
149150
private:
150151
const std::array<MutexDescriptor, 8> MutexDescriptors{
151152
// NOTE: There are standard library implementations where some methods
@@ -179,6 +180,8 @@ class BlockInCriticalSectionChecker : public Checker<check::PostCall> {
179180
{CDM::CLibrary, {"read"}},
180181
{CDM::CLibrary, {"recv"}}};
181182

183+
const CallDescription OpenFunction{CDM::CLibrary, {"open"}, 2};
184+
182185
const BugType BlockInCritSectionBugType{
183186
this, "Call to blocking function in critical section", "Blocking Error"};
184187

@@ -197,6 +200,8 @@ class BlockInCriticalSectionChecker : public Checker<check::PostCall> {
197200
void handleUnlock(const MutexDescriptor &Mutex, const CallEvent &Call,
198201
CheckerContext &C) const;
199202

203+
void handleOpen(const CallEvent &Call, CheckerContext &C) const;
204+
200205
[[nodiscard]] bool isBlockingInCritSection(const CallEvent &Call,
201206
CheckerContext &C) const;
202207

@@ -205,11 +210,14 @@ class BlockInCriticalSectionChecker : public Checker<check::PostCall> {
205210
/// Process lock.
206211
/// Process blocking functions (sleep, getc, fgets, read, recv)
207212
void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
213+
214+
void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;
208215
};
209216

210217
} // end anonymous namespace
211218

212219
REGISTER_LIST_WITH_PROGRAMSTATE(ActiveCritSections, CritSectionMarker)
220+
REGISTER_SET_WITH_PROGRAMSTATE(NonBlockFileDescriptor, SymbolRef)
213221

214222
// Iterator traits for ImmutableList data structure
215223
// that enable the use of STL algorithms.
@@ -306,6 +314,25 @@ void BlockInCriticalSectionChecker::handleUnlock(
306314
C.addTransition(State);
307315
}
308316

317+
void BlockInCriticalSectionChecker::handleOpen(const CallEvent &Call,
318+
CheckerContext &C) const {
319+
const auto *Flag = Call.getArgExpr(1);
320+
static std::optional<int> ValueOfONonBlockVFlag =
321+
tryExpandAsInteger("O_NONBLOCK", C.getBugReporter().getPreprocessor());
322+
if (!ValueOfONonBlockVFlag)
323+
return;
324+
325+
SVal FlagSV = C.getState()->getSVal(Flag, C.getLocationContext());
326+
const llvm::APSInt *FlagV = FlagSV.getAsInteger();
327+
if (!FlagV)
328+
return;
329+
330+
if ((*FlagV & ValueOfONonBlockVFlag.value()) != 0)
331+
if (SymbolRef SR = Call.getReturnValue().getAsSymbol()) {
332+
C.addTransition(C.getState()->add<NonBlockFileDescriptor>(SR));
333+
}
334+
}
335+
309336
bool BlockInCriticalSectionChecker::isBlockingInCritSection(
310337
const CallEvent &Call, CheckerContext &C) const {
311338
return BlockingFunctions.contains(Call) &&
@@ -315,16 +342,54 @@ bool BlockInCriticalSectionChecker::isBlockingInCritSection(
315342
void BlockInCriticalSectionChecker::checkPostCall(const CallEvent &Call,
316343
CheckerContext &C) const {
317344
if (isBlockingInCritSection(Call, C)) {
345+
// for 'read' and 'recv' call, check whether it's file descriptor(first
346+
// argument) is
347+
// created by 'open' API with O_NONBLOCK flag or is equal to -1, they will
348+
// not cause block in these situations, don't report
349+
StringRef FuncName = Call.getCalleeIdentifier()->getName();
350+
if (FuncName == "read" || FuncName == "recv") {
351+
const auto *Arg = Call.getArgExpr(0);
352+
if (!Arg)
353+
return;
354+
355+
SVal SV = C.getSVal(Arg);
356+
if (const auto *IntValue = SV.getAsInteger()) {
357+
if (*IntValue == -1)
358+
return;
359+
}
360+
361+
SymbolRef SR = C.getSVal(Arg).getAsSymbol();
362+
if (SR && C.getState()->contains<NonBlockFileDescriptor>(SR)) {
363+
return;
364+
}
365+
}
318366
reportBlockInCritSection(Call, C);
319367
} else if (std::optional<MutexDescriptor> LockDesc =
320368
checkDescriptorMatch(Call, C, /*IsLock=*/true)) {
321369
handleLock(*LockDesc, Call, C);
322370
} else if (std::optional<MutexDescriptor> UnlockDesc =
323371
checkDescriptorMatch(Call, C, /*IsLock=*/false)) {
324372
handleUnlock(*UnlockDesc, Call, C);
373+
} else if (OpenFunction.matches(Call)) {
374+
handleOpen(Call, C);
325375
}
326376
}
327377

378+
void BlockInCriticalSectionChecker::checkDeadSymbols(SymbolReaper &SymReaper,
379+
CheckerContext &C) const {
380+
ProgramStateRef State = C.getState();
381+
382+
// Remove the dead symbols from the NonBlockFileDescriptor set.
383+
NonBlockFileDescriptorTy Tracked = State->get<NonBlockFileDescriptor>();
384+
for (SymbolRef SR : Tracked) {
385+
if (SymReaper.isDead(SR)) {
386+
State = State->remove<NonBlockFileDescriptor>(SR);
387+
}
388+
}
389+
390+
C.addTransition(State);
391+
}
392+
328393
void BlockInCriticalSectionChecker::reportBlockInCritSection(
329394
const CallEvent &Call, CheckerContext &C) const {
330395
ExplodedNode *ErrNode = C.generateNonFatalErrorNode(C.getState());
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// RUN: %clang_analyze_cc1 \
2+
// RUN: -analyzer-checker=unix.BlockInCriticalSection \
3+
// RUN: -std=c++11 \
4+
// RUN: -analyzer-output text \
5+
// RUN: -verify %s
6+
7+
// expected-no-diagnostics
8+
9+
namespace std {
10+
struct mutex {
11+
void lock() {}
12+
void unlock() {}
13+
};
14+
template<typename T>
15+
struct lock_guard {
16+
lock_guard<T>(std::mutex) {}
17+
~lock_guard<T>() {}
18+
};
19+
template<typename T>
20+
struct unique_lock {
21+
unique_lock<T>(std::mutex) {}
22+
~unique_lock<T>() {}
23+
};
24+
template<typename T>
25+
struct not_real_lock {
26+
not_real_lock<T>(std::mutex) {}
27+
};
28+
} // namespace std
29+
30+
std::mutex mtx;
31+
using ssize_t = long long;
32+
using size_t = unsigned long long;
33+
int open (const char *__file, int __oflag, ...);
34+
ssize_t read(int fd, void *buf, size_t count);
35+
void close(int fd);
36+
#define O_RDONLY 00
37+
# define O_NONBLOCK 04000
38+
39+
void foo()
40+
{
41+
std::lock_guard<std::mutex> lock(mtx);
42+
43+
const char *filename = "example.txt";
44+
int fd = open(filename, O_RDONLY | O_NONBLOCK);
45+
46+
char buffer[200] = {};
47+
read(fd, buffer, 199); // no-warning: fd is a non-block file descriptor
48+
close(fd);
49+
}

0 commit comments

Comments
 (0)