I am using clang static analyzer 4.0.0. For the following example
int fun(){
int aa = 1,bb = 0;
int cc = aa/bb; // 1) devide by zero. // Reported by clang
int *pt = nullptr;
int a = *pt; // 2) null pointer dereference. // NOT Reported by clang
int b;
int c = a + b; // 3) Unused initialization. // Reported by clang
return cc;
}
Clang static analyzer reports only two issues 1 and 3 and skips issue 2.
Whereas if I changed the order of issue like this
int fun(){
int *pt = nullptr;
int a = *pt; // 1) null pointer dereference. // Reported by clang
int aa = 1,bb = 0;
int cc = aa/bb; // 2) devide by zero. // NOT Reported by clang
int b;
int c = a + b; // 3) Unused initialization. // Reported by clang
return cc;
}
then clang static analyzer reports 1 and 3 and skips 2.
I am running clang static analyzer with this command
clang-check.exe -analyze D:\testsrc\anothercpp.cpp
This is very inconsistent behavior. No matter in what order the issues are, one of the issue get skipped. Also, I checked this scenario with clang 5.0.1 only to yield same results.
Does anybody have any idea why this is happening with static analyzer?
Thanks in advance.
-Hemant
clang-tidy is a clang-based C++ “linter” tool. Its purpose is to provide an extensible framework for diagnosing and fixing typical programming errors, like style violations, interface misuse, or bugs that can be deduced via static analysis.
The Clang Static Analyzer is a source code analysis tool that finds bugs in C, C++, and Objective-C programs. It implements path-sensitive, inter-procedural analysis based on symbolic execution technique.
Having a quick look at the code it seems the behaviour you observed is by design.
When the DereferenceChecker
which presumably found the null-pointer dereference reports a bug, it creates an "error node" that stops further exploration for path-sensitive analyses.
void DereferenceChecker::reportBug(ProgramStateRef State, const Stmt *S,
CheckerContext &C) const {
// Generate an error node.
ExplodedNode *N = C.generateErrorNode(State);
CheckerContext::generateErrorNode is documented to stop exploration of the given path through the program.
/// \brief Generate a transition to a node that will be used to report
/// an error. This node will be a sink. That is, it will stop exploration of
/// the given path.
///
/// @param State The state of the generated node.
/// @param Tag The tag to uniquely identify the creation site. If null,
/// the default tag for the checker will be used.
ExplodedNode *generateErrorNode(ProgramStateRef State = nullptr,
const ProgramPointTag *Tag = nullptr) {
return generateSink(State, Pred,
(Tag ? Tag : Location.getTag()));
}
This makes sense, because after an error as grave as a null-pointer dereference, not many meaningful predictions about a program can be made. Since null-pointer dereference is undefined behaviour in C++, the standard allows anything to happen. Only by looking at the details of the program and the environment it is running on, more predictions could be made. Likely these predictions would be out of scope for the static analyzer.
In practice you can only fix one error at a time and would likely continue fixing errors until the static analyzer stops making valid complaints.
Detecting an unused variable does not require a path-sensitive analysis. Therefore this type of checker will still work.
All this demonstrates that you can't simply write a few primitive artificial tests for a static analyzer. I discussed this in detail in the article "Why I Dislike Synthetic Tests". It's most likely that for some internal reason (but surely not because of an error), the Clang analyzer skips the code fragment that follows a null dereferencing operation, which obviously leads to undefined behavior. Or it may skip because of a division by zero. And there's nothing wrong about it. It doesn't actually matter what's written after a null dereferencing or division by zero: the subsequent code doesn't execute anyway. So, this problem has nothing to do with the fault of the compiler/analyzer, but is rather the result of carelessly written tests like that. Writing a good test for a diagnostic is a difficult job, which requires you to be careful and aware of the many subtle details.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With