Skip to content

Commit d139242

Browse files
committed
copilot-theorem: Reject existentially quantified propositions in What4 backend. Refs #254.
The functions in `Copilot.Theorem.What4` (e.g., `prove`) take a proposition and attempt to prove that it is valid at all possible time steps. Currently, these functions do this regardless of whether the proposition is universally quantified or existentially quantified, which is unsound. The reason that this happens is because the functions call `extractProp` on the proposition being proven, causing the proposition's quantifier not to be taken into account. This commit removes the unsound uses of `extractProp` and instead checks if the proposition was actually quantified universally (i.e., with a `Forall`), which is the intended use case for `Copilot.Theorem.What4`. If the proposition was instead quantified existentially (i.e., with an `Exists`), then a `ProveException` will be thrown. Warnings have been added to Haddocks of the relevant functions that can potentially throw a `ProveException`.
1 parent bce4c10 commit d139242

File tree

2 files changed

+105
-15
lines changed

2 files changed

+105
-15
lines changed

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

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@
3232
-- We perform @k@-induction on all the properties in a given specification where
3333
-- @k@ is chosen to be the maximum amount of delay on any of the involved
3434
-- streams. This is a heuristic choice, but often effective.
35+
--
36+
-- The functions in this module are only designed to prove universally
37+
-- quantified propositions (i.e., propositions that use @forAll@). Attempting to
38+
-- prove an existentially quantified proposition (i.e., propositions that use
39+
-- @exists@) will cause a 'UnexpectedExistentialProposition' exception to be
40+
-- thrown.
3541
module Copilot.Theorem.What4
3642
( -- * Proving properties about Copilot specifications
3743
prove
@@ -40,6 +46,7 @@ module Copilot.Theorem.What4
4046
, proveWithCounterExample
4147
, SatResultCex(..)
4248
, CounterExample(..)
49+
, ProveException(..)
4350
-- * Bisimulation proofs about @copilot-c99@ code
4451
, computeBisimulationProofBundle
4552
, BisimulationProofBundle(..)
@@ -63,6 +70,7 @@ import qualified What4.InterpretedFloatingPoint as WFP
6370
import qualified What4.Solver as WS
6471
import qualified What4.Solver.DReal as WS
6572

73+
import Control.Exception (Exception, throw)
6674
import Control.Monad (forM)
6775
import Control.Monad.State
6876
import qualified Data.BitVector.Sized as BV
@@ -257,9 +265,24 @@ data CounterExample = CounterExample
257265
}
258266
deriving Show
259267

268+
-- | Exceptions that can arise when attempting a proof.
269+
data ProveException
270+
= UnexpectedExistentialProposition
271+
-- ^ The functions in "Copilot.Theorem.What4" can only prove properties with
272+
-- universally quantified propositions. The functions in
273+
-- "Copilot.Theorem.What4" will throw this exception if they encounter an
274+
-- existentially quantified proposition.
275+
deriving Show
276+
277+
instance Exception ProveException
278+
260279
-- | Attempt to prove all of the properties in a spec via an SMT solver (which
261280
-- must be installed locally on the host). Return an association list mapping
262281
-- the names of each property to the result returned by the solver.
282+
--
283+
-- PRE: All of the properties in the 'CS.Spec' use universally quantified
284+
-- propositions. Attempting to supply an existentially quantified proposition
285+
-- will cause a 'UnexpectedExistentialProposition' exception to be thrown.
263286
prove :: Solver
264287
-- ^ Solver to use
265288
-> CS.Spec
@@ -350,7 +373,12 @@ proveInternal solver spec k = do
350373
-- This process performs k-induction where we use @k = maxBufLen@.
351374
-- The choice for @k@ is heuristic, but often effective.
352375
let proveProperties = forM (CS.specProperties spec) $ \pr -> do
353-
let prop = CS.extractProp (CS.propertyProp pr)
376+
-- This function only supports universally quantified propositions, so
377+
-- throw an exception if we encounter an existentially quantified
378+
-- proposition.
379+
let prop = case CS.propertyProp pr of
380+
CS.Forall p -> p
381+
CS.Exists {} -> throw UnexpectedExistentialProposition
354382
-- State the base cases for k induction.
355383
base_cases <- forM [0 .. maxBufLen - 1] $ \i -> do
356384
xe <- translateExpr sym mempty prop (AbsoluteOffset i)
@@ -420,6 +448,10 @@ proveInternal solver spec k = do
420448
-- to carry out a bisimulation proof that establishes a correspondence between
421449
-- the states of the Copilot stream program and the C code that @copilot-c99@
422450
-- would generate for that Copilot program.
451+
--
452+
-- PRE: All of the properties in the 'CS.Spec' use universally quantified
453+
-- propositions. Attempting to supply an existentially quantified proposition
454+
-- will cause a 'UnexpectedExistentialProposition' exception to be thrown.
423455
computeBisimulationProofBundle ::
424456
WFP.IsInterpretedFloatSymExprBuilder sym
425457
=> sym
@@ -581,6 +613,10 @@ computeExternalInputs sym = do
581613
return (nm, Some tp, v)
582614

