GaloisInc / saw-script

The SAW scripting language.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Print SMT array counterexamples more intelligibly

RyanGlScott opened this issue · comments

Consider this erroneous SAW proof, which makes use of a predicate with an SMT array argument:

// test.saw
import "Array.cry";

prove_print
  (w4_unint_z3 [])
  {{ \(arr : Array Integer Integer) ->
     arrayLookup (arrayUpdate arr 0 27) 0 == 42 }};

SAW is able to realize that this proof is invalid. However, the way it prints the counterexample leaves a lot to be desired:

$ ~/Software/saw-1.1/bin/saw test.saw
[11:58:13.968] Loading file "/home/ryanscott/Documents/Hacking/SAW/test.saw"
[11:58:14.064] Stack trace:
"prove_print" (/home/ryanscott/Documents/Hacking/SAW/test.saw:4:1-4:12)
prove: 1 unsolved subgoal(s)
Invalid: [arr = FOTArray FOTInt FOTInt]

arr = FOTArray FOTInt FOTInt doesn't really say anything useful, as it just repeats the type of arr (Array Integer Integer) in terms of SAWCore's internals. Ideally, the counterexample would say something like "an array where every element is 0". In fact, if you retrieve a counter-example using SMT-LIB:

; test.smt2
(declare-const arr (Array Int Int))
(assert (not (= (select (store arr 0 27) 0) 42)))
(check-sat)
(get-model)

Then Z3 is able to produce this in a neat, compact way:

$ z3 test.smt2 
sat
(
  (define-fun arr () (Array Int Int)
    ((as const (Array Int Int)) 0))
)

We should strive to make SAW's counterexample output as neat as this. This issue tracks that task.


To do this, we will need to refactor some of saw-core's internals a bit. The immediate reason why SAW is unable to produce a meaningful counterexample for SMT array values is because saw-core simply doesn't store them:

data FirstOrderValue
= FOVBit Bool
| FOVInt Integer
| FOVIntMod Natural Integer
| FOVWord Natural Integer -- ^ a more efficient special case for 'FOVVec FOTBit _'.
| FOVVec FirstOrderType [FirstOrderValue]
| FOVArray FirstOrderType FirstOrderType
| FOVTuple [FirstOrderValue]
| FOVRec (Map FieldName FirstOrderValue)

FOVArray is the odd one out in the definition of FirstOrderValue, as it is the only constructor that doesn't store any meaningful value information. We would need to change the definition of FOVArray to do so. But where do we get this value information from? FirstOrderValues are created here:

groundToFOV :: BaseTypeRepr ty -> GroundValue ty -> Either String FirstOrderValue
groundToFOV BaseBoolRepr b = pure $ FOVBit b
groundToFOV BaseIntegerRepr i = pure $ FOVInt i
groundToFOV (BaseBVRepr w) bv = pure $ FOVWord (natValue w) (BV.asUnsigned bv)
groundToFOV BaseRealRepr _ = Left "Real is not FOV"
groundToFOV BaseComplexRepr _ = Left "Complex is not FOV"
groundToFOV (BaseStringRepr _) _ = Left "String is not FOV"
groundToFOV (BaseFloatRepr _) _ = Left "Floating point is not FOV"
groundToFOV (BaseArrayRepr (Empty :> ty) b) _
| Right fot1 <- typeReprToFOT ty
, Right fot2 <- typeReprToFOT b
= pure $ FOVArray fot1 fot2
groundToFOV (BaseArrayRepr _idx _b) _ = Left "Unsupported FOV Array"
groundToFOV (BaseStructRepr ctx) tup = FOVTuple <$> tupleToList ctx tup

This means that FirstOrderValues are ultimately derived from what4's notion of GroundValues. In particular, a ground SMT array is a GroundArray. We would need to figure out how to sensibly convert a GroundArray to a FirstOrderValue representation.

In the case of ArrayConcrete, this is fairly straightforward to print, as each ArrayConcrete value is a constant array value plus a finite number of array updates. ArrayMapping is harder, as an ArrayMapping contains an arbitrary function. We needn't let perfect be the enemy of good, however, as I suspect that the ArrayConcrete case will be far more common in practice. Perhaps we can just print ArrayMapping as <array-fun>, and if users want to figure out what the array contains at various indices, they can retrieve the array using caseProofResult or caseSatResult.