Frama-C: Modular Analysis of C Programs
frama-c.com
frama-c.com
I've used Frama-C ACSL+WP and it was incredibly painful to use to prove basically anything.
For me the main issue was that Frama-C can say 3 things about your specs: Yes, No, and Don't know. That means I have no idea where to start to debug my proof, especially as I'm already convinced that my proof works! This is inherent to computing weakest pre-condition AFAIK. I had to provide my own loop invariants, which is also a pain :-).
C's semantics also makes a lot of things which I would assume was "trivially true" fail verification. This isn't Frama-C's fault however, of course.
I haven't used the other plugins, perhaps there are better ones!
Type systems can be quite nice with regards to error messages, especially as the programmer themselves essentially derive their own granularity with regards to the domain and the proofs of the domain. But yes, we all have examples of absolutely terrible type system error messages.
The paradigm of making comments have semantic meaning in some other language is also terrible. I'd rather have a superset of the core language with syntactic extensions for proofs. The build system can pull out the core language source code for me.
Finally:
I think that abstract interpretation of a low-level compilation target combined with a proof-carrying compiler is the way to go. The compiler has proofs of a bunch of stuff regarding the code already, carry them down into the assembly level please! There's some work in abstract interpretation of WebAssembly, and I think that could be a great platform for formal verification.
Sorry for the barfing :-). Hopefully there're thoughts here to react to and reply to me about!
C's semantics also makes a lot of things which I would assume was "trivially true" fail verification. This isn't Frama-C's fault however, of course.
sounds really interesting, do you remember some concrete example? Not saying you're wrong or anything, just curious of what kind of code is hard to analyze like this.
∀a: a+1 > a
if the type of 'a' is 32 bit int. Similarly negate(a) ≠ -a for some numbers. function Increment(A : Integer) return Integer
with Post => Increment'Result = A + 1;
It would find the counterexample for the situation where A is equal to Integer'Last. So you'd need to add a precondition like: function Increment(A : Integer) return Integer
with Pre => A < Integer'Last,
Post => Increment'Result = A + 1;
(doing this from memory and not fluent, so syntax may be off but the idea isn't) /*@ ensures \result > a; */
int increment(int a) {
return a + 1;
}
you would change it to: /*@ requires a < INT_MAX;
ensures \result > a; */
int increment(int a) {
return a + 1;
}
But if this function has N call sites, you now have N problems: At every call site you must prove that the precondition holds."iff a =/= MAXINT"
You either make your spec up as you go along and don't check it as its hidden in the code, or provide it so that static analysis tools and/or things like tla+ can help confirm. There never was any getting around that regardless of language. The code follows from spec which both follow from requirements
for(int i = 0; i < n; i++) {..}
It looks like the invariant should be: 0 <= i < n
But if they mistyped the loop that would be wrong. You have to explicitly state your invariants. At best, you could detect them and ask for confirmation. And that's for an easy to detect case.Most of the time with Frama-C, all you have to provide is function pre- and post-conditions and loop invariants.
``` 0 <= i <= n ```
As the loop reaches this value to terminate. Else, we could not deduce for example that `i = n` at the end of the loop (`0 <= i <= n && !(i < n)`).
If I was proving that 0 + 1 + .. n = n(n+1)/2, I wouldn't say that whatever induction step I chose to use was "part of the theorem", would I?
In general I've found deductive verification techniques interesting / promising because they free engineers of a lot of required but tedious details you'd have in ITPs.
However, I think there's a LOT of room for improvement in terms of ergonomics of proof debugging. For a frequent (for me) problem when debugging invariants is conditionals that break the invariant.
if i have some code doing something like
while (X) {
invariant { forall i. 0 <= i < N .... }
if j < i A else B
}
But it turns out that one of the branches A, B doesn't preserve the invariant well all the provers will tell me is 'can't prove this!' it's up to you to perform the transformations that split the two cases (granted in this example it's trivial) so that you can see that only _one_ branch was failing.I think that there should be transforms that automatically do things like split the range of an interval along relevant points (aka j) to help you figure out which portions are failing.
There are tons of other issues related to proof ergonomics that could be improved, the UIs are really stuck in the 90s!
There are plugins for multi-thread programs.
One is an experimental plugin called Conc2Seq that I developed during my thesis with a focus on proving properties about small modules with a few functions accessing concurrently some global resources. Note however hat it is very experimental and it is not actively developed anymore, but at least it can be updated or give some ideas on how to write such an analyzer.
The second is a proprietary plugin called MThread (base on the Eva analysis of Frama-C), but it is only available via licensing.
Eva (the abstract interpreter) has different ways to model dynamic allocation. It is thus a tradeoff to find between precision and computation time.
WP does not have dynamic allocation support. This is an ongoing work, but it will not be available in the next release. Note that there are different ways to model the behavior of dynamic allocation, generally via axiomatic definitions and/or ghosts.
Applied Formal Logic: Brute Force String Search https://maniagnosis.crsr.net/2017/06/AFL-brute-force-search....
Applied Formal Logic: The bug in Quick Search. https://maniagnosis.crsr.net/2017/06/AFL-bug-in-quicksearch....
Applied Formal Logic: Correctness of Quick Search. https://maniagnosis.crsr.net/2017/07/AFL-correctness-of-quic...
That's because Frama-C is meant to be used on highly critical infrastructures (think nuclear plants) even in highly difficult times (think world war).
The number of dependencies for my current project is four, and each of those (vexflow, tone.js, lovefield, jquery) I'm in principle prepared to maintain myself. Even so, all of them have already proven to be here for the longer term. Ditto for tooling.
This is important, because as much as some of us would like to nuke those languages, they aren't going away and plenty of domains are not going to move away from them anyway.
However there is only so much that one can improve without changing their semantics.
And if you start changing their semantics, then you end up with what is effectively another language, e.g. Checked C.
To speak of Frama-C specifically, it isn't competing with new languages like Zig and Nim, it's competing with rival tools like [0], and with SPARK Ada. Formal reasoning about code is, for now, a niche reserved for critical-systems development. That kind of work tends to use tried-and-true languages with tried-and-true tooling: C, C++, Ada, and occasionally even Java.
> the languages we did design would be designed to make it easy to make tools that help us use them.
This is already a factor in language design. It's one of the reasons C-style preprocessors are now unfashionable.