crucible-0.9: test/helpers/SymSequence.hs
{-# LANGUAGE EmptyDataDeriving #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE ImportQualifiedPost #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
module SymSequence (tests) where
import Control.Monad.IO.Class (liftIO)
import Data.Foldable qualified as F
import Data.List qualified as List
import Data.Maybe qualified as Maybe
import Data.Parameterized.Nonce qualified as Nonce
import Data.Parameterized.Some (Some(Some))
import Hedgehog (Gen)
import Hedgehog qualified as H
import Hedgehog.Gen qualified as Gen
import Hedgehog.Range qualified as Range
import Lang.Crucible.Backend (SomeBackend(SomeBackend), backendGetSym)
import Lang.Crucible.Backend.Simple (newSimpleBackend)
import Lang.Crucible.Simulator.SymSequence (SymSequence)
import Lang.Crucible.Simulator.SymSequence qualified as S
import Test.Tasty qualified as TT
import Test.Tasty.Hedgehog qualified as TTH
import What4.Expr (EmptyExprBuilderState(EmptyExprBuilderState))
import What4.Expr.Builder (newExprBuilder)
import What4.FloatMode (FloatModeRepr(FloatIEEERepr))
import What4.Interface qualified as WI
import What4.Partial qualified as WP
---------------------------------------------------------------------
-- Tests
tests :: TT.TestTree
tests =
TTH.testProperty
"propSame"
-- This is a big API, so we want adequate coverage (default is 100)
(H.withTests 4096 propSame)
-- | Check that a generated API interaction has the same effect when interpreted
-- with either 'SymSequence' or lists.
propSame :: H.Property
propSame =
H.property $ do
Some (SomeBackend bak) <- liftIO mkBackend
let sym = backendGetSym bak
op <- H.forAll (Gen.sized $ \n -> genList (H.unSize n) Gen.bool)
let l = opList op
s <- liftIO (opSeq sym op)
l' <- liftIO (F.toList <$> asSeq sym s)
l H.=== l'
where
asSeq sym =
S.concretizeSymSequence (pure . asConstPred (Just sym)) pure
---------------------------------------------------------------------
-- Helpers
mkBackend :: IO (Some SomeBackend)
mkBackend = do
sym <- newExprBuilder FloatIEEERepr EmptyExprBuilderState Nonce.globalNonceGenerator
Some . SomeBackend <$> newSimpleBackend sym
-- Requires that the predicate is concrete
asConstPred ::
WI.IsExprBuilder sym =>
proxy sym ->
WI.Pred sym ->
Bool
asConstPred _proxy p =
case WI.asConstantPred p of
Just True -> True
Just False -> False
Nothing -> error "non-constant predicate?"
---------------------------------------------------------------------
-- Op
data Elem a deriving Show
data List a deriving Show
-- TODO: Replace with `Seq` for performance
type family AsList t where
AsList (List a) = [a]
AsList (Elem a) = a
AsList (Maybe a) = Maybe (AsList a)
AsList (a, b) = (AsList a, AsList b)
AsList a = a
type family AsSeq sym t where
AsSeq sym (List a) = SymSequence sym a
AsSeq sym (Elem a) = a
AsSeq sym (Maybe a) = Maybe (AsSeq sym a)
AsSeq sym (a, b) = (AsSeq sym a, AsSeq sym b)
AsSeq sym a = a
-- | An interaction with the 'SymSequence' API
data Op a t where
-- Generic functions
OTrue :: Op a Bool
OFalse :: Op a Bool
OFst :: Op a (l, r) -> Op a l
OSnd :: Op a (l, r) -> Op a r
OElem :: a -> Op a (Elem a)
OFromMaybe :: Op a t -> Op a (Maybe t) -> Op a t
-- Constructors
ONil :: Op a (List a)
OCons :: Op a (Elem a) -> Op a (List a) -> Op a (List a)
OAppend :: Op a (List a) -> Op a (List a) -> Op a (List a)
OMux :: Op a Bool -> Op a (List a) -> Op a (List a) -> Op a (List a)
-- Operations
OUncons :: Op a (List a) -> Op a (Maybe (Elem a), (List a))
OLength :: Op a (List a) -> Op a Integer
-- TODO: isNil, head, tail
sexp :: [String] -> String
sexp s = '(' : (unwords s ++ ")")
fun :: String -> [String] -> String
fun f s = sexp (f:s)
fun1 :: Show a => String -> a -> String
fun1 f a = fun f [show a]
fun2 :: (Show a, Show b) => String -> a -> b -> String
fun2 f a b = fun f [show a, show b]
fun3 :: (Show a, Show b, Show c) => String -> a -> b -> c -> String
fun3 f a b c = fun f [show a, show b, show c]
instance Show a => Show (Op a t) where
show =
\case
-- Generic functions
OTrue -> "true"
OFalse -> "false"
OFst t -> fun1 "fst" t
OSnd t -> fun1 "snd" t
OElem a -> show a
OFromMaybe a m -> fun2 "fromMaybe" a m
-- Constructors
ONil -> "nil"
OCons l r -> fun2 "cons" l r
OAppend l r -> fun2 "append" l r
OMux b l r -> fun3 "mux" b l r
-- Operations
OUncons l -> fun1 "uncons" l
OLength l -> fun1 "length" l
---------------------------------------------------------------------
-- Generating Op
genBool :: Gen (Op a Bool)
genBool =
Gen.choice
[ pure OTrue
, pure OFalse
]
genElem ::
Int ->
Gen a ->
Gen (Op a (Elem a))
genElem sz genA =
if sz <= 0
then OElem <$> genA
else
Gen.choice
[ OElem <$> genA
, OFromMaybe
<$> genElem (sz - 1) genA
<*> (OFst <$> (OUncons <$> genList (sz - 1) genA))
]
genList ::
Int ->
Gen a ->
Gen (Op a (List a))
genList sz genA =
if sz <= 0
then pure ONil
else
Gen.choice
[ genCons
, genAppend
, genMux
]
where
sub1 = genList (sz - 1) genA
sub2 = do
let budget = max 0 (sz - 1)
bl <- Gen.integral (Range.linear 0 budget)
let br = max 0 (budget - bl)
l <- genList bl genA
r <- genList br genA
pure (l, r)
genCons = OCons <$> genElem (sz - 1) genA <*> sub1
genAppend = uncurry OAppend <$> sub2
genMux = do
b <- genBool
uncurry (OMux b) <$> sub2
---------------------------------------------------------------------
-- Interpreting Op
opList :: Op a t -> AsList t
opList =
\case
-- Generic functions
OTrue -> True
OFalse -> False
OFst t -> fst (opList t)
OSnd t -> snd (opList t)
OElem a -> a
OFromMaybe a m -> Maybe.fromMaybe (opList a) (opList m)
-- Constructors
ONil -> []
OCons a l -> opList a : opList l
OAppend l r -> opList l ++ opList r
OMux b l r -> if opList b then opList l else opList r
-- Operations
OUncons l ->
let l' = opList l in
case List.uncons l' of
Just (hd, tl) -> (Just hd, tl)
Nothing -> (Nothing, l')
OLength l -> fromIntegral @Int @Integer (length (opList l)) -- safe
opSeq ::
WI.IsExprBuilder sym =>
sym ->
Op a t ->
IO (AsSeq sym t)
opSeq sym =
\case
-- Generic functions
OTrue -> pure True
OFalse -> pure False
OFst t -> fst <$> opSeq sym t
OSnd t -> snd <$> opSeq sym t
OElem a -> pure a
OFromMaybe a m ->
Maybe.fromMaybe
<$> opSeq sym a
<*> opSeq sym m
-- Constructors
ONil -> pure S.SymSequenceNil
OCons a l ->
S.SymSequenceCons
<$> Nonce.freshNonce Nonce.globalNonceGenerator
<*> opSeq sym a
<*> opSeq sym l
OAppend l r ->
S.SymSequenceAppend
<$> Nonce.freshNonce Nonce.globalNonceGenerator
<*> opSeq sym l
<*> opSeq sym r
OMux b l r -> do
b' <- opSeq sym b
let b'' = if b' then WI.truePred sym else WI.falsePred sym
S.SymSequenceMerge
<$> Nonce.freshNonce Nonce.globalNonceGenerator
<*> pure b''
<*> opSeq sym l
<*> opSeq sym r
-- Operations
OUncons l -> do
l' <- opSeq sym l
let interpPred p x y =
if asConstPred (Just sym) p
then pure x
else pure y
pe <- S.unconsSymSequence sym interpPred l'
case pe of
WP.Unassigned -> pure (Nothing, l')
WP.PE _ (hd, tl) -> -- TODO: assert pred is truePred
pure (Just hd, tl)
OLength s -> do
l <- S.lengthSymSequence sym =<< opSeq sym s
case WI.asInteger (WI.natToIntegerPure l) of
Just l' -> pure l'
Nothing -> error "SymSequence: symbolic length"