Skip to content

Commit 9d48705

Browse files
floventsteakhal
andauthored
[clang][analyzer] Teach the BlockInCriticalSectionChecker about O_NONBLOCK streams (#127049)
this PR close #124474 when calling `read` and `recv` function for a non-block file descriptor or a invalid file descriptor(`-1`), it will not cause block inside a critical section. this commit checks for non-block file descriptor assigned by `open` function with `O_NONBLOCK` flag. --------- Co-authored-by: Balazs Benics <[email protected]>
1 parent 919e72f commit 9d48705

File tree

3 files changed

+126
-0
lines changed

3 files changed

+126
-0
lines changed

clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,57 @@ using MutexDescriptor =
145145
std::variant<FirstArgMutexDescriptor, MemberMutexDescriptor,
146146
RAIIMutexDescriptor>;
147147

148+
class SuppressNonBlockingStreams : public BugReporterVisitor {
149+
private:
150+
const CallDescription OpenFunction{CDM::CLibrary, {"open"}, 2};
151+
SymbolRef StreamSym;
152+
const int NonBlockMacroVal;
153+
bool Satisfied = false;
154+
155+
public:
156+
SuppressNonBlockingStreams(SymbolRef StreamSym, int NonBlockMacroVal)
157+
: StreamSym(StreamSym), NonBlockMacroVal(NonBlockMacroVal) {}
158+
159+
static void *getTag() {
160+
static bool Tag;
161+
return &Tag;
162+
}
163+
164+
void Profile(llvm::FoldingSetNodeID &ID) const override {
165+
ID.AddPointer(getTag());
166+
}
167+
168+
PathDiagnosticPieceRef VisitNode(const ExplodedNode *N,
169+
BugReporterContext &BRC,
170+
PathSensitiveBugReport &BR) override {
171+
if (Satisfied)
172+
return nullptr;
173+
174+
std::optional<StmtPoint> Point = N->getLocationAs<StmtPoint>();
175+
if (!Point)
176+
return nullptr;
177+
178+
const auto *CE = Point->getStmtAs<CallExpr>();
179+
if (!CE || !OpenFunction.matchesAsWritten(*CE))
180+
return nullptr;
181+
182+
if (N->getSVal(CE).getAsSymbol() != StreamSym)
183+
return nullptr;
184+
185+
Satisfied = true;
186+
187+
// Check if open's second argument contains O_NONBLOCK
188+
const llvm::APSInt *FlagVal = N->getSVal(CE->getArg(1)).getAsInteger();
189+
if (!FlagVal)
190+
return nullptr;
191+
192+
if ((*FlagVal & NonBlockMacroVal) != 0)
193+
BR.markInvalid(getTag(), nullptr);
194+
195+
return nullptr;
196+
}
197+
};
198+
148199
class BlockInCriticalSectionChecker : public Checker<check::PostCall> {
149200
private:
150201
const std::array<MutexDescriptor, 8> MutexDescriptors{
@@ -182,6 +233,9 @@ class BlockInCriticalSectionChecker : public Checker<check::PostCall> {
182233
const BugType BlockInCritSectionBugType{
183234
this, "Call to blocking function in critical section", "Blocking Error"};
184235

236+
using O_NONBLOCKValueTy = std::optional<int>;
237+
mutable std::optional<O_NONBLOCKValueTy> O_NONBLOCKValue;
238+
185239
void reportBlockInCritSection(const CallEvent &call, CheckerContext &C) const;
186240

187241
[[nodiscard]] const NoteTag *createCritSectionNote(CritSectionMarker M,
@@ -337,6 +391,28 @@ void BlockInCriticalSectionChecker::reportBlockInCritSection(
337391
<< "' inside of critical section";
338392
auto R = std::make_unique<PathSensitiveBugReport>(BlockInCritSectionBugType,
339393
os.str(), ErrNode);
394+
// for 'read' and 'recv' call, check whether it's file descriptor(first
395+
// argument) is
396+
// created by 'open' API with O_NONBLOCK flag or is equal to -1, they will
397+
// not cause block in these situations, don't report
398+
StringRef FuncName = Call.getCalleeIdentifier()->getName();
399+
if (FuncName == "read" || FuncName == "recv") {
400+
SVal SV = Call.getArgSVal(0);
401+
SValBuilder &SVB = C.getSValBuilder();
402+
ProgramStateRef state = C.getState();
403+
ConditionTruthVal CTV =
404+
state->areEqual(SV, SVB.makeIntVal(-1, C.getASTContext().IntTy));
405+
if (CTV.isConstrainedTrue())
406+
return;
407+
408+
if (SymbolRef SR = SV.getAsSymbol()) {
409+
if (!O_NONBLOCKValue)
410+
O_NONBLOCKValue = tryExpandAsInteger(
411+
"O_NONBLOCK", C.getBugReporter().getPreprocessor());
412+
if (*O_NONBLOCKValue)
413+
R->addVisitor<SuppressNonBlockingStreams>(SR, **O_NONBLOCKValue);
414+
}
415+
}
340416
R->addRange(Call.getSourceRange());
341417
R->markInteresting(Call.getReturnValue());
342418
C.emitReport(std::move(R));
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#pragma clang system_header
2+
3+
namespace std {
4+
struct mutex {
5+
void lock();
6+
void unlock();
7+
};
8+
9+
template <typename T> struct lock_guard {
10+
lock_guard(std::mutex &);
11+
~lock_guard();
12+
};
13+
} // namespace std

clang/test/Analysis/issue-124474.cpp

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// RUN: %clang_analyze_cc1 \
2+
// RUN: -analyzer-checker=core,unix.BlockInCriticalSection \
3+
// RUN: -analyzer-output text -verify %s
4+
5+
// expected-no-diagnostics
6+
7+
#include "Inputs/system-header-simulator-cxx-std-locks.h"
8+
9+
std::mutex mtx;
10+
using ssize_t = long long;
11+
using size_t = unsigned long long;
12+
int open(const char *__file, int __oflag, ...);
13+
ssize_t read(int fd, void *buf, size_t count);
14+
void close(int fd);
15+
#define O_RDONLY 00
16+
#define O_NONBLOCK 04000
17+
18+
void foo() {
19+
std::lock_guard<std::mutex> lock(mtx);
20+
21+
const char *filename = "example.txt";
22+
int fd = open(filename, O_RDONLY | O_NONBLOCK);
23+
24+
char buffer[200] = {};
25+
read(fd, buffer, 199); // no-warning: fd is a non-block file descriptor or equals to -1
26+
close(fd);
27+
}
28+
29+
void foo1(int fd) {
30+
std::lock_guard<std::mutex> lock(mtx);
31+
32+
const char *filename = "example.txt";
33+
char buffer[200] = {};
34+
if (fd == -1)
35+
read(fd, buffer, 199); // no-warning: consider file descriptor is a symbol equals to -1
36+
close(fd);
37+
}

0 commit comments

Comments
 (0)