Skip to content

Commit 6234834

Browse files
committed
[clang][analyzer] fix false positive of BlockInCriticalSectionChecker
1 parent 9387fd9 commit 6234834

File tree

2 files changed

+165
-0
lines changed

2 files changed

+165
-0
lines changed

clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp

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

148+
class NonBlockOpenVisitor : public BugReporterVisitor {
149+
private:
150+
const VarRegion *VR;
151+
const CallExpr *OpenCallExpr;
152+
int O_NONBLOCKV;
153+
bool Satisfied;
154+
CallDescription OpenFunction{CDM::CLibrary, {"open"}, 2};
155+
156+
public:
157+
NonBlockOpenVisitor(const VarRegion *VR, int O_NONBLOCKV)
158+
: VR(VR), OpenCallExpr(nullptr), O_NONBLOCKV(O_NONBLOCKV),
159+
Satisfied(false) {}
160+
161+
static void *getTag() {
162+
static int Tag = 0;
163+
return static_cast<void *>(&Tag);
164+
}
165+
166+
void Profile(llvm::FoldingSetNodeID &ID) const override {
167+
ID.AddPointer(getTag());
168+
ID.AddPointer(VR);
169+
}
170+
171+
PathDiagnosticPieceRef VisitNode(const ExplodedNode *N,
172+
BugReporterContext &BRC,
173+
PathSensitiveBugReport &BR) override {
174+
if (Satisfied)
175+
return nullptr;
176+
177+
// check for open call's 2th argument
178+
if (std::optional<StmtPoint> P = N->getLocationAs<StmtPoint>()) {
179+
if (OpenCallExpr && OpenCallExpr == P->getStmtAs<CallExpr>()) {
180+
Satisfied = true;
181+
const auto *openFlagExpr = OpenCallExpr->getArg(1);
182+
SVal flagSV = N->getSVal(openFlagExpr);
183+
const llvm::APSInt *flagV = flagSV.getAsInteger();
184+
if (!flagV)
185+
return nullptr;
186+
187+
if ((*flagV & O_NONBLOCKV) != 0)
188+
BR.markInvalid(getTag(), nullptr);
189+
190+
return nullptr;
191+
}
192+
}
193+
194+
const ExplodedNode *Pred = N->getFirstPred();
195+
SVal presv = Pred->getState()->getSVal(VR);
196+
SVal sv = N->getState()->getSVal(VR);
197+
198+
// check if file descirptor's SVal changed between nodes
199+
if (sv == presv)
200+
return nullptr;
201+
202+
std::optional<PostStore> P = N->getLocationAs<PostStore>();
203+
if (!P)
204+
return nullptr;
205+
206+
if (const auto *DS = P->getStmtAs<DeclStmt>()) {
207+
// variable initialization
208+
// int fd = open(...)
209+
const VarDecl *VD = VR->getDecl();
210+
if (DS->getSingleDecl() == VR->getDecl()) {
211+
const auto *InitCall = dyn_cast_if_present<CallExpr>(VD->getInit());
212+
if (!InitCall || !OpenFunction.matchesAsWritten(*InitCall))
213+
return nullptr;
214+
215+
OpenCallExpr = InitCall;
216+
}
217+
} else if (const auto *BO = P->getStmtAs<BinaryOperator>()) {
218+
// assignment
219+
// fd = open(...)
220+
const auto *DRE = dyn_cast<DeclRefExpr>(BO->getLHS());
221+
if (DRE && DRE->getDecl() == VR->getDecl()) {
222+
const auto *InitCall = dyn_cast<CallExpr>(BO->getRHS());
223+
if (!InitCall || !OpenFunction.matchesAsWritten(*InitCall))
224+
return nullptr;
225+
226+
OpenCallExpr = InitCall;
227+
}
228+
}
229+
230+
return nullptr;
231+
}
232+
};
233+
148234
class BlockInCriticalSectionChecker : public Checker<check::PostCall> {
149235
private:
150236
const std::array<MutexDescriptor, 8> MutexDescriptors{
@@ -339,6 +425,36 @@ void BlockInCriticalSectionChecker::reportBlockInCritSection(
339425
os.str(), ErrNode);
340426
R->addRange(Call.getSourceRange());
341427
R->markInteresting(Call.getReturnValue());
428+
// for 'read' call, check whether it's file descriptor(first argument) is
429+
// created by 'open' API with O_NONBLOCK flag and don't report for this
430+
// situation.
431+
if (Call.getCalleeIdentifier()->getName() == "read") {
432+
do {
433+
const auto *arg = Call.getArgExpr(0);
434+
if (!arg)
435+
break;
436+
437+
const auto *DRE = dyn_cast<DeclRefExpr>(arg->IgnoreImpCasts());
438+
if (!DRE)
439+
break;
440+
441+
const auto *VD = dyn_cast_if_present<VarDecl>(DRE->getDecl());
442+
if (!VD)
443+
break;
444+
445+
const VarRegion *VR = C.getState()->getRegion(VD, C.getLocationContext());
446+
if (!VR)
447+
break;
448+
449+
std::optional<int> O_NONBLOCKV = tryExpandAsInteger(
450+
"O_NONBLOCK", C.getBugReporter().getPreprocessor());
451+
if (!O_NONBLOCKV)
452+
break;
453+
454+
R->addVisitor(
455+
std::make_unique<NonBlockOpenVisitor>(VR, O_NONBLOCKV.value()));
456+
} while (false);
457+
}
342458
C.emitReport(std::move(R));
343459
}
344460

clang/test/Analysis/issue-124474.cpp

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)