Why3 — Logic
The Why3 Platform
Why3 is targeted at deductive formal verification of algorithms and programs.
The most important conceptual components of the platform are the following:
-
WhyML, a functional-imperative programming language of the ML family
-
A logic language, which is used to write properties and annotations concerning the WhyML programs. These include preconditions, postconditions, loop invariants, variants, asserts …
-
A standard library containing many useful logical theories and data structures, including arithmetics, sets, lists, and maps (dictionaries).
The Why3 tool implements the following functionality:
-
Generation of verification conditions **(VCs): taking a program and a specification for it, the tool generates a set of first-order proof obligations that are sufficient to prove the correctness of the program: if all the VCs are valid then the program is guaranteed to be correct w.r.t its specification
-
Interaction with proof tools: Why3 has interfaces allowing it to use as background every popular first-order proof tool (including SMT solvers and interactive theorem provers). The tool will send proof obligations to any tool available in a given installation
-
Additionally, the Why3 IDE offers very useful functionality including syntax highlighting (very useful to associate VCs with the parts of the code from which they were generated), convenient access to proof transformations, and proof management (including storing and replaying proof sessions)
Note
Previously in this course the Alloy tool was also used for formal verification. However, in Alloy the word verification is used as a synonym for model checking (algorithmic state space exploration), whereas in Why3 it is based on actual deductive proofs. Moreover, Alloy targets systems specified at an abstract level, whereas Why3 is used to verify algorithms
Why3 as a Proof Tool
Although this was probably not the original intention, Why3 has become popular as a proof tool on its own (its logic functionality is also accessible through an API).
This is due to the extremely convenient interfacing with external proof tools, and also to the features of the logic language, which extends standard FOL with a set of features that are highly convenient for applications in computer science.
The logic language includes:
-
user-defined types, including algebraic (inductive), and polymorphic types
-
defined predicates and functions, including inductive and polymorphic definitions
-
inductive predicates, defined in a closed way by a set of inference rules
-
a rich logic library of theories, including (but not only) types that are particularly suited in the context of computer programming
It is possible, for instance, to define a type of binary trees and then define a recursive predicate stating when such a tree is a binary search tree (i.e. it is sorted inorder).
Example: Guest Placement
(from Lecture **2 on propositional logic)
We have three chairs in a row and need to place Anne, Susan and Peter
- Anne does not want to sit next to Peter
- Anne does not want to sit in the left chair
- Susan does not want to sit to the left of Peter
Implicit generic constraints
- Everyone must sit on a chair (at least one chair per guest) No need for “at most one chair per guest”, since the numbers of chairs and people are the same
- No more than one guest per chair (at most one guest per chair)
We will create a module containing
- propositional variable declarations (
predicate
) - axioms
-
goals
module GuestPlacement predicate anneLeft predicate anneMiddle predicate anneRight predicate susanLeft predicate susanMiddle predicate susanRight predicate peterLeft predicate peterMiddle predicate peterRight
(* At least one chair per guest *) axiom anneSits : anneLeft \/ anneMiddle \/ anneRight axiom susanSits : susanLeft \/ susanMiddle \/ susanRight axiom peterSits : peterLeft \/ peterMiddle \/ peterRight (* At most one guest per chair *) axiom maxLeft : not ((anneLeft /\ susanLeft ) \/ (anneLeft /\ peterLeft ) \/ (susanLeft /\ peterLeft )) axiom maxMiddle : not ((anneMiddle /\ susanMiddle) \/ (anneMiddle /\ peterMiddle) \/ (susanMiddle /\ peterMiddle)) axiom maxRight : not ((anneRight /\ susanRight ) \/ (anneRight /\ peterRight ) \/ (susanRight /\ peterRight )) (* Preferences *) axiom annePrefs1 : anneRight -> not peterMiddle axiom annePrefs2 : anneMiddle -> not peterLeft /\ not peterRight axiom annePrefs3 : not anneLeft axiom susanPrefs1 : peterRight -> not susanMiddle axiom susanPrefs2 : peterMiddle -> not susanLeft (* Does this problem have a solution? Yes if the following goal cannot be proved *) goal PlacementNotPossible: false (* Is it true that Susan can ONLY sit on the Middle chair? *) goal sitsSusanRight : susanMiddle (* Is it true that Peter cannot sit on the Right? *) goal sitsAnneRight : not peterRight
end
[Try this example online here]
Unlike a SAT solver, Why3 works with validity rather than satisfiability. The above module contains three goals; Why3 will generate a proof obligation for each one of them, that will be encoded and dispatched to an external proof tool.
Proving that a goal follows from the axioms is a consequence problem; it will be encoded as a satisfiability problem consisting of the axioms together with the negation of the goal.
Recall that a satisfiability solver may return one of three possible outcomes:
-
sat (Satisfiable) There exists at least one assignment (set of values for the variables) that makes the problem true. Thus the original formula is invalid : the proof goal is not a consequence of the theory defined by the axioms.
-
unsat (Unsatisfiable) No satisfying assignment exists for the problem (the constraints cannot all be simultaneously satisfied). Thus the original formula is valid : the proof goal is a consequence of the theory defined by the axioms.
-
unknown The solver is unable to determine whether the problem is satisfiable or not within the given time/memory bounds Thus it is unknown whether the proof goal is a consequence of the theory or not.
For instance the second goal **goal**
sitsSusanRight : susanMiddle
states that it follows from the axioms that Susan can only sit on the rightmost chair. If it is dispatched to Z3, the solver will receive a file ending with the following lines:
( ... )
;; "susanPrefs2"
(assert (=> peterMiddle (not susanLeft)))
;; Goal "sitssusanRight"
;; File "/Users/jorgesousapinto/Documents/Teaching/2o.Ciclo/WHY3/2024-MFES/PUZZLES/guestPlacement_prop.mlw", line 62, characters 7-21
(assert (not susanMiddle))
(check-sat)
Why3 includes a number of automated strategies that will attempt to prove goals by performing simple proof transformations (such as splitting goals into smaller formulas) and sending the resulting proof obligations in turn to several SMT solvers, if more than one is available.
The online version of Why3 contains a single SMT solver (Alt-Ergo). It allows the user to split goals and increase the time limit allowed to the solver for each goal. It does not allow for counter-examples to be inspected.
The purpose of the ```goal
PlacementNotPossible: false` , which should be invalid, is to establish that the theory defined by the previous axioms is consistent.
We can ask Why3 to display a counterexample for goals that cannot be proved. Counterexamples are simply models for the satisfiability problems sent to solvers (the theory together with the negation of a given goal).
A model for this satisfiability problem is a counterexample for the original validity problem.
In the above example, for instance the Z3 solver will find a counter-example, which corresponds to a model, i.e. an actual assignment of teachers to classes. It can be visualized as follows:
predicate anneLeft (* anneLeft : = false *)
predicate anneMiddle (* anneMiddle : = false *)
predicate anneRight (* anneRight : = true *)
predicate susanLeft (* susanLeft : = false *)
predicate susanMiddle (* susanMiddle : = true *)
predicate susanRight (* susanRight : = false *)
predicate peterLeft (* peterLeft : = true *)
predicate peterMiddle (* peterMiddle : = false *)
predicate peterRight (* peterRight : = false *)
Example: Guest Placement — first-order encoding
Why3’s logic language is many-sorted and first-order. In the following version we make use of:
- enumerated (algebraic) types for chairs and persons. Equality predicates on these types are automatically available.
- a binary predicate
**predicate**
sits person chair
-
axioms involving universal and existential quantifiers
module GuestPlacement type person = Anne | Susan | Peter type chair = Left | Middle | Right predicate sits person chair
(* At least one chair per guest *) axiom everybodySits : forall p:person. exists c :chair. sits p c (* At MOST one chair per guest -- if there were more chairs than guests *) axiom maxPerson : forall p :person, c1 c2 :chair. sits p c1 /\ sits p c2 -> c1=c2 (* At most one guest per chair *) axiom maxChair : forall c:chair. forall p1 p2 :person. sits p1 c /\ sits p2 c -> p1=p2 (* preferences *) axiom AnnePrefs1 : sits Anne Right -> not (sits Peter Middle) axiom AnnePrefs2 : sits Anne Middle -> not (sits Peter Left) /\ not (sits Peter Right) axiom AnnePrefs3 : not (sits Anne Left) axiom SusanPrefs1 : sits Peter Right -> not (sits Susan Middle) axiom SusanPrefs2 : sits Peter Middle -> not (sits Susan Left) (* Does this problem have a solution? Yes if the following goal cannot be proved *) goal PlacementNotPossible: false (* Is it true that Susan can ONLY sit on the Middle chair? *) goal sitsSusan : sits Susan Middle (* is it true that Peter cannot sit on the Right? *) goal sitsPeter : not (sits Peter Right) end
[Try this example online here]
Example: Guest Placement — first-order encoding with functions
In this third version we additionally make use of
- a logic function
The idea is that the function sits
associates a chair to each person, so the “at least one chair” and “at most one chair” requirements are no longer needed. The function must be injective in order for people to sit on different chairs.
module GuestPlacement
type person = Anne | Susan | Peter
type chair = Left | Middle | Right
function sits person : chair
(* At most one guest per chair *)
axiom inj : forall p1 p2 :person. p1 <> p2 -> sits p1 <> sits p2
(* Preferences *)
axiom AnnePrefs1 : sits Anne = Right -> sits Peter <> Middle
axiom AnnePrefs2 : sits Anne = Middle -> sits Peter <> Left /\ sits Peter <> Right
axiom AnnePrefs3 : not (sits Anne) = Left
axiom SusanPrefs1 : sits Peter = Right -> sits Susan <> Middle
axiom SusanPrefs2 : sits Peter = Middle -> sits Susan <> Left
(* Does this problem have a solution?
Yes if the following goal cannot be proved *)
goal PlacementNotPossible: false
(* Is it true that Susan can ONLY sit on the Middle chair? *)
goal sitsSusanRight : sits Susan = Middle
(* is it true that Peter cannot sit on the Right? *)
goal sitsAnneRight : sits Peter <> Right
end
[Try this example online here]
Example: File System Domain Model
We will use types Object, Entry, and Name. The specialized domains Dir, File, and Root will be captured as predicates (since “subtyping” is not supported in many-sorted logic).
In fact, since there is a single Root it makes sense to model Root as a constant, so we no longer need the axioms:
∃x. Root(x)
∀x, y. Root(x) ∧ Root(y) → x = y
Additionally, the following axioms do not make sense in this typed formalization, since types are disjoint by nature:
∀x. ¬(Object(x) ∧ Entry(x))
∀x. ¬(Object(x) ∧ Name(x))
∀x. ¬(Name(x) ∧ Entry(x))
Also, from the following constraints that were required as axioms in the untyped / Alloy setting:
∀x, y. contains(x, y) → Dir(x) ∧ Entry(y)
∀x, y. refersTo(x, y) → Entry(x) ∧ Object(y)
∀x, y. has(x, y) → Entry(x) ∧ Name(y)
In the typed setting we will require only ∀x, y. contains(x, y) → Dir(x). The others will be typing constraints.
It is important to clarify the meaning of ∀x : A. ϕ in Alloy and in many-sorted logic
-
In Alloy, untyped, A is a predicate and this is just syntactic sugar: ∀x : A. ϕ ≡ ∀x. A(x) → ϕ
-
Whereas in the typed setting (and Why3’s logic), ∀x : A denotes quantification over inhabitants of type A
module FileSystem type object type entry type name predicate dir (object) predicate file (object) constant root : object
axiom spec1 : forall o :object. not (dir o /\ file o) axiom spec2 : forall o :object. dir o \/ file o axiom spec3 : dir root (* Associations *) predicate has (entry) (name) predicate contains (object) (entry) predicate refersTo (entry) (object) (* Typing *) axiom type1 : forall o :object, e :entry. contains o e -> dir o (* Multiplicity *) axiom multHas : forall e :entry. (exists n :name. has e n) /\ (forall n1 n2 :name. has e n1 /\ has e n2 -> n1 = n2) axiom multRefersTo : forall e :entry. (exists o :object. refersTo e o) /\ (forall o1 o2 :object. refersTo e o1 /\ refersTo e o2 -> o1 = o2) axiom multContains : forall e :entry. (exists o :object. contains o e) /\ (forall o1 o2 :object. contains o1 e /\ contains o2 e -> o1 = o2) (* Different entries in the same directory have different names *) axiom req1 : forall o :object, e1 e2 :entry, n :name. contains o e1 /\ contains o e2 /\ e1 <> e2 -> not (has e1 n /\ has e2 n) (* All objects except the root are referred to in at least one entry *) axiom req2 : forall o :object. o <> root -> exists e :entry. refersTo e o (* at most one for the case of directories *) axiom req3 : forall o :object. dir o /\ o <> root -> forall e1 e2 :entry. refersTo e1 o /\ refersTo e2 o -> e1 = e2 (* The root is not reffered to *) axiom req4 : forall e :entry. not refersTo e root (* Is it a consequence of the above axioms that all files are contained in the root? *) lemma allInRoot : forall f :object. file f -> exists e :entry. refersTo e f /\ contains root e (* Is it a consequence of the above axioms that, if the root is the only directory, then all files are contained in it? *) lemma onlyRoot : (forall d :object. dir d -> d = root) -> forall f :object. file f -> exists e :entry. refersTo e f /\ contains root e goal inconsistency : false end
[Try this example online here]
Exercise: File System Model with functions
(Total) functions are used for the three associations, since all of them have a unicity constraint. This leads to a more compact model, since the multiplicity constraints do not have to be written as axioms:
module FileSystem
(...)
(* Other associations *)
function has (entry) : name
function container (entry) : object
function refersTo (entry) : object
(* Typing *)
axiom type1 : forall e :entry. dir (container e)
(* Different entries in the same directory have different names *)
axiom req1 : forall e1 e2 :entry.
e1 <> e2 /\ container e1 = container e2 ->
has e1 <> has e2
(...)
end
Complete this model.
Exercise: Photo Sharing Social Network Domain Model
Why3 offers a standard library of theories and data structures that can be used across all the supported proof tools.
We’ll take the above model as an opportunity to introduce the Why3 set theory. The idea will be to model the associations as functions to sets, rather than predicates.
module SocialNetwork
use set.Set
(* Entities *)
type user
predicate isInfluencer (user)
type photo
predicate isAd (photo)
type day
(* Associations *)
function follows (user) : set user
function sees (user) : set photo
function posts (user) : set photo
function suggested (user) : set user
function date (photo) : day (* no further multiplicty constraints are needed! *)
(...)
end
Finish the above model. You can find the available set predicates and functions in the Why3 library, but you can probably solve the exercise using just the membership predicate:
predicate mem (x: 'a) (s: set 'a)