Skip to content

Commit 0f5055e

Browse files
Merge branch 'develop-copilot-theorem-UpdateField'. Close #524.
**Description** Copilot currently supports modifying values from streams of structs, but not reasoning about them using `copilot-theorem`. **Type** - Bug: exception produced when executing valid specification. **Additional context** - Issue #520 introduced support for modifying structs. **Requester** - Ryan Scott (Galois) **Method to check presence of bug** The following Dockerfile installs copilot and runs a spec that uses copilot-theorem to prove that a stream with an update is equal to itself, in which case it prints the message "valid", followed by "Success": ``` --- Dockerfile FROM ubuntu:focal ENV DEBIAN_FRONTEND=noninteractive RUN apt-get update RUN apt-get install --yes libz-dev RUN apt-get install --yes git RUN apt-get install --yes wget RUN mkdir -p $HOME/.ghcup/bin RUN wget https://downloads.haskell.org/~ghcup/0.1.19.2/x86_64-linux-ghcup-0.1.19.2 -O $HOME/.ghcup/bin/ghcup RUN chmod a+x $HOME/.ghcup/bin/ghcup ENV PATH=$PATH:/root/.ghcup/bin/ ENV PATH=$PATH:/root/.cabal/bin/ RUN apt-get install --yes curl RUN apt-get install --yes gcc g++ make libgmp3-dev RUN apt-get install --yes pkg-config RUN apt-get install --yes z3 SHELL ["/bin/bash", "-c"] RUN ghcup install ghc 9.4 RUN ghcup install cabal 3.2 RUN ghcup set ghc 9.4.8 RUN cabal update ADD UpdateField.hs /tmp/UpdateField.hs CMD git clone $REPO \ && cd $NAME \ && git checkout $COMMIT \ && cabal v1-sandbox init \ && cabal v1-install alex happy \ && cabal v1-install copilot**/ \ && cabal v1-exec -- runhaskell /tmp/UpdateField.hs \ && echo Success --- UpdateField.hs {-# LANGUAGE DataKinds #-} {-# LANGUAGE NoImplicitPrelude #-} module Main (main) where import Data.Foldable (for_) import Data.Functor (void) import Data.Word (Word32) import qualified Copilot.Theorem.What4 as CT import Language.Copilot data S = S { unS :: Field "unS" Word32 } instance Struct S where typeName _ = "s" toValues s = [Value typeOf (unS s)] instance Typed S where typeOf = Struct (S (Field 0)) spec :: Spec spec = do let externS :: Stream S externS = extern "extern_s" Nothing example :: Stream Word32 example = (externS ## unS =: 42) # unS void $ prop "example" (forAll $ example == example) main :: IO () main = do spec' <- reify spec -- Use Z3 to prove the properties. results <- CT.prove CT.Z3 spec' -- Print the results. for_ results $ \(nm, res) -> do putStr $ nm <> ": " case res of CT.Valid -> putStrLn "valid" CT.Invalid -> putStrLn "invalid" CT.Unknown -> putStrLn "unknown" ``` Command (substitute variables based on new path after merge): ``` $ docker run -e "REPO=https://github.com/Copilot-Language/copilot" -e "NAME=copilot" -e "COMMIT=<HASH>" -it copilot-verify-524 ``` **Expected result** Running the dockerfile above prints the message "valid", followed by "Success", indicating that Copilot allows using struct updates in `copilot-theorem` and to reason about them. **Solution implemented** Translate struct updates to `what4`. Update example to demonstrate support for struct updates in `copilot-theorem`. Include a test that checks that struct updates are supported in `copilot-theorem`. **Further notes** None.
2 parents 6034e6d + bf0f454 commit 0f5055e

File tree

5 files changed

+120
-4
lines changed

5 files changed

+120
-4
lines changed

