-
Notifications
You must be signed in to change notification settings - Fork 14.7k
[clang][analyzer] fix false positive of BlockInCriticalSectionChecker #126752
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
404ceb9
044d916
51340ca
4245955
8921bf1
e99e45b
8607b92
e929fba
edc7cbf
1e002aa
5f1f59a
80d1b8c
bf768fa
f42e757
d7ebf28
d387030
e3001ae
4d59b49
e9a950c
04afd3d
18c0d85
1d7f11b
6679b18
a3b8829
bd543c0
53ba8fc
5157425
1b38733
3d94088
4705987
efa1101
2c0c71a
2fb80ce
672a21a
406064c
77d7645
b351873
7416711
5a997d5
6968f3e
de7826e
8bd8f5c
973f013
4e38ed5
d9e66ab
def306d
5dba28e
d7fe304
6dca4ec
fe97f1b
9f107b4
a1474cb
769a916
831dab1
3008086
195da3d
6e542c6
77e3656
464bf26
b252de4
2df73fb
cf7c8e7
bfa8acf
be34333
10d6057
40860cb
32942c0
7ee8958
b968813
d8b2181
d03080a
76bb67a
ccb3f7b
8104d41
6dc8a9a
7836801
ee99f56
26038e3
232b002
300a9b6
edcfdcf
0b11c4b
3937ea2
80eef7b
e575fee
320c79f
83bab88
a073687
9f7d607
87c9343
7b12208
8e48631
29b74e8
baae21d
1bb3ab3
0f21601
b3b8f0b
daf67bc
be69cae
667cb60
0994ad7
8d93683
b822746
ee39f9d
2adc9d4
79d53c6
fbb6b8b
072927c
a6efb2a
ad82f39
e6d6f66
c6cb410
b24a854
7a8e769
d73f9c9
a51016e
7ed69fd
f0b5e42
22f708d
488ce90
e4345dd
a95a8fe
5fdfad7
9bd4c7e
efd2bf0
e32ef08
5e46853
0d202bd
5ffc2e6
b947982
a098066
beb99fd
b5ed280
8c8123a
dfeef87
f285635
fe8d601
6b02b0b
147bd30
e633db4
3ba4250
0cdced1
f4d3ed2
7c9d95f
3bf81f0
e88d3a1
c002af9
535397e
3ae02a8
e835edd
0dc5629
a9f9442
795902d
0bb20a2
a78e911
1624713
7610aac
f2ba3bb
a7545ab
c09e98b
03e71a3
0b4a71d
aa360a5
58a13f9
a5618a0
56167d6
adc7fc3
5c95559
f76827d
eacc139
9f04032
f35f69a
11974a4
7481069
51cad11
43120de
745b475
ee72f6b
7010a40
f7daffd
4a91ac3
f8a4fe5
ec84684
5135feb
16044f7
9bdaac9
0c6bfb6
d64a9f9
0da960f
0a4fb00
5e636bc
c58637a
0850676
369219a
27d9867
10a80d8
637ed4e
689998b
294b1e6
16ef2aa
d5e28e6
989bb22
7cb4380
77309b6
b4777b8
4c7a2a8
fbac315
873ee09
db43ed3
23f7261
2e49d4f
e0ad3d9
b9c250f
e9a3d26
ff6a4cb
88a4d24
9e4472e
57e9540
7a356c9
3a58168
a0d7415
fe864ab
2993012
659d771
3d47d9a
60f0e05
cb5992e
96458ef
54a65dc
da2ac55
8737638
e8c3575
cf02e67
e43dbb5
e823f14
3ef0e7f
cd6f46d
909fd42
618e74f
6878783
95c06d4
6912175
8b10a39
8c88cb5
08e541a
50f2560
3ae58c1
e865105
021ebc6
551b041
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -145,6 +145,92 @@ using MutexDescriptor = | |
std::variant<FirstArgMutexDescriptor, MemberMutexDescriptor, | ||
RAIIMutexDescriptor>; | ||
|
||
class NonBlockOpenVisitor : public BugReporterVisitor { | ||
private: | ||
const VarRegion *VR; | ||
const CallExpr *OpenCallExpr; | ||
int O_NONBLOCKV; | ||
bool Satisfied; | ||
CallDescription OpenFunction{CDM::CLibrary, {"open"}, 2}; | ||
|
||
public: | ||
NonBlockOpenVisitor(const VarRegion *VR, int O_NONBLOCKV) | ||
: VR(VR), OpenCallExpr(nullptr), O_NONBLOCKV(O_NONBLOCKV), | ||
Satisfied(false) {} | ||
|
||
static void *getTag() { | ||
static int Tag = 0; | ||
return static_cast<void *>(&Tag); | ||
} | ||
|
||
void Profile(llvm::FoldingSetNodeID &ID) const override { | ||
ID.AddPointer(getTag()); | ||
ID.AddPointer(VR); | ||
} | ||
|
||
PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, | ||
BugReporterContext &BRC, | ||
PathSensitiveBugReport &BR) override { | ||
if (Satisfied) | ||
return nullptr; | ||
|
||
// check for open call's 2th argument | ||
if (std::optional<StmtPoint> P = N->getLocationAs<StmtPoint>()) { | ||
if (OpenCallExpr && OpenCallExpr == P->getStmtAs<CallExpr>()) { | ||
Satisfied = true; | ||
const auto *openFlagExpr = OpenCallExpr->getArg(1); | ||
SVal flagSV = N->getSVal(openFlagExpr); | ||
const llvm::APSInt *flagV = flagSV.getAsInteger(); | ||
if (!flagV) | ||
return nullptr; | ||
|
||
if ((*flagV & O_NONBLOCKV) != 0) | ||
BR.markInvalid(getTag(), nullptr); | ||
|
||
return nullptr; | ||
} | ||
} | ||
|
||
const ExplodedNode *Pred = N->getFirstPred(); | ||
SVal presv = Pred->getState()->getSVal(VR); | ||
SVal sv = N->getState()->getSVal(VR); | ||
|
||
// check if file descirptor's SVal changed between nodes | ||
if (sv == presv) | ||
return nullptr; | ||
|
||
std::optional<PostStore> P = N->getLocationAs<PostStore>(); | ||
if (!P) | ||
return nullptr; | ||
|
||
if (const auto *DS = P->getStmtAs<DeclStmt>()) { | ||
// variable initialization | ||
// int fd = open(...) | ||
const VarDecl *VD = VR->getDecl(); | ||
if (DS->getSingleDecl() == VR->getDecl()) { | ||
const auto *InitCall = dyn_cast_if_present<CallExpr>(VD->getInit()); | ||
if (!InitCall || !OpenFunction.matchesAsWritten(*InitCall)) | ||
return nullptr; | ||
|
||
OpenCallExpr = InitCall; | ||
} | ||
} else if (const auto *BO = P->getStmtAs<BinaryOperator>()) { | ||
// assignment | ||
// fd = open(...) | ||
const auto *DRE = dyn_cast<DeclRefExpr>(BO->getLHS()); | ||
if (DRE && DRE->getDecl() == VR->getDecl()) { | ||
const auto *InitCall = dyn_cast<CallExpr>(BO->getRHS()); | ||
if (!InitCall || !OpenFunction.matchesAsWritten(*InitCall)) | ||
return nullptr; | ||
|
||
OpenCallExpr = InitCall; | ||
} | ||
} | ||
|
||
return nullptr; | ||
} | ||
}; | ||
|
||
class BlockInCriticalSectionChecker : public Checker<check::PostCall> { | ||
private: | ||
const std::array<MutexDescriptor, 8> MutexDescriptors{ | ||
|
@@ -339,6 +425,36 @@ void BlockInCriticalSectionChecker::reportBlockInCritSection( | |
os.str(), ErrNode); | ||
R->addRange(Call.getSourceRange()); | ||
R->markInteresting(Call.getReturnValue()); | ||
// for 'read' call, check whether it's file descriptor(first argument) is | ||
// created by 'open' API with O_NONBLOCK flag and don't report for this | ||
// situation. | ||
if (Call.getCalleeIdentifier()->getName() == "read") { | ||
Xazax-hun marked this conversation as resolved.
Show resolved
Hide resolved
|
||
do { | ||
const auto *arg = Call.getArgExpr(0); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: I know the function is not consistent at the moment but the coding convention requires variable names to start with an uppercase letter. |
||
if (!arg) | ||
break; | ||
|
||
const auto *DRE = dyn_cast<DeclRefExpr>(arg->IgnoreImpCasts()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Matching the AST is not a good idea. What if the user writes something like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i am hoping to get the MemRegion of file descriptor here and compare its corresponding SVal between pred node and current node in
this is the same reason i didn't use set There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see. Unfortunately, matching on the memory region might not be the most reliable. Having things like E.g., imagine if someone has some logging and does something like So my proposal would be to maintain a set of symbols in the analysis state. After each When we report a problem check if the file descriptor we use to report the problem is in the set. This works great for symbolic file descriptors. Unfortunately, this might break down for concrete integers like 1. But I am wondering if that is a rare edge case enough that we might not need to support. I'd expect open to almost always return a symbolic value. What do you think? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. thank you for your detailed explanation, I did overlook some complex scenarios. And I agree with your point, Checking for
|
||
if (!DRE) | ||
break; | ||
|
||
const auto *VD = dyn_cast_if_present<VarDecl>(DRE->getDecl()); | ||
if (!VD) | ||
break; | ||
|
||
const VarRegion *VR = C.getState()->getRegion(VD, C.getLocationContext()); | ||
if (!VR) | ||
break; | ||
|
||
std::optional<int> O_NONBLOCKV = tryExpandAsInteger( | ||
"O_NONBLOCK", C.getBugReporter().getPreprocessor()); | ||
if (!O_NONBLOCKV) | ||
break; | ||
|
||
R->addVisitor( | ||
std::make_unique<NonBlockOpenVisitor>(VR, O_NONBLOCKV.value())); | ||
} while (false); | ||
} | ||
C.emitReport(std::move(R)); | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
// RUN: %clang_analyze_cc1 \ | ||
// RUN: -analyzer-checker=unix.BlockInCriticalSection \ | ||
// RUN: -std=c++11 \ | ||
// RUN: -analyzer-output text \ | ||
// RUN: -verify %s | ||
|
||
// expected-no-diagnostics | ||
|
||
namespace std { | ||
struct mutex { | ||
void lock() {} | ||
void unlock() {} | ||
}; | ||
template<typename T> | ||
struct lock_guard { | ||
lock_guard<T>(std::mutex) {} | ||
~lock_guard<T>() {} | ||
}; | ||
template<typename T> | ||
struct unique_lock { | ||
unique_lock<T>(std::mutex) {} | ||
~unique_lock<T>() {} | ||
}; | ||
template<typename T> | ||
struct not_real_lock { | ||
not_real_lock<T>(std::mutex) {} | ||
}; | ||
} // namespace std | ||
|
||
std::mutex mtx; | ||
using ssize_t = long long; | ||
using size_t = unsigned long long; | ||
int open (const char *__file, int __oflag, ...); | ||
ssize_t read(int fd, void *buf, size_t count); | ||
void close(int fd); | ||
#define O_RDONLY 00 | ||
# define O_NONBLOCK 04000 | ||
|
||
void foo() | ||
{ | ||
std::lock_guard<std::mutex> lock(mtx); | ||
|
||
const char *filename = "example.txt"; | ||
int fd = open(filename, O_RDONLY | O_NONBLOCK); | ||
|
||
char buffer[200] = {}; | ||
read(fd, buffer, 199); // no-warning: fd is a non-block file descriptor | ||
close(fd); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: missing new line at the end of the file. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These POSIX constants are often defined as preprocessor macros, so I don't think it's a good idea to use these as symbol names where they could expand to their definition.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i am not sure if i understand you comment right.
To avoid get it every time we need it,
O_NONBLOCK
's value is got for marco usingtryExpandAsInteger
API inreportBlockInCritSection
, passed toNonBlockOpenVisitor
and stored to this class memberO_NONBLOCKV
.And it may not exists in current TU(didn't include certain header or some other reason), when we can't get it's value, the
NonBlockOpenVisitor
will not be needed then.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My suggestion is to call this
int
here something else. Maybeint ValueOfONonBlockVFlag; or just
int ValueO_NONBLOCKV;. Something that prevents an accidental expansion to invalid code
int 8;` or something in this file.Getting the value at runtime from the target architecture and putting it behind this variable is not a problem to do. The only concern here is the name of this variable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i understand now, thank you for explaining, i will change it in the subsequent modification