Labs 01: Using the Leon Verification System
In this first lab, you will use the web-interface of leon to try to verify programs. You will use an experimental version of the Leon interface available at: http://laraquad1.epfl.ch:9000/sav15
You should also install Leon locally, on your (preferably linux) machine. Please follow the instructions on https://github.com/epfl-lara/leon#building-leon and tell us if something fails.
Labs Web Interface
The web interface for the SAV labs will be directly linked to a git repository. It will allow you to run tests, submit deliverables, receive feedback, etc..
The web interface is located at http://larasrv09.epfl.ch:9000/sav15 (Tequila authentication required.)
Labs will be done in groups of at most 2. Try to form groups of two as much as possible. You will have the choice of either creating a new group or joining an existing group.
- Go to http://larasrv09.epfl.ch:9000/sav15 and authenticate yourself
- The first time you go there, you can either create a group or wait until somebody created a group with you.
- Once you are within a group, go to the YourName > SSH Keys tab, and register your Public SSH key. Make sure you copy your entire key, including the `ssh-rsa` prefix and the key identifier at the end.
Deliverable
You are given 1 week for this assignment.
Deadline: Sunday, Mar. 1, 25:59.
You must use the interface on http://larasrv09.epfl.ch:9000/sav15/repository as follows:
- Click on the “Deliverables” tab
- Click “Deliver this version” on the commit corresponding to the version of your code you want to submit
Exercise 1
Try to verify the putAside function. When a function fails to verify, you can explore the counter-example by clicking on various relevant parts of the invalid functions. You can also click on the red X-mark to open the verification result details. Look at the counter-example returned; what is wrong with this function?
Fix the function so that Leon succeeds in showing the function correct.
object Bank1 { case class Acc(checking : BigInt, savings : BigInt) def putAside(x: BigInt, a: Acc): Acc = { require (notRed(a) && a.checking >= x) Acc(a.checking - x, a.savings + x) } ensuring { r => notRed(r) && sameTotal(a, r) } def sameTotal(a1: Acc, a2: Acc): Boolean = { a1.checking + a1.savings == a2.checking + a2.savings } def notRed(a: Acc) : Boolean = { a.checking >= 0 && a.savings >= 0 } }
Exercise 2
Implement isNNF so that it returns true on NNF formulas, and false otherwise. A correct implementation of isNNF should allow vars to verify.
object PropositionalLogic { sealed abstract class Formula case class And(lhs: Formula, rhs: Formula) extends Formula case class Or(lhs: Formula, rhs: Formula) extends Formula case class Implies(lhs: Formula, rhs: Formula) extends Formula case class Not(f: Formula) extends Formula case class Literal(id: BigInt) extends Formula def nnf(formula: Formula): Formula = (formula match { case And(lhs, rhs) => And(nnf(lhs), nnf(rhs)) case Or(lhs, rhs) => Or(nnf(lhs), nnf(rhs)) case Implies(lhs, rhs) => nnf(Or(Not(lhs), rhs)) case Not(And(lhs, rhs)) => Or(nnf(Not(lhs)), nnf(Not(rhs))) case Not(Or(lhs, rhs)) => And(nnf(Not(lhs)), nnf(Not(rhs))) case Not(Implies(lhs, rhs)) => And(nnf(lhs), nnf(Not(rhs))) case Not(Not(f)) => nnf(f) case Not(Literal(_)) => formula case Literal(_) => formula }) ensuring(isNNF(_)) def isNNF(f: Formula): Boolean = f match { /* TODO: Implement isNNF */ } // Note that matching should be exhaustive due to precondition. def vars(f: Formula): Set[BigInt] = { require(isNNF(f)) f match { case And(lhs, rhs) => vars(lhs) ++ vars(rhs) case Or(lhs, rhs) => vars(lhs) ++ vars(rhs) case Not(Literal(i)) => Set[BigInt](i) case Literal(i) => Set[BigInt](i) } } }
Exercise 3
- Add pre- and postconditions, or fix the code such that the following functions verify: zip, sizesAreEquiv, reverse. The postconditions on these functions give what should be checked, i.e. they should not be changed.
- find out what the function drunk does, and provide a correct postcondition that relates the sizes of the input list with the output list
- the function appendAssoc is meant to prove that the appending lists is associative. Formulate this predicate and make sure Leon can verify it.
import leon.lang._ import leon.annotation._ object ListWithSize { sealed abstract class List[T] { def size: BigInt = { this match { case Cons(h, t) => 1 + t.size case Nil() => 0 } } // TODO: Add appropriate post-condition def sizeTailRec: BigInt = sizeTailRec0(0) def sizeTailRec0(acc: BigInt): BigInt = { require(acc >= 0) this match { case Cons(h, t) => t.sizeTailRec0(acc + 1) case Nil() => acc } } // TODO: Add appropriate post-condition // Verify def zip[U](that: List[U]): List[(T, U)] = { // TODO: Add appropriate pre-condition this match { case Nil() => Nil() case Cons(h1, t1) => that match { case Cons(h2, t2) => Cons((h1, h2), t1.zip(t2)) } } } ensuring(_.size == this.size) def content: Set[T] = this match { case Nil() => Set() case Cons(h, t) => Set(h) ++ t.content } // Verify def reverse: List[T] = { reverse0(Nil()) } ensuring(_.content == this.content) def reverse0(acc: List[T]): List[T] = (this match { case Nil() => acc case Cons(h, t) => t.reverse0(Cons(h, acc)) }) // TODO: Add appropriate post-condition def append(that: List[T]): List[T] = (this match { case Nil() => that case Cons(h, t) => Cons(h, t.append(that)) }) ensuring(_.content == this.content ++ that.content) } case class Cons[T](head: T, tail: List[T]) extends List[T] case class Nil[T]() extends List[T] // Verify def sizesAreEquiv[T](l: List[T]): Boolean = { l.size == l.sizeTailRec }.holds def sizeAndContent[T](l: List[T]): Boolean = { l.size == 0 || l.content != Set[T]() }.holds def drunk[T](l: List[T]): List[T] = (l match { case Nil() => Nil() case Cons(x,l1) => Cons(x,Cons(x,drunk(l1))) }) ensuring { res => true // TODO: find postcondition } def funnyCons[T](x: T, l: List[T]): List[T] = (l match { case Nil() => Cons(x, Nil()) case c @ Cons(_,_) => Cons(x, c) }) ensuring(_.size > 0) @induct def nilAppend[T](l: List[T]): Boolean = { l.append(Nil()) == l }.holds @induct def appendAssoc[T](xs: List[T], ys: List[T], zs: List[T]) : Boolean = { false // find predicate }.holds @induct def sizeAppend[T](l1: List[T], l2 : List[T]) : Boolean = { (l1.append(l2).size == l1.size + l2.size) }.holds }
Exercise 4
Add specifications so that sort verifies.
import leon.annotation._ import leon.lang._ object InsertionSort { sealed abstract class Option[T] case class Some[T](value: T) extends Option[T] case class None[T]() extends Option[T] sealed abstract class List { def size: BigInt = (this match { case Nil() => 0 case Cons(_, t) => 1 + t.size }) def content: Set[BigInt] = this match { case Nil() => Set() case Cons(h, t) => Set(h) ++ t.content } def min: Option[BigInt] = this match { case Nil() => None() case Cons(h, t) => t.min match { case None() => Some(h) case Some(m) => if(h < m) Some(h) else Some(m) } } def isSorted: Boolean = this match { case Nil() => true case Cons(h, Nil()) => true case Cons(h1, t1 @ Cons(h2, t2)) => h1 <= h2 && t1.isSorted } /* Inserting element 'e' into a sorted list 'l' produces a sorted list with * the expected content and size */ def insert(e: BigInt): List = { require(isSorted) this match { case Nil() => Cons(e, Nil()) case Cons(h, t) => if (h <= e) { Cons(h, t.insert(e)) } else { Cons(e, this) } } } /* Insertion sort yields a sorted list of same size and content as the input * list */ def sort: List = (this match { case Nil() => Nil() case Cons(h, t) => t.sort.insert(h) }) ensuring(res => res.content == this.content && res.isSorted && res.size == this.size ) } case class Cons(h: BigInt, t: List) extends List case class Nil() extends List }
Exercise 5
Add a post-condition to firstPosOf and see comment in function wtf.
import leon.lang._ import leon.annotation._ object SearchList { sealed abstract class List[T] { def size: BigInt = { this match { case Nil() => 0 case Cons(h, t) => 1 + t.size } } ensuring { _ >= 0 } def content: Set[T] = { this match { case Nil() => Set() case Cons(h, t) => Set(h) ++ t.content } } def firstPosOf(v: T): BigInt = { this match { case Nil() => -1 case Cons(h, t) if h == v => 0 case Cons(h, t) => val p = t.firstPosOf(v) if (p >= 0) { p + 1 } else { p } } } ensuring { res => false // TODO: Ensure that if (and only if) res is non-negative, the list contains v. } def take(n: BigInt): List[T] = { require(n >= 0) this match { case Nil() => Nil() case Cons(h, t) if n == 0 => Nil() case Cons(h, t) => Cons(h, t.take(n - 1)) } } ensuring { res => res.size <= n } def contains(v: T): Boolean = { this match { case Nil() => false case Cons(h, _) if h == v => true case Cons(_, t) => t.contains(v) } } ensuring { res => res == (content contains v) } } case class Cons[T](h: T, t: List[T]) extends List[T] case class Nil[T]() extends List[T] @induct def wtf[T](l: List[T], v: T): Boolean = { !((l.contains(v)) && (l.take(l.firstPosOf(v)).contains(v))) // What is this function checking? Translate to english. Can you remove the l.contains(v) part? Why? }.holds }