583615
-- | Compute the user-provided property assumptions in a Copilot program.
616+
--
617+
-- PRE: All of the properties in the 'CS.Spec' use universally quantified
618+
-- propositions. Attempting to supply an existentially quantified proposition
619+
-- will cause a 'UnexpectedExistentialProposition' exception to be thrown.
584620
computeAssumptions ::
585621
forall sym.
586622
WFP.IsInterpretedFloatSymExprBuilder sym
@@ -603,6 +639,9 @@ computeAssumptions sym properties spec =
603639
[ CS.extractProp (CS.propertyProp p)
604640
| p <- CS.specProperties spec
605641
, elem (CS.propertyName p) properties
642+
, let prop = case CS.propertyProp p of
643+
CS.Forall pr -> pr
644+
CS.Exists {} -> throw UnexpectedExistentialProposition
606645
]
607646

608647
-- Compute all of the what4 predicates corresponding to each user-provided

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

Lines changed: 65 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,22 @@
1-
{-# LANGUAGE DataKinds #-}
1+
{-# LANGUAGE DataKinds #-}
2+
{-# LANGUAGE ScopedTypeVariables #-}
3+
{-# LANGUAGE TypeApplications #-}
24
-- The following warning is disabled due to a necessary instance of SatResult
35
-- defined in this module.
46
{-# OPTIONS_GHC -fno-warn-orphans #-}
57
-- | Test copilot-theorem:Copilot.Theorem.What4.
68
module Test.Copilot.Theorem.What4 where
79

810
-- External imports
11+
import Control.Exception (Exception, try)
912
import Data.Int (Int8)
13+
import Data.Proxy (Proxy (..))
14+
import Data.Typeable (typeRep)
1015
import Data.Word (Word32)
1116
import Test.Framework (Test, testGroup)
1217
import Test.Framework.Providers.QuickCheck2 (testProperty)
13-
import Test.HUnit (assertFailure)
18+
import Test.HUnit (Assertion, assertBool,
19+
assertFailure)
1420
import Test.QuickCheck (Arbitrary (arbitrary), Property,
1521
arbitrary, forAll)
1622
import Test.QuickCheck.Monadic (monadicIO, run)
@@ -26,9 +32,9 @@ import Copilot.Core.Type (Field (..),
2632
Value (..))
2733

2834
-- Internal imports: Modules being tested
29-
import Copilot.Theorem.What4 (CounterExample (..), SatResult (..),
30-
SatResultCex (..), Solver (..), prove,
31-
proveWithCounterExample)
35+
import Copilot.Theorem.What4 (CounterExample (..), ProveException (..),
36+
SatResult (..), SatResultCex (..), Solver (..),
37+
prove, proveWithCounterExample)
3238

3339
-- * Constants
3440

@@ -42,6 +48,7 @@ tests =
4248
, testProperty "Prove via Z3 that a struct update is valid" testProveZ3StructUpdate
4349
, testProperty "Counterexample with invalid base case" testCounterExampleBaseCase
4450
, testProperty "Counterexample with invalid induction step" testCounterExampleInductionStep
51+
, testProperty "Check that the What4 backend rejects existential quantification" testWhat4ExistsException
4552
]
4653

4754
-- * Individual tests
@@ -58,7 +65,7 @@ testProveZ3True =
5865
propName = "prop"
5966

6067
spec :: Spec
61-
spec = propSpec propName [] $ Const typeOf True
68+
spec = forallPropSpec propName [] $ Const typeOf True
6269

6370
-- | Test that Z3 is able to prove the following expression invalid:
6471
-- @
@@ -72,7 +79,7 @@ testProveZ3False =
7279
propName = "prop"
7380

7481
spec :: Spec
75-
spec = propSpec propName [] $ Const typeOf False
82+
spec = forallPropSpec propName [] $ Const typeOf False
7683

7784
-- | Test that Z3 is able to prove the following expresion valid:
7885
-- @
@@ -86,7 +93,7 @@ testProveZ3EqConst = forAll arbitrary $ \x ->
8693
propName = "prop"
8794

8895
spec :: Int8 -> Spec
89-
spec x = propSpec propName [] $
96+
spec x = forallPropSpec propName [] $
9097
Op2 (Eq typeOf) (Const typeOf x) (Const typeOf x)
9198

9299
-- | Test that Z3 is able to prove the following expresion valid:
@@ -102,7 +109,7 @@ testProveZ3StructUpdate = forAll arbitrary $ \x ->
102109
propName = "prop"
103110

104111
spec :: TestStruct -> Spec
105-
spec s = propSpec propName [] $
112+
spec s = forallPropSpec propName [] $
106113
Op2
107114
(Eq typeOf)
108115
(getField
@@ -151,7 +158,7 @@ testCounterExampleBaseCase =
151158
sId = 0
152159

153160
spec :: Spec
154-
spec = propSpec propName [s] $ Drop typeOf 0 sId
161+
spec = forallPropSpec propName [s] $ Drop typeOf 0 sId
155162

156163
-- | Test that Z3 is able to produce a counterexample to the following property,
157164
-- where the induction step is proved invalid:
@@ -183,7 +190,23 @@ testCounterExampleInductionStep =
183190
sId = 0
184191

185192
spec :: Spec
186-
spec = propSpec propName [s] $ Drop typeOf 0 sId
193+
spec = forallPropSpec propName [s] $ Drop typeOf 0 sId
194+
195+
-- | Test that @copilot-theorem@'s @what4@ backend will throw an exception if it
196+
-- attempts to prove an existentially quantified proposition.
197+
testWhat4ExistsException :: Property
198+
testWhat4ExistsException =
199+
monadicIO $ run $
200+
checkException (prove Z3 spec) isUnexpectedExistentialProposition
201+
where
202+
isUnexpectedExistentialProposition :: ProveException -> Bool
203+
isUnexpectedExistentialProposition UnexpectedExistentialProposition = True
204+
205+
propName :: String
206+
propName = "prop"
207+
208+
spec :: Spec
209+
spec = existsPropSpec propName [] $ Const typeOf True
187210

188211
-- | A simple data type with a 'Struct' instance and a 'Field'. This is only
189212
-- used as part of 'testProveZ3StructUpdate'.
@@ -245,14 +268,42 @@ checkCounterExample solver propName spec cexPred = do
245268
UnknownCex {} ->
246269
assertFailure "Expected invalid result, but result was unknown"
247270

271+
-- | Check that the given 'IO' action throws a particular exception. This is
272+
-- largely taken from the implementation of @shouldThrow@ in
273+
-- @hspec-expectations@ (note that this test suite uses @test-framework@ instead
274+
-- of @hspec@).
275+
checkException :: forall e a. Exception e => IO a -> (e -> Bool) -> Assertion
276+
checkException action p = do
277+
r <- try action
278+
case r of
279+
Right _ ->
280+
assertFailure $
281+
"did not get expected exception: " ++ exceptionType
282+
Left e ->
283+
assertBool
284+
("predicate failed on expected exception: " ++ exceptionType ++
285+
"\n" ++ show e)
286+
(p e)
287+
where
288+
-- String representation of the expected exception's type
289+
exceptionType = show $ typeRep $ Proxy @e
290+
248291
-- * Auxiliary
249292

250293
-- | Build a 'Spec' that contains one property with the given name, which
251-
-- contains the given streams, and is defined by the given boolean expression.
252-
propSpec :: String -> [Stream] -> Expr Bool -> Spec
253-
propSpec propName propStreams propExpr =
294+
-- contains the given streams, and is defined by the given boolean expression,
295+
-- which is universally quantified.
296+
forallPropSpec :: String -> [Stream] -> Expr Bool -> Spec
297+
forallPropSpec propName propStreams propExpr =
254298
Spec propStreams [] [] [Copilot.Property propName (Copilot.Forall propExpr)]
255299

300+
-- | Build a 'Spec' that contains one property with the given name, which
301+
-- contains the given streams, and is defined by the given boolean expression,
302+
-- which is existentially quantified.
303+
existsPropSpec :: String -> [Stream] -> Expr Bool -> Spec
304+
existsPropSpec propName propStreams propExpr =
305+
Spec propStreams [] [] [Copilot.Property propName (Copilot.Exists propExpr)]
306+
256307
-- | Equality for 'SatResult'.
257308
--
258309
-- This is an orphan instance, so we suppress the warning that GHC would

0 commit comments

Comments
 (0)