copilot-theorem/CHANGELOG

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
2024-08-30
2+
* Add support for struct updates in Copilot.Theorem.What4. (#524)
3+
14
2024-07-07
25
* Version bump (3.20). (#522)
36
* What4 upper-bound dependency version bump. (#514)

copilot-theorem/src/Copilot/Theorem/What4/Translate.hs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -799,6 +799,8 @@ translateOp2 sym origExpr op xe1 xe2 = case (op, xe1, xe2) of
799799
(CE.BwShiftL _ _, xe1, xe2) -> translateBwShiftL xe1 xe2
800800
(CE.BwShiftR _ _, xe1, xe2) -> translateBwShiftR xe1 xe2
801801
(CE.Index _, xe1, xe2) -> translateIndex xe1 xe2
802+
(CE.UpdateField atp _ftp extractor, structXe, fieldXe) ->
803+
translateUpdateField atp extractor structXe fieldXe
802804
where
803805
-- Translate an 'CE.Add' operation and its arguments into a what4
804806
-- representation of the appropriate type.
@@ -964,6 +966,55 @@ translateOp2 sym origExpr op xe1 xe2 = case (op, xe1, xe2) of
964966
liftIO $ buildIndexExpr sym ix xes
965967
_ -> unexpectedValues "index operation"
966968

969+
-- Translate an 'CE.UpdateField' operation and its arguments into a what4
970+
-- representation. This function will panic if one of the following does not
971+
-- hold:
972+
--
973+
-- - The argument is not a struct.
974+
--
975+
-- - The struct's field cannot be found.
976+
translateUpdateField :: forall struct s.
977+
KnownSymbol s
978+
=> CT.Type struct
979+
-- ^ The type of the struct argument
980+
-> (struct -> CT.Field s b)
981+
-- ^ Extract a struct field
982+
-> XExpr sym
983+
-- ^ The first argument value (should be a struct)
984+
-> XExpr sym
985+
-- ^ The second argument value (should be the same type
986+
-- as the struct field)
987+
-> TransM sym (XExpr sym)
988+
-- ^ The first argument value, but with an updated
989+
-- value for the supplied field.
990+
translateUpdateField structTp extractor structXe newFieldXe =
991+
case (structTp, structXe) of
992+
(CT.Struct s, XStruct structFieldXes) ->
993+
case mIx s of
994+
Just ix -> return $ XStruct $ updateAt ix newFieldXe structFieldXes
995+
Nothing ->
996+
panic [ "Could not find field " ++ show fieldNameRepr
997+
, show s
998+
]
999+
_ -> unexpectedValues "update-field operation"
1000+
where
1001+
-- Update an element of a list at a particular index. This assumes the
1002+
-- preconditions that the index is a non-negative number that is less
1003+
-- than the length of the list.
1004+
updateAt :: forall a. Int -> a -> [a] -> [a]
1005+
updateAt _ _ [] = []
1006+
updateAt 0 new (_:xs) = new : xs
1007+
updateAt n new (x:xs) = x : updateAt (n-1) new xs
1008+
1009+
fieldNameRepr :: SymbolRepr s
1010+
fieldNameRepr = fieldName (extractor undefined)
1011+
1012+
structFieldNameReprs :: CT.Struct struct => struct -> [Some SymbolRepr]
1013+
structFieldNameReprs s = valueName <$> CT.toValues s
1014+
1015+
mIx :: CT.Struct struct => struct -> Maybe Int
1016+
mIx s = elemIndex (Some fieldNameRepr) (structFieldNameReprs s)
1017+
9671018
-- Check the types of the arguments. If the arguments are bitvector values,
9681019
-- apply the 'BVOp2'. If the arguments are floating-point values, apply the
9691020
-- 'FPOp2'. Otherwise, 'panic'.

copilot-theorem/tests/Test/Copilot/Theorem/What4.hs

Lines changed: 59 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
{-# LANGUAGE DataKinds #-}
12
-- The following warning is disabled due to a necessary instance of SatResult
23
-- defined in this module.
34
{-# OPTIONS_GHC -fno-warn-orphans #-}
@@ -6,17 +7,22 @@ module Test.Copilot.Theorem.What4 where
67

78
-- External imports
89
import Data.Int (Int8)
10+
import Data.Word (Word32)
911
import Test.Framework (Test, testGroup)
1012
import Test.Framework.Providers.QuickCheck2 (testProperty)
11-
import Test.QuickCheck (Property, arbitrary, forAll)
13+
import Test.QuickCheck (Arbitrary (arbitrary), Property,
14+
arbitrary, forAll)
1215
import Test.QuickCheck.Monadic (monadicIO, run)
1316

1417
-- External imports: Copilot
15-
import Copilot.Core.Expr (Expr (Const, Op2))
16-
import Copilot.Core.Operators (Op2 (..))
18+
import Copilot.Core.Expr (Expr (Const, Op1, Op2))
19+
import Copilot.Core.Operators (Op1 (..), Op2 (..))
1720
import Copilot.Core.Spec (Spec (..))
1821
import qualified Copilot.Core.Spec as Copilot
19-
import Copilot.Core.Type (Typed (typeOf))
22+
import Copilot.Core.Type (Field (..),
23+
Struct (toValues, typeName),
24+
Type (Struct), Typed (typeOf),
25+
Value (..))
2026

2127
-- Internal imports: Modules being tested
2228
import Copilot.Theorem.What4 (SatResult (..), Solver (..), prove)
@@ -30,6 +36,7 @@ tests =
3036
[ testProperty "Prove via Z3 that true is valid" testProveZ3True
3137
, testProperty "Prove via Z3 that false is invalid" testProveZ3False
3238
, testProperty "Prove via Z3 that x == x is valid" testProveZ3EqConst
39+
, testProperty "Prove via Z3 that a struct update is valid" testProveZ3StructUpdate
3340
]
3441

3542
-- * Individual tests
@@ -77,6 +84,54 @@ testProveZ3EqConst = forAll arbitrary $ \x ->
7784
spec x = propSpec propName $
7885
Op2 (Eq typeOf) (Const typeOf x) (Const typeOf x)
7986

87+
-- | Test that Z3 is able to prove the following expresion valid:
88+
-- @
89+
-- for all (s :: MyStruct),
90+
-- ((s ## testField =$ (+1)) # testField) == ((s # testField) + 1)
91+
-- @
92+
testProveZ3StructUpdate :: Property
93+
testProveZ3StructUpdate = forAll arbitrary $ \x ->
94+
monadicIO $ run $ checkResult Z3 propName (spec x) Valid
95+
where
96+
propName :: String
97+
propName = "prop"
98+
99+
spec :: TestStruct -> Spec
100+
spec s = propSpec propName $
101+
Op2
102+
(Eq typeOf)
103+
(getField
104+
(Op2
105+
(UpdateField typeOf typeOf testField)
106+
sExpr
107+
(add1 (getField sExpr))))
108+
(add1 (getField sExpr))
109+
where
110+
sExpr :: Expr TestStruct
111+
sExpr = Const typeOf s
112+
113+
getField :: Expr TestStruct -> Expr Word32
114+
getField = Op1 (GetField typeOf typeOf testField)
115+
116+
add1 :: Expr Word32 -> Expr Word32
117+
add1 x = Op2 (Add typeOf) x (Const typeOf 1)
118+
119+
-- | A simple data type with a 'Struct' instance and a 'Field'. This is only
120+
-- used as part of 'testProveZ3StructUpdate'.
121+
newtype TestStruct = TestStruct { testField :: Field "testField" Word32 }
122+
123+
instance Arbitrary TestStruct where
124+
arbitrary = do
125+
w32 <- arbitrary
126+
return (TestStruct (Field w32))
127+
128+
instance Struct TestStruct where
129+
typeName _ = "testStruct"
130+
toValues s = [Value typeOf (testField s)]
131+
132+
instance Typed TestStruct where
133+
typeOf = Struct (TestStruct (Field 0))
134+
80135
-- | Check that the solver's satisfiability result for the given property in
81136
-- the given spec matches the expectation.
82137
checkResult :: Solver -> String -> Spec -> SatResult -> IO Bool

copilot/CHANGELOG

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
2024-08-30
2+
* Update example to demonstrate struct update support. (#524)
3+
14
2024-07-07
25
* Version bump (3.20). (#522)
36
* Update README to reflect support for GHC 9.8. (#518)

copilot/examples/what4/Structs.hs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ spec = do
6464
void $ prop "Example 2" $ forAll $
6565
(((battery#other) .!! 2) .!! 3) == (((battery#other) .!! 2) .!! 4)
6666

67+
-- Update a struct field, then check it for equality.
68+
void $ prop "Example 3" $ forAll $
69+
((battery ## temp =$ (+1))#temp == (battery#temp + 1))
70+
6771
main :: IO ()
6872
main = do
6973
spec' <- reify spec

0 commit comments

Comments
 (0)