Frama-C news and ideas

To content | To menu | To search | Frama-C Home

Tuesday, May 15 2012

On arrays vs. pointers

This post is a follow-up of bug 990. When this issue was resolved the following question arose: "Why is it so dangerous to have a global c declared as a pointer in one compilation unit and defined as an array in another one, given the fact that almost anywhere an array decays as a pointer to their first element when needed?" Well, the key to the answer lies in the *almost*. In fact, if a is an array, the associated pointer expression is &a[0], which means that it is not an lvalue, on the contrary to a pointer variable p. In other words, the compilation unit that sees c as a pointer can assign new values into it, while it is not really expected by the compilation unit where c is defined. Worse, this point cannot be checked by a compiler, since there's no type at linking phase, where the issue could be spotted. Take for instance the following two files: main.c

extern char* c;

extern char f(void);

int main () {
  char *oldc;
  char c1[] = "bar"; 
  c = c1;
  char test = f();
  c = oldc;
  switch (test) {
  case 'o': return 0;
  case 'r': return 1;
  default: return test;
  }
 }

and aux.c

char c [] = "foo";

char f() { return c[2]; }

gcc (Ubuntu/Linaro 4.6.3-1ubuntu5 to be precise) compiles them without any message:

gcc -o test aux.c main.c

Yet, what should be the exit status of test? One could imagine that it might be 0 if the compiler optimizes in f the fact that c is at a constant address, or 1 if it uses the actual address of c (treating it effectively as pointer) when f is called. In fact, on my machine, the exit status is a completely arbitrary number that depends on whatever lies in the memory when test is launched:

virgile@is220177:~/tmp$ ./test
virgile@is220177:~/tmp$ echo $?
98
virgile@is220177:~/tmp$ ./test
virgile@is220177:~/tmp$ echo $?
145

In summary, the future behavior of Frama-C, that will, starting from Oxygen, flatly reject such code (instead of just emitting a warning), is not exceedingly pedantic. Such a discrepancy between declarations is really an important issue.

On ending discussions and painting bikesheds

A high-profile maintainer to a large Open-Source project recently found himself involved in a discussion in which, at some point, after having explained his arguments—I think—very clearly, he had to call someone else a moron. Now everyone with write access to the internet is weighting in on how this is no way to behave and how the bike-shed really should be painted a more friendly color.


Most of the people I have seen opine do so with the confidence of someone who has created something as widely useful as Linux and knows how things are done. Dominik Dabrowski comments “You might have fun raging on the internet, but I think your goals would be better served if ...”. Kenneth Reitz writes a blog post with the title “Be Cordial or Be on Your Way”. If everyone who has expressed their opinion had created as much great software as Linus, this would be a better world. I might even not have to use Mac OS X.


Now, of course, the Github discussion is a little bit difficult to follow, because the comment Linus was replying to has been deleted. A little bit of investigation is needed to find out that he was probably replying to the sentence “I did not realizes that Linus' shit does not stink. Thanks for clearing that up...” (source).

“You're a moron” seems to me a perfectly fine retort to someone who thus butts into a public discussion in which you have already made your technical arguments clear. Plenty of alternatives are also acceptable, of course, including ending the thread. I do not see what the racket over Linus' answer is. But my point is that even if I thought his answer was unacceptable, I would first question whether my opinion should matter, as someone whose contribution is relatively small. Where is the evidence that doing things my way is better? Is there, for instance, anyone in the world with a comparable contribution (especially in terms of visibility) who isn't brutally frank in eir interactions with the thousands of persons ey has to deal with—some of whom, statistically, have to be pretty dim-witted? Perhaps this is the kind of personality one needs to have to build something useful in the first place. Perhaps one becomes more and more direct with each of the million e-mails I roughly estimate one needs to handle in the lifetime of such a project. Where, in other words, is the person that Linus Torvalds could use as role model?


To reiterate, I wish everyone who has been expressing their opinion on how to handle Open Source contributions had written the kind of highly visible, useful software that justifies having an opinion on contribution management. If they had written so much more, so much more useful software that someone like Linus could be on his way without being missed, that would be great.

Saturday, May 12 2012

Life stole my joke—and all I got is this lousy blog post

On a recent occasion to visit the USA on a professional basis, I nearly declined. I had a lengthy explanation ready for my refusal, and it might even have become a rant-tagged blog post. I finally went, so that hypothetical blog post never became. Today, I feel I have been cheated: a really good bit of my explanation would, if I were to re-use it now in another rant, appear unimaginative; stale, even.

