@RefinementPredicate

LiquidJava also supports a more general mechanism for declaring ghost functions, through the @RefinementPredicate annotation. This is a more flexible but less user-friendly way to declare ghosts. It allows declaring any ghost function with an explicit signature.

The @Ghost and @StateSet annotations are sugar for the @RefinementPredicate annotation, which create specific kinds of ghost functions with a more concise syntax:

Syntax Sugar Refinement Predicate
@Ghost("int size") @RefinementPredicate("int size(ArrayList)")
@Ghost("boolean open") @RefinementPredicate("boolean open(InputStreamReader)")
@StateSet({"open", "closed"}) @RefinementPredicate("int state1(File)")
state1(this) == 0 <=> open(this)
state1(this) == 1 <=> closed(this)

Ghost Functions

A ghost function is an uninterpreted function used only during verification. In practice, that means:

  • It has a name, parameter types, and a return type
  • It has no implementation
  • The verifier does not compute it from program code
  • The SMT encoding declares it as a function symbol and then reasons only from the constraints that mention it

So a declaration like:

@RefinementPredicate("int size(ArrayList)")

Does not say how size is computed. It only says that, during verification, there exists a function size : ArrayList -> int and the verifier may constrain it with refinements such as:

@StateRefinement(to = "size(this) == 0")

or

@StateRefinement(to = "size(this) == size(old(this)) + 1")

The meaning comes from those constraints, not from a body.

Example

You can model any typestate protocol without using the @StateSet annotation, by declaring a ghost function that represents the state and then using aliases to give names to the different states, which is kind of like how @StateSet works. Under the hood, states are just ghost functions that return an integer representing the current state. For example:

import liquidjava.specification.*;

@RefinementPredicate("int state(File f)")
@RefinementAlias("Open(File f) { state(f) == 0}")
@RefinementAlias("Closed(File f) { state(f) == 1}")
public class File {
    @StateRefinement(to="Open(this)")
    public File() {}

    @StateRefinement(from="Open(this)")
    public void read() {}

    @StateRefinement(from="Open(this)", to="Closed(this)")
    public void close() {}
}

In this case, we must pass the explicit this as an argument to the alias.