|
| 1 | +--- |
| 2 | +title: Refinement Predicates |
| 3 | +parent: Concepts |
| 4 | +nav_order: 6 |
| 5 | +permalink: /concepts/refinement-predicates/ |
| 6 | +description: Learn how to declare custom ghost functions and how they relate to ghosts and states. |
| 7 | +--- |
| 8 | + |
| 9 | +# Refinement Predicates |
| 10 | + |
| 11 | +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. |
| 12 | + |
| 13 | +The `@Ghost` and `@StateSet` annotations are sugar for the `@RefinementPredicate` annotation, which create specific kinds of ghost functions with a more concise syntax: |
| 14 | + |
| 15 | +| Syntax Sugar | Refinement Predicate | |
| 16 | +| --- | --- | |
| 17 | +| `@Ghost("int size")` | `@RefinementPredicate("int size(ArrayList)")` | |
| 18 | +| `@Ghost("boolean open")` | `@RefinementPredicate("boolean open(InputStreamReader)")` | |
| 19 | +| `@StateSet({"open", "closed"})` | `@RefinementPredicate("int state1(File)")`<br>`state1(this) == 0 <=> empty(this)`<br>`state1(this) == 1 <=> full(this)` | |
| 20 | + |
| 21 | +In the current implementation, `@RefinementPredicate` is only supported on methods and constructors, but it is not currently scoped to the annotated method or constructor. Instead, is a global declaration that is available for the whole verification. |
| 22 | + |
| 23 | +## What's a Ghost Function? |
| 24 | + |
| 25 | +A ghost function is an uninterpreted function used only during verification. In practice, that means: |
| 26 | + |
| 27 | +- It has a name, parameter types, and a return type |
| 28 | +- It has no implementation |
| 29 | +- The verifier does not compute it from program code |
| 30 | +- The SMT encoding declares it as a function symbol and then reasons only from the constraints that mention it |
| 31 | + |
| 32 | +So a declaration like: |
| 33 | + |
| 34 | +```java |
| 35 | +@RefinementPredicate("int size(ArrayList)") |
| 36 | +``` |
| 37 | + |
| 38 | +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: |
| 39 | + |
| 40 | +```java |
| 41 | +@StateRefinement(to = "size(this) == 0") |
| 42 | +``` |
| 43 | + |
| 44 | +or |
| 45 | + |
| 46 | +```java |
| 47 | +@StateRefinement(to = "size(this) == size(old(this)) + 1") |
| 48 | +``` |
| 49 | + |
| 50 | +The meaning comes from those constraints, not from a body. |
| 51 | + |
| 52 | +## Example |
| 53 | + |
| 54 | +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 current the state. For example: |
| 55 | + |
| 56 | +```java |
| 57 | +import liquidjava.specification.*; |
| 58 | + |
| 59 | +@RefinementPredicate("int state(File f)") |
| 60 | +@RefinementAlias("Open(File f) { state(f) == 0}") |
| 61 | +@RefinementAlias("Closed(File f) { state(f) == 1}") |
| 62 | +public class File { |
| 63 | + @StateRefinement(to="Open(this)") |
| 64 | + public File() {} |
| 65 | + |
| 66 | + @StateRefinement(from="Open(this)") |
| 67 | + public void read() {} |
| 68 | + |
| 69 | + @StateRefinement(from="Open(this)", to="Closed(this)") |
| 70 | + public void close() {} |
| 71 | +} |
| 72 | +``` |
| 73 | + |
| 74 | +In this case, we must pass the explicit `this` as an argument to the alias. |
0 commit comments