Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

clang static analyzer skipping some checks

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

like image 667
Hemant Avatar asked Feb 09 '18 11:02

Hemant


People also ask

What is clang tidy checks?

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.

What is clang static analyzer?

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.


2 Answers

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.

like image 155
PaulR Avatar answered Oct 11 '22 17:10

PaulR


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.

like image 23
AndreyKarpov Avatar answered Oct 11 '22 15:10

AndreyKarpov