GaloisInc / saw-script

The SAW scripting language.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Extensions to/limitations of the current ghost state API

sauclovian-g opened this issue · comments

Currently you can declare ghost variables with declare_ghost_state and assert things about their values with llvm_ghost_value, mir_ghost_value, etc.

This creates the following limitation: you cannot update the ghost state explicitly, nor can you fetch it and assert things about it other than equality to some other value.

As an example of why you might want to update a ghost variable, suppose you have some functions that you both wish to verify and then use as overrides for verifying higher-level code. Suppose furthermore that these functions are a low-level implementation of a higher-level interface, and you wish to provide ghost variables to track the higher-level state so the callers do not need to be exposed to the implementation. Currently you cannot do this: as part of the postcondition of one of these functions you can assert that it updates its low-level state correctly and that this corresponds to the intended new high-level state. But there's no way to assign the new high-level state into the ghost variable. (All you can do call llvm_ghost_value to assert that the new high-level state is equal to the original high-level state, and that's not what you want and also always going to fail.)

Asserting the new high-level state in the postcondition works if the spec is purely used as an override, which is probably why this hasn't come up before.

Meanwhile, you might want to fetch a ghost variable and assert something other than equality about it in any number of cases related to inexact specs: for example, if you're counting events that happen in the C code and it's ok for there to be extras, and the count's tracked in a ghost variable, the postcondition you want is ghost_value >= n. You might be able to build a model such that you can test for equality, but only at the cost of significant extra effort and probably a bunch of silly contortions.

Suggestion:

  1. Rename llvm_ghost_value and friends to llvm_assert_ghost_value to make it clearer what they do.
  2. Add llvm_update_ghost_value et al.
  3. Also add llvm_get_ghost_value et al.

Somewhat silly example for update:

First, some C code:


static unsigned *words;
struct unsigned nwords;

void bignum_add(unsigned x) {
   unsigned i;

   words[0] += x;
   if (words[0] < x) {
      i = 1;
      while (1) {
         if (i == the_nwords) {
            bignum_embiggen();
         }
         words[i] += 1;
         if (words[i] != 0) {
            break;
         }
      }
   }
}

And now, a candidate spec:

// declare ghost state for tracking the actual value
bignum_val <- declare_ghost_state "bignum_val";

let bignum_add_spec = do {
   // get a fresh var for the tracking value and bind it
   val <- llvm_fresh_cryptol_var {{ Integer }};
   llvm_ghost_value bignum_val val;

   // get vars for the internal state and bind them
   words_array <- llvm_fresh_value( /* something-or-other */ );
   words_var <- llvm_fresh_value( /* something-or-other */ );
   llvm_points_to(words_var, words_array);
   nwords_var <- llvm_fresh_value( /* something-or-other */ );
   llvm_precond(words_var, llvm_find_var("words")); /* or something */
   llvm_precond(nwords_var, llvm_find_var("nwords")); /* or something */

   // get a var for the argument
   x_var <- llvm_fresh_value( /* something-or-other */ );

   // run it
   llvm_execute_func(x);

   // compute updated state
   let words_array' = {{ whatever }};
   let nwords' = {{ whatever }};
   llvm_points_to(words_var, words_array');
   llvm_postcond(nwords_var, llvm_find_var("nwords")); /* or something */

   // update the tracking state
   let val' = {{ val + x_var }};

   // assert it matches
   assert {{ val' == cryptol_get_value words_array' }};

   // update the tracking value (except...)
   llvm_ghost_value bignum_val {{ val + x_var }};
}

Then we call

llvm_verify [] bignum_add_spec ...;

(All of this including the code is doubtless wrong in many places; that's not the point.)

The call at the end needs to be llvm_update_ghost_value or the verification will not succeed.

Note that while there's not really any need for llvm_assert_ghost_value given llvm_get_ghost_value, there's a reason it's the only operator we currently have: it's the common case. So it seems worth keeping, even if we deprecate the old spelling.