Ghost Context

Introduction

When verifying code, one often needs to introduce lemmas, auxiliary functions, additional fields and parameters, and contracts, whose only function is to specify and prove that some properties are satisfied by the program.

import stainless.lang._
import stainless.lang.collection._
import stainless.lang.annotation._

def isSorted(list: List[BigInt]): Boolean = list match {
  case Nil()                            => true
  case Cons(_, Nil())                   => true
  case Cons(x1, Cons(x2, _)) if x1 > x2 => false
  case Cons(_, xs)                      => isSorted(xs)
}

def sort(list: List[BigInt]): List[BigInt] = {
  /* ... */
} ensuring { res =>
  res.contents == list.contents &&
  isSorted(res) &&
  res.size == l.size
}

One can alleviate this issue by adding the following import:

import stainless.lang.StaticChecks._

Ghost annotation

Correctness check

As part of the verification pipeline, Stainless will check that it never encounters a ghost expression outside of a ghost context. Should the check fail, verification will fail and compilation will be aborted.

A ghost expression is any of:

  • Call to a ghost method
  • Selection of a ghost field
  • Instantiation of a ghost class
  • Reference to a ghost variable

A ghost context as any of:

  • Body of a ghost method
  • Argument to a ghost parameter (method or class constructor)
  • Assignment to a ghost variable
  • Anywhere where a value of type Unit is expected

Ghost expression elimination

If the correctness check described in the previous section succeeds, the sbt plugin will then proceed to eliminate ghost methods and expressions from the programs.

Case study

import stainless.lang._
import stainless.lang.StaticChecks._
import stainless.collection._
import stainless.annotation._

import java.util.ArrayDeque

object MessageQueue {

  case class MsgQueue[A](
    @extern @pure
    queue: ArrayDeque[A],
    @ghost
    var msgs: List[A]
  ) {
    def put(msg: A): Unit = {

      msgs = msg :: msgs
      _put(msg)
    }

    @extern @pure
    private def _put(msg: A): Unit = {
      queue.addFirst(msg)
    }

    def take(): Option[A] = {
      val result = _take()
      msgs = msgs.tailOption.getOrElse(Nil())
      result
    } ensuring { res =>
      res == old(this).msgs.headOption
    }

    @extern @pure
    private def _take(): Option[A] = {
      Option(queue.pollFirst())
    } ensuring { res =>
      res == msgs.headOption
    }

    @extern @pure
    def isEmpty: Boolean = {
      queue.size() == 0
    } ensuring { res =>
      res == msgs.isEmpty
    }
  }

  object MsgQueue {
    @extern @pure
    def empty[A]: MsgQueue[A] = {
      MsgQueue(new ArrayDeque(), Nil())
    } ensuring { res =>
      res.isEmpty && res.msgs.isEmpty
    }
  }

  def test = {
    val queue = MsgQueue.empty[String]

    queue.put("World")
    queue.put("Hello")

    assert(!queue.isEmpty)

    val a = queue.take()
    assert(a == Some("Hello"))

    val b = queue.take()
    assert(b == Some("World"))
    assert(queue.isEmpty)

    val c = queue.take()
    assert(!c.isDefined)
  }
}