My attempt to write an ACSL predicate to see if an integer is a power of 2 goes like this:
/*#
predicate positive_power_of_2 (integer i) =
i > 0 &&
(i == 1 || ((i & 1) == 0 && positive_power_of_2 (i >> 1)));
*/
However when I added some assert lines into a random function, some Timeout (ie. fail). I don't understand why?
//# assert positive_power_of_2 (1); // Timeout
//# assert positive_power_of_2 (2); // Valid
//# assert positive_power_of_2 (4); // Valid
//# assert !positive_power_of_2 (7); // Timeout
As a side note, for such purely logic properties, you can use lemmas instead of assert, as in //# lemma pow2_1: positive_power_of_2(1);. Since a lemma is a global annotation, it spares you from writing a function just for the sake of holding an assert.
Now back to the issue itself. Mixing bitwise operations with arithmetic ones (the less-than comparison) tends to confuse automated theorem provers. You did not specify which one(s) you use, but if you only used one, you might want to try installing others (nowadays, a mix of alt-ergo, z3 and cvc4 tends to provide good results). That said, a small interactive help to WP's internal simplifier QED is also sufficient: by using the GUI (see section 2.4 of WP manual), you can conclude by just unfolding the definition of positive_power_of_2 in each of the goals (as far as I know, there's no command-line option to do the equivalent).
Basically, once you are in the WP Proofs panel of the GUI, you have to double click in the Script column of the row corresponding to the proof obligation you want to work on, which will let you enter the interactive proof mode, as in the screenshot below:
Now, the point is that the list of available tactics (on the right) is contextual: only the ones that are relevant for the term you have selected in the proof obligation (on the left) are shown. Some tactics are always relevant, such as Cut, which let you prove an auxiliary statement that can be used as an hypothesis in the rest of the proof, but unfolding a definition only makes sense if there's a definition to unfold in your selection. Hence you have to click on P_positive_power_of_2 to let the tactic appear. Afterwards, just click on the corresponding triangle to let WP unfold the definition and attempt to complete the proof afterwards.
Related
I am trying to create my first Isabelle/HOL theory for the algorithm, that computer max integer from the two integers. My intention is to formalize algorithms, this is the initial attempt. That is why I am trying to make my Isabelle code as similar to usual s. c. "industrial programming langauges" or the algol-style code as possible. So - no much use of the default primrec and recursive definitions (with matching and branching) that Isabelle prefers. I came up with the theory:
theory Max_Of_Two_Integers
imports Main
begin
function two_integer_max :: "nat ⇒ nat ⇒ nat"
where
"two_integer_max first second =
(if (first > second) then
return first
else
return second)"
end
But there is error message at function keyword:
Variable "return" occurs on right hand side only:
⋀first second return.
two_integer_max first second =
(if second < first then return first else return second)
I am closely following the pattern of very elaborate algorithm https://isabelle.in.tum.de/library/HOL/HOL-Imperative_HOL/Imperative_Quicksort.html and it used return as expected in usual programming.
So - what is wrong with my return command and how to correct it?
Besides, for this theory to be complete I have to prove termination (theorems about the nature of the function are not mandatory but I will add them).
Isabelle tutorial mainly uses primrec with recursive definition of functions and with pattern matching. I opted for function with the flexibility that comes with it (my guess).
Besides Tutorial, the blog post https://www.joachim-breitner.de/blog/732-Isabelle_functions__Always_total%2C_sometimes_undefined is fantastic guide to functions for Isabelle - but it is strange - it does not provide the example with return keyword, although - as one can see in Imperative_Quicksort example, function and return are valid constructions.
The return is specific to writing imperative algorithms in Imperative HOL (which is what that Quicksort implementation is using). The same goes for the do notation. The reason why you get that error message is that you have not imported Imperative HOL.
Imperative HOL is essentially a library that allows you to write imperative programs that modify a heap. That's why the return type of quicksort in that example is not a list or an array, but a unit Heap. (But even if you had, you would still get an error message, since you are returning a nat and not a Heap).
If you're just starting out with Isabelle, I strongly recommend not to do anything with Imperative HOL for now. Rather, you should start with ‘normal’ Isabelle, which uses a functional style to define things. A good introduction into Isabelle is the first half of the Concrete Semantics book, which is available free online.
We've been using Frama-C for 'experimental' static analysis on a commercial project (integrated into our CI, with a few selective blocking checks, on a small section of the overall codebase).
One of the snags that comes up relates to satisfying the proof obligations that the wp plugin generates anytime it encounters a memcpy call. Specifically, the three obligations below:
From the 'goal' notes, it looks like Frama-C is trying to prove that the destination and source memory are valid, .
I've tried adding requires \valid() preconditions, but that doesn't seem to help. In these instances, the memcpy call within the function under test is copying data from an input parameter to the function, and placing that data into a local variable (scoped within the function).
To further complicate matters, the local variable where the data is being copied is an attribute within a packed struct.
Concretely, I'm hoping that someone out there is able to share some real examples of memcpy uses where the goals introduced by wp can be satisfied (e.g. what preconditions must I add to make it provable?)
If it matters, I'm running Frama-C Magnesium-20151002 (according to apt-get on Ubuntu 16, this is 'up to date'), and invoking with the following parameters:
frama-c -wp -wp-split -wp-dynamic -lib-entry -wp-proof alt-ergo -wp-report
Also related, but missing a clear working example: Frama-c : Trouble understanding WP memory models
As you mentioned in you comment, the proper solution is to use -wp-model "Typed+Cast" in order to let WP accept casts to/from void* (more precisely, it will consider that p and (void*)p are the same thing for any pointer, which will be sufficient for proving the requires of memcpy). Now, as mentioned in the answer to the question you linked to, the main issue of this memory model (and the reason why it is not the default) is that it is inherently unsafe: it relies on hypotheses that by definition cannot be assessed by WP itself. Here is a small example that highlights this issue:
int x;
char* c;
/*# assigns c;
ensures c == ((char *)&x);
*/
void g(void) {
c = &x;
}
/*# assigns \nothing;
ensures \separated(&x,c);
*/
void f() {
}
void main () {
g();
f();
//# assert \false;
}
Basically, the default Typed memory model ensures the separation between the location pointed to by c and x (i.e. the post-condition of f), because int and char are different, and you neither can prove the post-condition of g or use it as an hypothesis to derive \false in main, because the equality can't be expressed in the model at all.
Now, if you use Typed+Cast, the post-condition of g is now properly understood, and completely trivial to prove. WP won't let you prove at the same time that &x and c are separated, because they are involved together in an assignment. However, in f no such assignment exists, and the post-condition is also easily proved, leading to proving \false in main since we have two contradictory statements about &x and c. More generally, WP relies on a local alias analysis to track potential aliases between pointers of different types (a global analysis would defeat the purpose of having a modular analyzer). Passing option -wp-model +Cast can thus be seen as way to tell WP "Trust me, the program won't create miss-typed aliases". It is however possible to pass alias information by hand (or with the help of e.g. a yet-to-be-written global alias detection plug-in). For instance, with option -wp-alias-vars x,c the post-condition of f becomes Unknown (i.e. the separation between &x and c is not an assumption anymore, even for f).
I have seen a lot of documentation about Isabelle's syntax and proof strategies. However, little have I found about its foundations. I have a few questions that I would be very grateful if someone could take the time to answer:
Why doesn't Isabelle/HOL admit functions that do not terminate? Many other languages such as Haskell do admit non-terminating functions.
What symbols are part of Isabelle's meta-language? I read that there are symbols in the meta-language for Universal Quantification (/\) and for implication (==>). However, these symbols have their counterpart in the object-level language (∀ and -->). I understand that --> is an object-level function of type bool => bool => bool. However, how are ∀ and ∃ defined? Are they object-level Boolean functions? If so, they are not computable (considering infinite domains). I noticed that I am able to write Boolean functions in therms of ∀ and ∃, but they are not computable. So what are ∀ and ∃? Are they part of the object-level? If so, how are they defined?
Are Isabelle theorems just Boolean expressions? Then Booleans are part of the meta-language?
As far as I know, Isabelle is a strict programming language. How can I use infinite objects? Let's say, infinite lists. Is it possible in Isabelle/HOL?
Sorry if these questions are very basic. I do not seem to find a good tutorial on Isabelle's meta-theory. I would love if someone could recommend me a good tutorial on these topics.
Thank you very much.
You can define non-terminating (i.e. partial) functions in Isabelle (cf. Function package manual (section 8)). However, partial functions are more difficult to reason about, because whenever you want to use its definition equations (the psimps rules, which replace the simps rules of a normal function), you have to show that the function terminates on that particular input first.
In general, things like non-definedness and non-termination are always problematic in a logic – consider, for instance, the function ‘definition’ f x = f x + 1. If we were to take this as an equation on ℤ (integers), we could subtract f x from both sides and get 0 = 1. In Haskell, this problem is ‘solved’ by saying that this is not an equation on ℤ, but rather on ℤ ∪ {⊥} (the integers plus bottom) and the non-terminating function f evaluates to ⊥, and ‘⊥ + 1 = ⊥’, so everything works out fine.
However, if every single expression in your logic could potentially evaluate to ⊥ instead of a ‘proper‘ value, reasoning in this logic will become very tedious. This is why Isabelle/HOL chooses to restrict itself to total functions; things like partiality have to be emulated with things like undefined (which is an arbitrary value that you know nothing about) or option types.
I'm not an expert on Isabelle/Pure (the meta logic), but the most important symbols are definitely
⋀ (the universal meta quantifier)
⟹ (meta implication)
≡ (meta equality)
&&& (meta conjunction, defined in terms of ⟹)
Pure.term, Pure.prop, Pure.type, Pure.dummy_pattern, Pure.sort_constraint, which fulfil certain internal functions that I don't know much about.
You can find some information on this in the Isabelle/Isar Reference Manual in section 2.1, and probably more elsewhere in the manual.
Everything else (that includes ∀ and ∃, which indeed operate on boolean expressions) is defined in the object logic (HOL, usually). You can find the definitions, of rather the axiomatisations, in ~~/src/HOL/HOL.thy (where ~~ denotes the Isabelle root directory):
All_def: "All P ≡ (P = (λx. True))"
Ex_def: "Ex P ≡ ∀Q. (∀x. P x ⟶ Q) ⟶ Q"
Also note that many, if not most Isabelle functions are typically not computable. Isabelle is not a programming language, although it does have a code generator that allows exporting Isabelle functions as code to programming languages as long as you can give code equations for all the functions involved.
3)
Isabelle theorems are a complex datatype (cf. ~~/src/Pure/thm.ML) containing a lot of information, but the most important part, of course, is the proposition. A proposition is something from Isabelle/Pure, which in fact only has propositions and functions. (and itself and dummy, but you can ignore those).
Propositions are not booleans – in fact, there isn't even a way to state that a proposition does not hold in Isabelle/Pure.
HOL then defines (or rather axiomatises) booleans and also axiomatises a coercion from booleans to propositions: Trueprop :: bool ⇒ prop
Isabelle is not a programming language, and apart from that, totality does not mean you have to restrict yourself to finite structures. Even in a total programming language, you can have infinite lists. (cf. Idris's codata)
Isabelle is a theorem prover, and logically, infinite objects can be treated by axiomatising them and then reasoning about them using the axioms and rules that you have.
For instance, HOL assumes the existence of an infinite type and defines the natural numbers on that. That already gives you access to functions nat ⇒ 'a, which are essentially infinite lists.
You can also define infinite lists and other infinite data structures as codatatypes with the (co-)datatype package, which is based on bounded natural functors.
Let me add some points to two of your questions.
1) Why doesn't Isabelle/HOL admit functions that do not terminate? Many other languages such as Haskell do admit non-terminating functions.
In short: Isabelle/HOL does not require termination, but totality (i.e., there is a specific result for each input to the function) of functions. Totality does not mean that a function is actually terminating when transcribed to a (functional) programming language or even that it is computable at all.
Therefore, talking about termination is somewhat misleading, even though it is encouraged by the fact that Isabelle/HOL's function package uses the keyword termination for proving some property P about which I will have to say a little more below.
On the one hand the term "termination" might sound more intuitive to a wider audience. On the other hand, a more precise description of P would be well-foundedness of the function's call graph.
Don't get me wrong, termination is not really a bad name for the property P, it is even justified by the fact that many techniques that are implemented in the function package are very close to termination techniques from term rewriting or functional programming (like the size-change principle, dependency pairs, lexicographic orders, etc.).
I'm just saying that it can be misleading. The answer to why that is the case also touches on question 4 of the OP.
4) As far as I know Isabelle is a strict programming language. How can I use infinite objects? Let's say, infinite lists. Is it possible in Isabelle/HOL?
Isabelle/HOL is not a programming language and it specifically does not have any evaluation strategy (we could alternatively say: it has any evaluation strategy you like).
And here is why the word termination is misleading (drum roll): if there is no evaluation strategy and we have termination of a function f, people might expect f to terminate independent of the used strategy. But this is not the case. A termination proof of a function rather ensures that f is well-defined. Even if f is computable a proof of P merely ensures that there is an evaluation strategy for which f terminates.
(As an aside: what I call "strategy" here, is typically influenced by so called cong-rules (i.e., congruence rules) in Isabelle/HOL.)
As an example, it is trivial to prove that the function (see Section 10.1 Congruence rules and evaluation order in the documentation of the function package):
fun f' :: "nat ⇒ bool"
where
"f' n ⟷ f' (n - 1) ∨ n = 0"
terminates (in the sense defined by termination) after adding the cong-rule:
lemma [fundef_cong]:
"Q = Q' ⟹ (¬ Q' ⟹ P = P') ⟹ (P ∨ Q) = (P' ∨ Q')"
by auto
Which essentially states that logical-or should be "evaluated" from right to left. However, if you write the same function e.g. in OCaml it causes a stack overflow ...
EDIT: this answer is not really correct, check out Lars' comment below.
Unfortunately I don't have enough reputation to post this as a comment, so here is my go at an answer (please bear in mind I am no expert in Isabelle, but I also had similar questions once):
1) The idea is to prove statements about the defined functions. I am not sure how familiar you are with Computability Theory, but think about the Halting Problem and the fact most undeciability problems stem from it (such as Acceptance Problem). Imagine defining a function which you can't prove it terminates. How could you then still prove it returns the number 42 when given input "ABC" and it doesn't go in an infinite loop?
If instead you limit yourself to terminating functions, you can prove much more about them, essentially making a trade-off (or at least this is how I see it).
These ideas stem from Constructivism and Intuitionism and I recommend you check out Robert Harper's very interesting lecture series: https://www.youtube.com/watch?v=9SnefrwBIDc&list=PLGCr8P_YncjXRzdGq2SjKv5F2J8HUFeqN on Type Theory
You should check out especially the part about the absence of the Law of Excluded middle: http://youtu.be/3JHTb6b1to8?t=15m34s
2) See Manuel's answer.
3,4) Again see Manuel's answer keeping in mind Intuitionistic logic: "the fundamental entity is not the boolean, but rather the proof that something is true".
For me it took a long time to get adjusted to this way of thinking and I'm still not sure I understand it. I think the key though is to understand it is a more-or-less completely different way of thinking.
I have a rather large term foo. When I type
value "foo"
then Isabelle evaluates foo to a value, say foo_value. I would now like to prove the following lemma.
lemma "foo = foo_value"
What proof method should I use? I tried try, but that timed out. I guess I could proceed manually by unfolding the various definitions that occur in foo, but surely I should be able to tap into whatever mechanism the value command is using, right?
There are three proof methods that correspond to the different evaluation mechanisms of value:
eval uses the code generator; it corresponds to value [code]. The proof succeeds if the generated ML code evaluates to True.
normalization compiles the statement to a symbolic normalisation engine in ML. It mimicks value [nbe].
code_simp uses Isabelle's simplifier as an evaluator. It corresponds to value [simp].
The tutorial on code generation describes these proof methods in more detail. eval and normalization act like oracles, i.e., they bypass Isabelle's kernel whereas every evaluation step of code_simp goes through the kernel. Usually, eval is faster than normalization and normalization is faster than code_simp.
I am not sure whether it works in all cases, but you could try:
lemma "foo = foo_value"
by eval
In many cases, by simp should also work and I guess eval is kind of an oracle (in the sense that it is not fully verified by the kernel; please somebody correct me if I am wrong).
Let's say I have a simple function like this:
int all_true(int* bools, int len) {
if (len < 1) return TRUE;
return *bools && all_true(bools+1, len-1);
}
This function can be rewritten in a more obviously tail-recursive style as follows:
int all_true(int* bools, int len) {
if (len < 1) return TRUE;
if (!*bools) return FALSE;
return all_true(bools+1, len-1);
}
Logically, there is zero difference between the two; assuming bools contains only TRUE or FALSE (sensibly defined), they do exactly the same thing.
My question is: if a compiler is smart enough to optimize the second as a tail-recursive call, is it reasonable to expect it to optimize the first in the same way, given that "&&" short-circuits? Obviously, if a non-short-circuiting operator were used, this would not be tail-recursive because both expressions would be evaluated before the operator is even applied, but I'm curious about the short-circuited case.
(Before I get a flood of comments telling me that C compilers don't usually optimize tail-recursive calls: consider this to be a general question about optimizing tail-recursive calls with short-circuit operators, independent of language. I'll be happy to rewrite this in Scheme, Haskell, OCaml, F#, Python, or what the heck ever else for you if you don't understand C.)
Your question is really "how smart is the compiler?" but you don't state which compiler you are using.
Given a hypothetical reasonable compiler which converts source code to an intermediary flow graph before optimizations, both fragments of code that you have written could be represented in the same way (the && operator, while convenient to type, is not nearly as trivially compiled as the & operator; so I wouldn't be surprised if it gets expanded out in one phase on a hypothetical compiler). On that assumption, it is reasonable to assert that the answer to your question is "yes".
However, if you're actually going to rely on this, you should just test it with whatever compiler you happen to be using.