Skip to content

Defining Predicates

Suppose we want to write a function that takes two pointers to integers and increments the contents of both of them.

First, let's deal with the "normal" case where the two arguments do not alias...

exercises/slf_incr2_noalias.c
void incr2a (unsigned int *p, unsigned int *q)
/*@ requires take P = Owned<unsigned int>(p);
             take Q = Owned<unsigned int>(q);
    ensures take P_post = Owned<unsigned int>(p);
            take Q_post = Owned<unsigned int>(q);
            P_post == P + 1u32;
            Q_post == Q + 1u32;
@*/
{
  unsigned int n = *p;
  unsigned int m = n+1;
  *p = m;
  n = *q;
  m = n+1;
  *q = m;
}

But what if they do alias? The clunky solution is to write a whole different version of incr2 with a different embedded specification...

exercises/slf_incr2_alias.c
// Increment two different pointers (same as above)
void incr2a (unsigned int *p, unsigned int *q)
/*@ requires take P = Owned<unsigned int>(p);
             take Q = Owned<unsigned int>(q);
    ensures take P_post = Owned<unsigned int>(p);
            take Q_post = Owned<unsigned int>(q);
            P_post == P + 1u32;
            Q_post == Q + 1u32;
@*/
{
  unsigned int n = *p;
  unsigned int m = n+1;
  *p = m;
  n = *q;
  m = n+1;
  *q = m;
}

// Increment the same pointer twice
void incr2b (unsigned int *p, unsigned int *q)
/*@ requires take P = Owned<unsigned int>(p);
             ptr_eq(q,p);
    ensures take P_post = Owned<unsigned int>(p);
            ptr_eq(q,p);
            P_post == P + 2u32;
@*/
{
  unsigned int n = *p;
  unsigned int m = n+1;
  *p = m;
  n = *q;
  m = n+1;
  *q = m;
}

void call_both (unsigned int *p, unsigned int *q)
/*@ requires take pv = Owned<unsigned int>(p);
             take qv = Owned<unsigned int>(q);
    ensures take pv_ = Owned<unsigned int>(p);
            take qv_ = Owned<unsigned int>(q);
            pv_ == pv + 3u32;
            qv_ == qv + 1u32;
@*/
{
  incr2a(p, q);   // increment two different pointers
  incr2b(p, p);   // increment the same pointer twice
}

This version does correctly state that the final values of p and q are,m respectively, 3 and 1 more than their original values. But the way we got there -- by duplicating the whole function incr2, is horrible.

A better way is to define a predicate that captures both the aliased and the non-aliased cases together and use it in the pre- and postconditions:

exercises/slf_incr2.c
/*@
predicate { u32 P, u32 Q } BothOwned (pointer p, pointer q)
{
  if (ptr_eq(p,q)) {
    take PX = Owned<unsigned int>(p);
    return {P: PX, Q: PX};
  }
  else {
    take PX = Owned<unsigned int>(p);
    take QX = Owned<unsigned int>(q);
    return {P: PX, Q: QX};
  }
}
@*/

void incr2(unsigned int *p, unsigned int *q)
/*@ requires take PQ = BothOwned(p,q);
    ensures take PQ_post = BothOwned(p,q);
            PQ_post.P == (!ptr_eq(p,q) ? (PQ.P + 1u32) : (PQ.P + 2u32));
            PQ_post.Q == (!ptr_eq(p,q) ? (PQ.Q + 1u32) : PQ_post.P);
@*/
{
  /*@ split_case ptr_eq(p,q); @*/
  unsigned int n = *p;
  unsigned int m = n + 1;
  *p = m;
  n = *q;
  m = n + 1;
  *q = m;
}

void call_both_better(unsigned int *p, unsigned int *q)
/*@ requires take P = Owned<unsigned int>(p);
             take Q = Owned<unsigned int>(q);
             !ptr_eq(p,q);
    ensures take P_post = Owned<unsigned int>(p);
            take Q_post = Owned<unsigned int>(q);
            P_post == P + 3u32;
            Q_post == Q + 1u32;
@*/
{
  incr2(p, q);
  incr2(p, p);
}