Search code examples
c++frontendllvm-clangclang-static-analyzer

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


Solution

  • 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.