Confused by analysis

If I have a unit test like:

// RUN: clang -analyze -analyzer-constraints=range -checker-cfref -verify %s

void f2(int n) {
  char *p = 0;
  char a[10];

  if (n < 1)
    p = a;

  if (n > 0)
    *p = 'X'; // expected-warning{{Dereference of null pointer.}}

  if (n >= -3)
    *p = 'X'; // expected-warning{{Dereference of null pointer.}}
}

void f3(int n) {
  char *p = 0;
  char a[10];

  if (n < 1)
    p = a;

  if (n >= -3)
    *p = 'X'; // expected-warning{{Dereference of null pointer.}}
}

then the second expected warning is not emitted, but the third one is.
Why is this?

Ben Laurie wrote:

If I have a unit test like:

// RUN: clang -analyze -analyzer-constraints=range -checker-cfref -verify %s

void f2(int n) {
  char *p = 0;
  char a[10];

  if (n < 1)
    p = a;

  if (n > 0)
    *p = 'X'; // expected-warning{{Dereference of null pointer.}}

  if (n >= -3)
    *p = 'X'; // expected-warning{{Dereference of null pointer.}}
}
  

It's not actually possible to dereference a null pointer in the second
case here. What are the options?
1) n < -3 --> if is not entered
2) -3 <= n < 1 --> p is not null
3) 1 <= n --> the null pointer dereference above already killed the program

I'm pretty sure the analyzer works under the assumption that null
pointer dereferences are unrecoverable.

Sebastian

Hi Ben,

For these kind of questions its often useful to look at the simulation graph. e.g.:

   clang -checker-cfref -analyze -analyzer-viz-egraph-graphviz /tmp/t.c -analyze-function=f2

From the graph I see three paths through 'f2':

PATH 1

That's correct. It's an excellent way to prune paths. We also try avoiding the cascading of "hard errors"; e.g. dereferencing garbage values, but halting the simulation of a path.

I did wonder if this was the case, cool. I haven't yet found the code
that drives the path analysis (admittedly I haven't looked!) - where
is it?

The code in question is in GRExprEngine.[h, cpp]. That's a fairly complicated beast.

Probably the best place to get a holistic view of the analyzer is AnalysisConsumer.cpp (in the Driver). There you will see how the analyzer API is actually invoked, how checks get registered, when a function gets analyzed, etc. For example, if you search for "GRExprEngine" you'll see where an instance of the path-sensitive engine gets created, executed, and destroyed.