See, my explanation of why I didn't want to be a visitor in the USA included a passage on how schools everywhere in the world, at all levels, have bullies, but only in the US these get elected president. (You had to see it in context. There were tie-ins with culture and international politics. It was a beautiful rant)

The reason this bit is suddenly worthless is of course the latest mini-scandal in US election debates. Here is the article through which I first heard of it. My rant was about another person, who may or may not have been US president at some point in eir political career.


It is not my place to comment on foreign presidentiables. The following comments are intended only in the abstract.

  • About the facts themselves, the fact that a brat bullied five homosexual fellow students does not in itself imply that the brat has a bias. If ey similarly bullied 95 heterosexual students, say, forcibly cutting the hair of 19 of them, and if 5% actually is the percentage of the population who recognizes itself as homosexual, ey cannot be blamed for being biased—although ey can still be blamed for being a bully.
  • am I the only one to find “I [didn't think] the fellow was homosexual” a lousy excuse? It does not matter whether one “thinks”. From the point of view of the victim, it shouldn't be necessary to broadcast one's sexual orientation to be morally protected from harassment. A student who takes offence at any visible lifestyle choice of eir colleagues that aren't any of eir business is guilty of intolerance. If it later turns out that a relatively large proportion of eir victims are from a same minority, that's just bad luck for em, because it makes the crime more obvious. But even if the victims did not happen to be from a recognizable minority, the crime would still be the same crime.
  • Finally, the only way I can imagine a kid doing the kind of “hijinks” described in the article and not being sent home for the remainder of the schoolyear is if ey is a very rich kid in a school/university that needs to worry where its next budget is going to come from. Since French Universities have begun to “gain autonomy” under our own previous president, are we now going to have the same system where rich kids get a free pass with discipline, even at the cost of the peace of mind of their fellow students? A few decades from now, will we have to elect them for the highest offices, too?


If told that this post is too off-topic for this blog, I plan to use the “but the link to the original article came from my colleague Eric Eide” defense. That, and something on the general topic of conference attendance and testicle massages.

Tuesday, May 8 2012

Facebook

When I created a Facebook account, I did something that I fully expect will be made a felony before 2030: I filled the inquisitive, mandatory field on the electronic form with false information. You see, Facebook was asking me about my birth date, which it didn't need to know.


There is just one thing I didn't anticipate...


Facebook was asking so that it could then pass the information around, sometimes legitimately to people who might already have known it anyway, and for money to anyone with money.


If you wished me a happy birthday, I am so very sorry, but I was not born on May 8. Please find relief in the thought that this is as embarrassing for me as it is for you.

Monday, April 30 2012

Benchmarking static analyzers

A meta-article on benchmarking

Some colleagues and I are about to submit this article on the subject of benchmarking static analyzers. Rather than another benchmark, it is a list of general recommendations and pitfalls when benchmarking static analyzers. The article is illustrated on C program constructs because that's what we are familiar with, but these examples are only intended to show how complicated static analysis can be in general. Had we focused on another language, the underlying ideas would have remained the same: different users and toolmakers have different views of what static analysis is about. Even if they agreed on what static analysis is about, there would still be no perfect oracle to tell what the right answer to a particular testcase is.

Discussing this article as it was being written, several valid points have been raised, but unfortunately, the limit for the category in which we wish to submit is 4 pages, so I have to address these here. Consider this post a sort of addendum to the article.

Why should the C standard be the ultimate authority?

The short, facetious answer to this question is that the C standard is the democracy of C semantics: it is the worst form of reference document we have, to the exception of all others.


Which version of the C standard?

This is not a big problem in practice. Everyone seems to focus on what compilers use, for obvious reasons, and what compilers use is C90. In Frama-C, we refer to C99 when it is convenient. For instance, in C99, division is defined in more detail than in C90. C90 does not specify whether division rounds towards minus infinity (Euclidean division with a positive remainder) or rounds towards zero (all processors implement division like this, because it means that the sign of the results can be computed independently from their magnitudes, saving transistors). C99 specifies that division rounds towards zero. More information is good, so Frama-C follows C99 here. But when C90 and C99 are mutually exclusive, and compilers all implement C90, we follow the compilers. In this post about subtle typing issues, we are with gcc, predicting 2147483648 for the second example, not with gcc -std=c99 which compiles the program as displaying -2147483648. (See the following post for the explanations behind these differences).


What if most implementations agree with each other but disagree with the standard?

There are a couple of more interesting examples that we intended for the article, to illustrate this very dilemma. One is about pointer comparisons. Technically, the comparison &a < &b is undefined behavior. The same reasoning that, in the article, justifies that analyzers do not have to worry what happens in the program after an illegal pointer access could be applied to this expression.

The point here is not just that most analyzers do not care about this issue, and simply treat the comparison as returning 0 or 1. The point is that there are good reasons for an analyzer to let this kind of comparison slip: it happens naturally when a user implementation of memmove() is passed &a as source and &b as destination. An analyzer that lets this construct slip may be an analyzer that received more testing and for which usability studies revealed that users were too confused when &a < &b was treated as if it halted execution. Since there are arguments both ways, it is difficult for organizers to arbitrarily decide what an analyzer should do when confronted with this expression. And since it does not seem too fundamental, the simple escape that we recommend is to omit the construction from the benchmark. It shouldn't occur in any testcase, not as the goal of the testcase (the undefined behavior to be detected), nor as an incidental construct, that would bias testcases intended for another criterion.


For the sake of exhaustiveness, this can be contrasted to the condition &a == &b + 1. You may expect that this would be undefined, too, but for some reason it is only unspecified. The implementation may return 0 or 1, and it will not return the same value everytime. Derek Jones would point out here that an implementation could rely on just-in-time compilation and that the value of &a == &b + 1 could as a result change from one evaluation to the other, for the same expression referring to the same variables a and b. Anyway, it is not undefined, so if the standard was the absolute reference, static analyzers would not be allowed to consider that execution gets stuck on evaluating &a == &b + 1.

In Frama-C's value analysis, we do not see much reason to treat equality and inequality differently, but we recognize that both stopping execution and continuing with results {0; 1} can have their uses, so we offer an option to choose between these two behaviors. This series of posts already covers this kind of nuance.


Frama-C's value analysis detects, warns and considers execution stuck on uninitialized variables. But by design, it avoids warning (and considering execution stuck) on the statement y = x; or *dst = *src;. This is a compromise, made necessary by the necessity to analyze user implementations of memcpy(), which may use exactly that second statement for uninitialized data when copying a struct or union.


One long-standing bug in Frama-C's front-end is that it erases a possible bug during parsing and normalization. According to the letter of the standard, addressee().a is dangerous. However, this misfeature of the C99 standard has quite a history (in the circle of people who care about this sort of thing), as illustrated by the numerous comments on the linked CERT page. A static analyzer could omit this bug (and consider execution continues) by a conscious design decision (for instance if it specializes in a compiler that documents that this construct is safe). Another static analyzer could treat it as undefined, and consider that execution gets stuck. Again, the construct is not very interesting in itself, so it shouldn't be restrictive to omit it from the explicit goals of the benchmark, and to omit it from the incidental features that might perturb other tests.


Finally, there are areas where the standard under-specifies things. One egregious example is floating-point. David Monniaux's report is still the reference I recommend for these floating-point issues.


All but the last of these examples are cases of “Don't do that, then”: the issue can simply be avoided by not using any of the ambiguous constructs for which it is unclear whether an analyzer should be allowed to consider them blocking. Also, all the issues in these first examples disappear when toolmakers are allowed to participate: when their analyzer scores badly on a test because it is better than others are detecting incidental issues, they will be prompt to point out the problem.

For the last example, floating-point, I do not have a solution to offer. Some real-life software issues come specifically from the liberties that the standard allows and from the fact that compilers take advantage of them, so it is a worthy goal for a static analyzer to try to model these liberties, and a worthy goal for a benchmark to measure how well the static analyzer is doing. But floating-point in C is a mess, and since one of the issues is that compilation is under-specified, it does not suffice to use one (or even several) compilations as reference.


What about implementation-defined behavior?

This is a non-issue, really. All real C programs invoke implementation-defined behavior. As an example, the line int x = 38000; invokes implementation-defined behavior. Therefore, all static analyzers that aim to be useful for real programs assume some implementation choices, and they all tend to assume the compilation choices of the dominant platform, so implementation-defined behavior causes very little trouble in practice.

Acknowledgments

Radu Grigore provided many ideas that did not fit in the article, and is directly responsible for the existence of this blog post.

Saturday, April 28 2012

The value analysis propagation order is inscrutable

Frama-C has a mailing-list. It's a place people visit for free to complain that they are not getting the quality, technical, detailed answers that they deserve, and to tell us what our priorities should be. I expressed my opinion about the mailing list a long time ago in this very blog. It hasn't gotten any better, and I recently unsubscribed. It is liberating. If you are an unsatisfied user, you should, too.

Every once in a while, there seems to be an interesting message, though. My colleagues tell me about these while we wait for the elevator, so not only am I not missing anything, but in addition they no longer have time to launch discussions about the housing market situation in intra-muros Paris. This is what is called a win-win situation.


The recent interesting question in the mailing list is roughly “why does the value analysis behavior change when I add a statement inside nested loops, and how can I get the behavior that gives the best results for what I would like to do?”. So that's two questions.


The answer to the first question is the title of this post. The value analysis works forwards on the control flow graph of the program, and when there are arbitrary decisions to take, such as, in case of a choice, which statements to propagate to next, it propagates according to its whim. When looking at the control flow graph (and it is necessary to handle arbitrary control flow graphs because embedded code can use goto), the initial structure of the program is much less apparent. The notions of “outer loop” and “inner loop” pretty much disappear.

The propagation order is not specified, and may even change between Frama-C versions. The addition of a single statement may also completely change the flow of the analysis inside the program, with no apparent reason. Here, adding one statement probably causes the analysis to re-analyze the outer loop before having converged on the inner loop, causing a slightly inefficient use of the allowed -slevel value—but a large enough -slevel still allows to obtain the precise analysis one wishes.


The answer to the second question is that industrial users with SVN access can already test a new feature that gives users more control over the propagation at the scale of the individual statements. This is in the continuity of the move from a single global -slevel option to -slevel-function options that can be set, ahem, function by function. Oh, and if you are an industrial user and you were still wondering what the new feature was for, well, David's program is another example where it is useful.


David's goal is apparently to get a precise value for variable k outside the loop. When a single unrelated statement is added in the wrong place, it ceases to work:

$ time frama-c ... -slevel 100
...
          k ∈ [34..143]
...
user	0m0.673s

The -slevel option is made less efficient by the additional statement out of bad luck, but it still works. You just have to use more of it (and analysis becomes a little bit slower):

$ time frama-c ... -slevel 620
...
          k ∈ {50}
...
user	0m1.911s

Or, with the development version, and a simple additional annotation inside the source code that could likely be placed automatically:

$ time bin/toplevel.opt ... -slevel 100
...
          k ∈ {50}
...
user	0m1.159s

The latter time is slightly longer, but still close to the time it took with Nitrogen without the additional statement that perturbs propagation (0.72s). Actually, the difference may well be only the price of handling the additional statement.

Thursday, April 12 2012

I discovered another blog

If you like this blog, then on the basis of recent posts, you will with good probability like that blog. The recent posts I have read all deal with various subtle undefined behaviors in C.

A convert-to-floating-point function at the frontier of formal verification

The function in this post to the musl mailing list is a good candidate for formal verification. It should just barely be possible to verify the absence of undefined behaviors with Frama-C's value analysis, and it should just barely be possible to verify it functionally with one of the Jessie or Wp plug-ins.

Context

Musl is a libc project. Key quote: “musl is lightweight, fast, simple, free, and strives to be correct in the sense of standards-conformance and safety”.

The function highlighted in this post converts the decimal representation of a number to a long double in a correctly rounded manner. The long double type is not very portable, and therefore I do not expect any Frama-C plug-in to support it, but after replacing all instances of long double with double and choosing the corresponding LDBL_MANT_DIG and LDBL_MAX_EXP, the function becomes a plain decimal-to-double conversion function, that it should be possible to verify formally with Frama-C.

Motivation

The formal verification I am encouraging the valiant reader to attempt would be useful, too! This is a brand-new implementation. I was looking for one such decimal-to-binary function at about the time I was writing this post. At the time, there were about four categories of Open Source implementations:

  1. the very wrong version, available in Ruby and Tcl to compile on platforms that lack strtod(). This one can return a result off by 15 ULPs or so;
  2. the slightly wrong and somewhat complicated version (Glibc);
  3. the correctly rounded but non-portable and terribly complicated version (David M. Gay's implementation);
  4. and the naïve version (the pseudo-code from Rick Regan that I ended up basing my own function on).


The new function I am suggesting to formally verify nicely fills the gap between 3. and 4. above. It is quite clean and portable, but from a quick code review, I expect it to be faster than, say, my own implementation. It is written to be correctly rounded according to the FPU's rounding mode. It seems to use base-10⁹ multi-precision integers. I am still trying to make sense of parts of it.

Helping the value analysis — part 3

Sven Mattsen is working at CEA until the summer. He is the author of this post. The post continues the series explaining how to guide the value analysis towards more precise conclusions. It starts where that one and that other left off.

Problem

This article is concerned with the Value Analysis plug-in of Frama-C, which implements an automatic approach towards verifying C programs. The Value Analysis provides an overapproximated set of possible values for each occurence of each variable of a C program. I will describe how to guide the value analysis in the example below:

int main(int argc, char* argv[]) {
      int x = argc % 11;
      return 100 / x;
}

We can call the value analysis on this program by issuing frama-c -val smallcprogram.c on the shell. Among other things, the plug-in tells us that x is always in the range from -10 to 10 at the point of the division. Frama-C also warns of a possible division by zero in the argument to return. Great, we had overlooked that. Let us insert a check for this and return 0 in this case.

int main(int argc, char* argv[]) {
       int x = argc % 11;
       if(x != 0)
               return 100 / x;
       else
               return 0;
}

When we call Frama-C again, it gives the same result, including the warning for a possible division by zero. This is despite the division being guarded by the if(x != 0). This time, the alarm is a false alarm.

Explanation

What happened? The Value Analysis plug-in performs an abstract interpretation of the program, with the abstract domain being ranges of integers (the actual abstract domain is more complex, but for this example it is enough to consider this much simpler domain). Apart from the type, the value analysis does not know anything about the variable argc and therefore sets the range of possible values for it to [MININT, MAXINT]. When x is initialized, the set of possible values of the expression argc % 11 is computed, which is [-10,10]. Now it gets interesting. Inside the then block of the if condition, we know that x can have every possible value within [-10,10] except 0. However, in our simplified version of the value analysis it cannot represent [-10,10] \ 0, and therefore overapproximates to [-10,10]. This is precisely the reason why we still get this warning.

Solution

One way to get around this issue is to split the range of possible values of x right before the if condition into two sub-ranges. Luckily, the Value Analysis plug-in allows us to do just that by inserting an assertion:

int main(int argc, char* argv[]) {
       int x = argc % 11;
       /*@ assert x < 0 || x >= 0;*/
       if(x != 0)
               return 100 / x;
       else
               return 0;
}

The plug-in now splits the state propagated through the assert annotation, continues with two independent analyses and later merges the results. To allow this, the parameter -slevel N with N at least two must be passed to Frama-C. With these changes, the range of possible values for the first analysis is [-10,-1] and the second is [0,10]. The first range is not interesting since there is no zero in there and the abstract interpretation will therefore not enter the then block of the if condition. However, it will do this for the second range, but now it can compute a range that includes all of the original elements without zero ([1,10]). Therefore it can now detect that x can not be zero inside the division expression. Everything is fine...

Final thoughts

If you want to track the states of the Value Analysis plug-in, I recommend the special Frama-C functions Frama_C_dump_each() and Frama_C_show_each(). Just insert them in the C file at the position you are interested in.

Thursday, March 29 2012

Why do signed overflows so often do what programmers expect?

Semi-serious musings

During the Frama-C random testing experiment described at length on this blog and this page, we found a few bugs in Csmith, too. John Regehr, one of the Csmith developers and, not entirely coincidentally, a co-author of the article linked in the previous post, is also a co-author in the NFM12 article where the random testing of Frama-C is described. Some time ago, while, in the context of the latter article, discussing the fact that Csmith had been used intensively, finding 400+ bugs in compilers, without the bugs that Frama-C uncovered being an annoyance or even noticed, he said:

It would also maybe be useful to remark that the Csmith bugs had not been detected because compilers are pretty weak at exploiting undefined behaviors (though they do this sometimes).


I do not agree about the choice of the adjective “weak”. I find compilers are pretty good at what they are supposed to do, which is apparently to ignore undefined behaviors in the program and generate the shortest and/or fastest sequences of instructions that compute the correct result for all defined executions. Sometimes, an opportunity arises to exploit the fact that some inputs cause undefined behavior to shorten/fasten the sequence of instructions. But in the general case, with industry-standard instruction sets, there just isn't any such opportunity. The shortest, fastest sequence that works for all defined inputs is the sequence that happens to give 2's complement semantics to signed overflows. I think that this is a fundamental fact of current instruction sets, and considering the rate at which these change, I do not expect programmers misunderstandings about signed overflows to change in the near or far future. Even with much progress in compilation techniques, signed overflow will continue to give 2's complement results most of the times, and programmers will continue to be surprised when it doesn't.

And now for some more Regehr links

This blog post shows how good compilers already are.

I put forward a weak version of the argument presented here on the Frama-C mailing list a long time ago, and John answered with an example C program where the compiler can take advantage of the undefinedness of signed overflows to generate code that does not give 2's complement results.

John also discussed undefined behaviors on his blog, which prompted Chris Lattner to write a 3-post series with more of the compiler implementer's point of view and revealing examples.

- page 1 of 14