-
Notifications
You must be signed in to change notification settings - Fork 36
/
SMT.hs
709 lines (601 loc) · 28.2 KB
/
SMT.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
{-# LANGUAGE GADTs #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE MonadComprehensions #-}
{-# LANGUAGE ViewPatterns #-}
{-# LANGUAGE LambdaCase #-}
module SMT (
Solver(..),
SMTConfig(..),
Query(..),
SMTResult(..),
spawnSolver,
stopSolver,
sendLines,
runQuery,
mkPostconditionQueries,
mkInvariantQueries,
target,
getQueryContract,
isFail,
isPass,
ifExists,
getBehvName,
identifier,
getSMT
) where
import Prelude hiding (GT, LT)
import Data.Containers.ListUtils (nubOrd)
import System.Process (createProcess, cleanupProcess, proc, ProcessHandle, std_in, std_out, std_err, StdStream(..))
import Text.Regex.TDFA hiding (empty)
import Text.PrettyPrint.ANSI.Leijen hiding ((<$>))
import Control.Applicative ((<|>))
import Control.Monad.Reader
import Data.List.NonEmpty (NonEmpty(..))
import qualified Data.List.NonEmpty as NonEmpty
import Data.Maybe
import Data.List
import GHC.IO.Handle (Handle, hGetLine, hPutStr, hFlush)
import Data.ByteString.UTF8 (fromString)
import Data.Singletons (SomeSing(..))
import Syntax
import Syntax.Annotated
import Print
import Type (defaultStore)
--- ** Data ** ---
data Solver = Z3 | CVC4
deriving Eq
instance Show Solver where
show Z3 = "z3"
show CVC4 = "cvc4"
data SMTConfig = SMTConfig
{ _solver :: Solver
, _timeout :: Integer
, _debug :: Bool
}
type SMT2 = String
-- | The context is a `Reader` monad which allows us to read
-- the name of the current interface.
type Ctx = Reader Id
-- | Specify the name to use as the current interface when creating SMT-code.
withInterface :: Id -> Ctx SMT2 -> SMT2
withInterface = flip runReader
-- | An SMTExp is a structured representation of an SMT Expression
-- The _storage, _calldata, and _environment fields hold variable declarations
-- The _assertions field holds the various constraints that should be satisfied
data SMTExp = SMTExp
{ _storage :: [SMT2]
, _calldata :: [SMT2]
, _environment :: [SMT2]
, _assertions :: [SMT2]
}
deriving (Show)
instance Pretty SMTExp where
pretty e = vsep [storage, calldata, environment, assertions]
where
storage = text ";STORAGE:" <$$> (vsep . (fmap text) . nubOrd . _storage $ e) <> line
calldata = text ";CALLDATA:" <$$> (vsep . (fmap text) . nubOrd . _calldata $ e) <> line
environment = text ";ENVIRONMENT" <$$> (vsep . (fmap text) . nubOrd . _environment $ e) <> line
assertions = text ";ASSERTIONS:" <$$> (vsep . (fmap text) . nubOrd . _assertions $ e) <> line
-- | A Query is a structured representation of an SMT query for an individual
-- expression, along with the metadata needed to extract a model from a satisfiable query
data Query
= Postcondition Transition (Exp Bool) SMTExp
| Inv Invariant (Constructor, SMTExp) [(Behaviour, SMTExp)]
deriving (Show)
data SMTResult
= Sat Model
| Unsat
| Unknown
| Error Int String
deriving (Show)
-- | An assignment of concrete values to symbolic variables structured in a way
-- to allow for easy pretty printing. The LHS of each pair is the symbolic
-- variable, and the RHS is the concrete value assigned to that variable in the
-- counterexample
data Model = Model
{ _mprestate :: [(StorageLocation, TypedExp)]
, _mpoststate :: [(StorageLocation, TypedExp)]
, _mcalldata :: (String, [(Decl, TypedExp)])
, _menvironment :: [(EthEnv, TypedExp)]
-- invariants always have access to the constructor context
, _minitargs :: [(Decl, TypedExp)]
}
deriving (Show)
instance Pretty Model where
pretty (Model prestate poststate (ifaceName, args) environment initargs) =
(underline . text $ "counterexample:") <$$> line
<> (indent 2
( calldata'
<$$> ifExists environment (line <> environment' <> line)
<$$> storage
<$$> ifExists initargs (line <> initargs')
))
where
calldata' = text "calldata:" <$$> line <> (indent 2 $ formatSig ifaceName args)
environment' = text "environment:" <$$> line <> (indent 2 . vsep $ fmap formatEnvironment environment)
storage = text "storage:" <$$> (indent 2 . vsep $ [ifExists prestate (line <> prestate'), poststate'])
initargs' = text "constructor arguments:" <$$> line <> (indent 2 $ formatSig "constructor" initargs)
prestate' = text "prestate:" <$$> line <> (indent 2 . vsep $ fmap formatStorage prestate) <> line
poststate' = text "poststate:" <$$> line <> (indent 2 . vsep $ fmap formatStorage poststate)
formatSig iface cd = text iface <> (encloseSep lparen rparen (text ", ") $ fmap formatCalldata cd)
formatCalldata (Decl _ name, val) = text $ name <> " = " <> prettyTypedExp val
formatEnvironment (env, val) = text $ prettyEnv env <> " = " <> prettyTypedExp val
formatStorage (loc, val) = text $ prettyLocation loc <> " = " <> prettyTypedExp val
data SolverInstance = SolverInstance
{ _type :: Solver
, _stdin :: Handle
, _stdout :: Handle
, _stderr :: Handle
, _process :: ProcessHandle
}
--- ** Analysis Passes ** ---
-- | For each postcondition in the claim we construct a query that:
-- - Asserts that the preconditions hold
-- - Asserts that storage has been updated according to the rewrites in the behaviour
-- - Asserts that the postcondition cannot be reached
-- If this query is unsatisfiable, then there exists no case where the postcondition can be violated.
mkPostconditionQueries :: Claim -> [Query]
mkPostconditionQueries (B behv@(Behaviour _ Pass _ (Interface ifaceName decls) preconds postconds stateUpdates _)) = mkQuery <$> postconds
where
-- declare vars
storage = concatMap (declareStorageLocation . locFromRewrite) stateUpdates
args = declareArg ifaceName <$> decls
envs = declareEthEnv <$> ethEnvFromBehaviour behv
-- constraints
pres = mkAssert ifaceName <$> preconds
updates = encodeUpdate ifaceName <$> stateUpdates
mksmt e = SMTExp
{ _storage = storage
, _calldata = args
, _environment = envs
, _assertions = [mkAssert ifaceName . Neg nowhere $ e] <> pres <> updates
}
mkQuery e = Postcondition (Behv behv) e (mksmt e)
mkPostconditionQueries (C constructor@(Constructor _ Pass (Interface ifaceName decls) preconds postconds initialStorage stateUpdates)) = mkQuery <$> postconds
where
-- declare vars
localStorage = declareInitialStorage <$> initialStorage
externalStorage = concatMap (declareStorageLocation . locFromRewrite) stateUpdates
args = declareArg ifaceName <$> decls
envs = declareEthEnv <$> ethEnvFromConstructor constructor
-- constraints
pres = mkAssert ifaceName <$> preconds
updates = encodeUpdate ifaceName <$> stateUpdates
initialStorage' = encodeInitialStorage ifaceName <$> initialStorage
mksmt e = SMTExp
{ _storage = localStorage <> externalStorage
, _calldata = args
, _environment = envs
, _assertions = [mkAssert ifaceName . Neg nowhere $ e] <> pres <> updates <> initialStorage'
}
mkQuery e = Postcondition (Ctor constructor) e (mksmt e)
mkPostconditionQueries _ = []
-- | For each invariant in the list of input claims, we first gather all the
-- specs relevant to that invariant (i.e. the constructor for that contract,
-- and all passing behaviours for that contract).
--
-- For the constructor we build a query that:
-- - Asserts that all preconditions hold
-- - Asserts that external storage has been updated according to the spec
-- - Asserts that internal storage values have the value given in the creates block
-- - Asserts that the invariant does not hold over the poststate
--
-- For the behaviours, we build a query that:
-- - Asserts that the invariant holds over the prestate
-- - Asserts that all preconditions hold
-- - Asserts that storage has been updated according to the spec
-- - Asserts that the invariant does not hold over the poststate
--
-- If all of the queries return `unsat` then we have an inductive proof that
-- the invariant holds for all possible contract states.
mkInvariantQueries :: [Claim] -> [Query]
mkInvariantQueries claims = fmap mkQuery gathered
where
mkQuery (inv, ctor, behvs) = Inv inv (mkInit inv ctor) (fmap (mkBehv inv ctor) behvs)
gathered = fmap (\inv -> (inv, getConstructor inv, getBehaviours inv)) [i | I i <- claims]
getBehaviours (Invariant c _ _ _) = [b | B b <- claims, matchBehaviour c b]
getConstructor (Invariant c _ _ _) = head [c' | C c' <- claims, matchConstructor c c']
matchBehaviour contract behv = (_mode behv) == Pass && (_contract behv) == contract
matchConstructor contract defn = _cmode defn == Pass && _cname defn == contract
mkInit :: Invariant -> Constructor -> (Constructor, SMTExp)
mkInit (Invariant _ invConds _ (_,invPost)) ctor@(Constructor _ _ (Interface ifaceName decls) preconds _ initialStorage stateUpdates) = (ctor, smt)
where
-- declare vars
localStorage = declareInitialStorage <$> initialStorage
externalStorage = concatMap (declareStorageLocation . locFromRewrite) stateUpdates
args = declareArg ifaceName <$> decls
envs = declareEthEnv <$> ethEnvFromConstructor ctor
-- constraints
pres = mkAssert ifaceName <$> preconds <> invConds
updates = encodeUpdate ifaceName <$> stateUpdates
initialStorage' = encodeInitialStorage ifaceName <$> initialStorage
postInv = mkAssert ifaceName $ Neg nowhere invPost
smt = SMTExp
{ _storage = localStorage <> externalStorage
, _calldata = args
, _environment = envs
, _assertions = postInv : pres <> updates <> initialStorage'
}
mkBehv :: Invariant -> Constructor -> Behaviour -> (Behaviour, SMTExp)
mkBehv (Invariant _ invConds invStorageBounds (invPre,invPost)) ctor behv = (behv, smt)
where
(Interface ctorIface ctorDecls) = _cinterface ctor
(Interface behvIface behvDecls) = _interface behv
-- storage locs mentioned in the invariant but not in the behaviour
implicitLocs = Constant <$> (locsFromExp invPre \\ (locFromRewrite <$> _stateUpdates behv))
-- declare vars
invEnv = declareEthEnv <$> ethEnvFromExp invPre
behvEnv = declareEthEnv <$> ethEnvFromBehaviour behv
initArgs = declareArg ctorIface <$> ctorDecls
behvArgs = declareArg behvIface <$> behvDecls
storage = concatMap (declareStorageLocation . locFromRewrite) (_stateUpdates behv <> implicitLocs)
-- constraints
preInv = mkAssert ctorIface $ invPre
postInv = mkAssert ctorIface . Neg nowhere $ invPost
behvConds = mkAssert behvIface <$> _preconditions behv
invConds' = mkAssert ctorIface <$> invConds <> invStorageBounds
implicitLocs' = encodeUpdate ctorIface <$> implicitLocs
updates = encodeUpdate behvIface <$> _stateUpdates behv
smt = SMTExp
{ _storage = storage
, _calldata = initArgs <> behvArgs
, _environment = invEnv <> behvEnv
, _assertions = [preInv, postInv] <> behvConds <> invConds' <> implicitLocs' <> updates
}
--- ** Solver Interaction ** ---
-- | Checks the satisfiability of all smt expressions contained with a query, and returns the results as a list
runQuery :: SolverInstance -> Query -> IO (Query, [SMTResult])
runQuery solver query@(Postcondition trans _ smt) = do
res <- checkSat solver (getPostconditionModel trans) smt
pure (query, [res])
runQuery solver query@(Inv (Invariant _ _ _ predicate) (ctor, ctorSMT) behvs) = do
ctorRes <- runCtor
behvRes <- mapM runBehv behvs
pure (query, ctorRes : behvRes)
where
runCtor = checkSat solver (getInvariantModel predicate ctor Nothing) ctorSMT
runBehv (b, smt) = checkSat solver (getInvariantModel predicate ctor (Just b)) smt
-- | Checks the satisfiability of a single SMT expression, and uses the
-- provided `modelFn` to extract a model if the solver returns `sat`
checkSat :: SolverInstance -> (SolverInstance -> IO Model) -> SMTExp -> IO SMTResult
checkSat solver modelFn smt = do
err <- sendLines solver ("(reset)" : (lines . show . pretty $ smt))
case err of
Nothing -> do
sat <- sendCommand solver "(check-sat)"
case sat of
"sat" -> Sat <$> modelFn solver
"unsat" -> pure Unsat
"timeout" -> pure Unknown
"unknown" -> pure Unknown
_ -> pure $ Error 0 $ "Unable to parse solver output: " <> sat
Just msg -> do
pure $ Error 0 msg
-- | Global settings applied directly after each solver instance is spawned
smtPreamble :: [SMT2]
smtPreamble = [ "(set-logic ALL)" ]
-- | Arguments used when spawing a solver instance
solverArgs :: SMTConfig -> [String]
solverArgs (SMTConfig solver timeout _) = case solver of
Z3 ->
[ "-in"
, "-t:" <> show timeout]
CVC4 ->
[ "--lang=smt"
, "--interactive"
, "--no-interactive-prompt"
, "--produce-models"
, "--tlimit-per=" <> show timeout]
-- | Spawns a solver instance, and sets the various global config options that we use for our queries
spawnSolver :: SMTConfig -> IO SolverInstance
spawnSolver config@(SMTConfig solver _ _) = do
let cmd = (proc (show solver) (solverArgs config)) { std_in = CreatePipe, std_out = CreatePipe, std_err = CreatePipe }
(Just stdin, Just stdout, Just stderr, process) <- createProcess cmd
let solverInstance = SolverInstance solver stdin stdout stderr process
_ <- sendCommand solverInstance "(set-option :print-success true)"
err <- sendLines solverInstance smtPreamble
case err of
Nothing -> pure solverInstance
Just msg -> error $ "could not spawn solver: " <> msg
-- | Cleanly shutdown a running solver instnace
stopSolver :: SolverInstance -> IO ()
stopSolver (SolverInstance _ stdin stdout stderr process) = cleanupProcess (Just stdin, Just stdout, Just stderr, process)
-- | Sends a list of commands to the solver. Returns the first error, if there was one.
sendLines :: SolverInstance -> [SMT2] -> IO (Maybe String)
sendLines solver smt = case smt of
[] -> pure Nothing
hd : tl -> do
suc <- sendCommand solver hd
if suc == "success"
then sendLines solver tl
else pure (Just suc)
-- | Sends a single command to the solver, returns the first available line from the output buffer
sendCommand :: SolverInstance -> SMT2 -> IO String
sendCommand (SolverInstance _ stdin stdout _ _) cmd =
if null cmd || ";" `isPrefixOf` cmd then pure "success" -- blank lines and comments do not produce any output from the solver
else do
hPutStr stdin (cmd <> "\n")
hFlush stdin
hGetLine stdout
--- ** Model Extraction ** ---
-- | Extracts an assignment of values for the variables in the given
-- transition. Assumes that a postcondition query for the given transition has
-- previously been checked in the given solver instance.
getPostconditionModel :: Transition -> SolverInstance -> IO Model
getPostconditionModel (Ctor ctor) solver = getCtorModel ctor solver
getPostconditionModel (Behv behv) solver = do
let locs = locsFromBehaviour behv
env = ethEnvFromBehaviour behv
Interface ifaceName decls = _interface behv
prestate <- mapM (getStorageValue solver ifaceName Pre) locs
poststate <- mapM (getStorageValue solver ifaceName Post) locs
calldata <- mapM (getCalldataValue solver ifaceName) decls
environment <- mapM (getEnvironmentValue solver) env
pure $ Model
{ _mprestate = prestate
, _mpoststate = poststate
, _mcalldata = (ifaceName, calldata)
, _menvironment = environment
, _minitargs = []
}
-- | Extracts an assignment of values for the variables in the given
-- transition. Assumes that an invariant query has previously been checked
-- in the given solver instance.
getInvariantModel :: InvariantPred -> Constructor -> Maybe Behaviour -> SolverInstance -> IO Model
getInvariantModel _ ctor Nothing solver = getCtorModel ctor solver
getInvariantModel predicate ctor (Just behv) solver = do
let locs = nub $ locsFromBehaviour behv <> locsFromExp (invExp predicate)
env = nub $ ethEnvFromBehaviour behv <> ethEnvFromExp (invExp predicate)
Interface behvIface behvDecls = _interface behv
Interface ctorIface ctorDecls = _cinterface ctor
-- TODO: v ugly to ignore the ifaceName here, but it's safe...
prestate <- mapM (getStorageValue solver "" Pre) locs
poststate <- mapM (getStorageValue solver "" Post) locs
behvCalldata <- mapM (getCalldataValue solver behvIface) behvDecls
ctorCalldata <- mapM (getCalldataValue solver ctorIface) ctorDecls
environment <- mapM (getEnvironmentValue solver) env
pure $ Model
{ _mprestate = prestate
, _mpoststate = poststate
, _mcalldata = (behvIface, behvCalldata)
, _menvironment = environment
, _minitargs = ctorCalldata
}
-- | Extracts an assignment for the variables in the given contructor
getCtorModel :: Constructor -> SolverInstance -> IO Model
getCtorModel ctor solver = do
let locs = locsFromConstructor ctor
env = ethEnvFromConstructor ctor
Interface ifaceName decls = _cinterface ctor
poststate <- mapM (getStorageValue solver ifaceName Post) locs
calldata <- mapM (getCalldataValue solver ifaceName) decls
environment <- mapM (getEnvironmentValue solver) env
pure $ Model
{ _mprestate = []
, _mpoststate = poststate
, _mcalldata = (ifaceName, calldata)
, _menvironment = environment
, _minitargs = []
}
-- | Gets a concrete value from the solver for the given storage location
getStorageValue :: SolverInstance -> Id -> When -> StorageLocation -> IO (StorageLocation, TypedExp)
getStorageValue solver ifaceName whn loc@(Loc typ _) = do
output <- getValue solver name
-- TODO: handle errors here...
pure (loc, parseModel typ output)
where
name = if isMapping loc
then withInterface ifaceName
$ select
(nameFromLoc whn loc)
(NonEmpty.fromList $ ixsFromLocation loc)
else nameFromLoc whn loc
-- | Gets a concrete value from the solver for the given calldata argument
getCalldataValue :: SolverInstance -> Id -> Decl -> IO (Decl, TypedExp)
getCalldataValue solver ifaceName decl@(Decl (FromAbi tp) _) = do
val <- parseModel tp <$> getValue solver (nameFromDecl ifaceName decl)
pure (decl, val)
-- | Gets a concrete value from the solver for the given environment variable
getEnvironmentValue :: SolverInstance -> EthEnv -> IO (EthEnv, TypedExp)
getEnvironmentValue solver env = do
output <- getValue solver (prettyEnv env)
let val = case lookup env defaultStore of
Just (FromAct typ) -> parseModel typ output
_ -> error $ "Internal Error: could not determine a type for" <> show env
pure (env, val)
-- | Calls `(get-value)` for the given identifier in the given solver instance.
getValue :: SolverInstance -> String -> IO String
getValue solver name = sendCommand solver $ "(get-value (" <> name <> "))"
-- | Parse the result of a call to getValue as the supplied type.
parseModel :: SType a -> String -> TypedExp
parseModel = \case
SInteger -> _TExp . LitInt nowhere . read . parseSMTModel
SBoolean -> _TExp . LitBool nowhere . readBool . parseSMTModel
SByteStr -> _TExp . ByLit nowhere . fromString . parseSMTModel
where
readBool "true" = True
readBool "false" = False
readBool s = error ("Could not parse " <> s <> "into a bool")
-- | Extracts a string representation of the value in the output from a call to `(get-value)`
parseSMTModel :: String -> String
parseSMTModel s = if length s0Caps == 1
then if length s1Caps == 1 then head s1Caps else head s0Caps
else ""
where
-- output should be in the form "((identifier value))" for positive integers / booleans / strings
-- or "((identifier (value)))" for negative integers.
-- The stage0 regex first extracts either value or (value), and then the
-- stage1 regex is used to strip the additional brackets if required.
stage0 = "\\`\\(\\([a-zA-Z0-9_]+ ([ \"\\(\\)a-zA-Z0-9_\\-]+)\\)\\)\\'"
stage1 = "\\(([ a-zA-Z0-9_\\-]+)\\)"
s0Caps = getCaptures s stage0
s1Caps = getCaptures (head s0Caps) stage1
getCaptures str regex = captures
where (_, _, _, captures) = str =~ regex :: (String, String, String, [String])
--- ** SMT2 Generation ** ---
-- | encodes a storage update from a constructor creates block as an smt assertion
encodeInitialStorage :: Id -> StorageUpdate -> SMT2
encodeInitialStorage behvName (Update _ item expr) =
let
postentry = withInterface behvName $ expToSMT2 (TEntry nowhere Post item)
expression = withInterface behvName $ expToSMT2 expr
in "(assert (= " <> postentry <> " " <> expression <> "))"
-- | declares a storage location that is created by the constructor, these
-- locations have no prestate, so we declare a post var only
declareInitialStorage :: StorageUpdate -> SMT2
declareInitialStorage (locFromUpdate -> Loc _ item) = case ixsFromItem item of
[] -> constant (nameFromItem Post item) (itemType item)
(ix:ixs) -> array (nameFromItem Post item) (ix :| ixs) (itemType item)
-- | encodes a storge update rewrite as an smt assertion
encodeUpdate :: Id -> Rewrite -> SMT2
encodeUpdate _ (Constant loc) = "(assert (= " <> nameFromLoc Pre loc <> " " <> nameFromLoc Post loc <> "))"
encodeUpdate behvName (Rewrite update) = encodeInitialStorage behvName update
-- | declares a storage location that exists both in the pre state and the post
-- state (i.e. anything except a loc created by a constructor claim)
declareStorageLocation :: StorageLocation -> [SMT2]
declareStorageLocation (Loc _ item) = case ixsFromItem item of
[] -> [ constant (nameFromItem Pre item) (itemType item)
, constant (nameFromItem Post item) (itemType item) ]
(ix:ixs) -> [ array (nameFromItem Pre item) (ix :| ixs) (itemType item)
, array (nameFromItem Post item) (ix :| ixs) (itemType item) ]
-- | produces an SMT2 expression declaring the given decl as a symbolic constant
declareArg :: Id -> Decl -> SMT2
declareArg behvName d@(Decl typ _) = constant (nameFromDecl behvName d) (actType typ)
-- | produces an SMT2 expression declaring the given EthEnv as a symbolic constant
declareEthEnv :: EthEnv -> SMT2
declareEthEnv env = constant (prettyEnv env) tp
where tp = fromJust . lookup env $ defaultStore
-- | encodes a typed expression as an smt2 expression
typedExpToSMT2 :: TypedExp -> Ctx SMT2
typedExpToSMT2 (TExp _ e) = expToSMT2 e
-- | encodes the given Exp as an smt2 expression
expToSMT2 :: Exp a -> Ctx SMT2
expToSMT2 expr = case expr of
-- booleans
And _ a b -> binop "and" a b
Or _ a b -> binop "or" a b
Impl _ a b -> binop "=>" a b
Neg _ a -> unop "not" a
LT _ a b -> binop "<" a b
LEQ _ a b -> binop "<=" a b
GEQ _ a b -> binop ">=" a b
GT _ a b -> binop ">" a b
LitBool _ a -> pure $ if a then "true" else "false"
-- integers
Add _ a b -> binop "+" a b
Sub _ a b -> binop "-" a b
Mul _ a b -> binop "*" a b
Div _ a b -> binop "div" a b
Mod _ a b -> binop "mod" a b
Exp _ a b -> expToSMT2 $ simplifyExponentiation a b
LitInt _ a -> pure $ if a >= 0
then show a
else "(- " <> (show . negate $ a) <> ")" -- cvc4 does not accept negative integer literals
IntEnv _ a -> pure $ prettyEnv a
-- bounds
IntMin p a -> expToSMT2 . LitInt p $ intmin a
IntMax _ a -> pure . show $ intmax a
UIntMin _ a -> pure . show $ uintmin a
UIntMax _ a -> pure . show $ uintmax a
-- bytestrings
Cat _ a b -> binop "str.++" a b
Slice p a start end -> triop "str.substr" a start (Sub p end start)
ByStr _ a -> pure a
ByLit _ a -> pure $ show a
ByEnv _ a -> pure $ prettyEnv a
-- polymorphic
Eq _ a b -> binop "=" a b
NEq p a b -> unop "not" (Eq p a b)
ITE _ a b c -> triop "ite" a b c
Var _ _ a -> nameFromVarId a
TEntry _ w item -> entry item w
where
unop :: String -> Exp a -> Ctx SMT2
unop op a = ["(" <> op <> " " <> a' <> ")" | a' <- expToSMT2 a]
binop :: String -> Exp a -> Exp b -> Ctx SMT2
binop op a b = ["(" <> op <> " " <> a' <> " " <> b' <> ")"
| a' <- expToSMT2 a, b' <- expToSMT2 b]
triop :: String -> Exp a -> Exp b -> Exp c -> Ctx SMT2
triop op a b c = ["(" <> op <> " " <> a' <> " " <> b' <> " " <> c' <> ")"
| a' <- expToSMT2 a, b' <- expToSMT2 b, c' <- expToSMT2 c]
entry :: TStorageItem a -> When -> Ctx SMT2
entry item whn = case ixsFromItem item of
[] -> pure $ nameFromItem whn item
(ix:ixs) -> select (nameFromItem whn item) (ix :| ixs)
-- | SMT2 has no support for exponentiation, but we can do some preprocessing
-- if the RHS is concrete to provide some limited support for exponentiation
simplifyExponentiation :: Exp Integer -> Exp Integer -> Exp Integer
simplifyExponentiation a b = fromMaybe (error "Internal Error: no support for symbolic exponents in SMT lib")
$ [LitInt nowhere $ a' ^ b' | a' <- eval a, b' <- evalb]
<|> [foldr (Mul nowhere) (LitInt nowhere 1) (genericReplicate b' a) | b' <- evalb]
where
evalb = eval b -- TODO is this actually necessary to prevent double evaluation?
-- | declare a constant in smt2
constant :: Id -> ActType -> SMT2
constant name tp = "(declare-const " <> name <> " " <> sType tp <> ")"
-- | encode the given boolean expression as an assertion in smt2
mkAssert :: Id -> Exp Bool -> SMT2
mkAssert c e = "(assert " <> withInterface c (expToSMT2 e) <> ")"
-- | declare a (potentially nested) array in smt2
array :: Id -> NonEmpty TypedExp -> ActType -> SMT2
array name (hd :| tl) ret = "(declare-const " <> name <> " (Array " <> sType' hd <> " " <> valueDecl tl <> "))"
where
valueDecl [] = sType ret
valueDecl (h : t) = "(Array " <> sType' h <> " " <> valueDecl t <> ")"
-- | encode an array lookup in smt2
select :: String -> NonEmpty TypedExp -> Ctx SMT2
select name (hd :| tl) = do
inner <- ["(" <> "select" <> " " <> name <> " " <> hd' <> ")" | hd' <- typedExpToSMT2 hd]
foldM (\smt ix -> ["(select " <> smt <> " " <> ix' <> ")" | ix' <- typedExpToSMT2 ix]) inner tl
-- | act -> smt2 type translation
sType :: ActType -> SMT2
sType Integer = "Int"
sType Boolean = "Bool"
sType ByteStr = "String"
-- | act -> smt2 type translation
sType' :: TypedExp -> SMT2
sType' (TExp t _) = sType $ SomeSing t
--- ** Variable Names ** ---
-- Construct the smt2 variable name for a given storage item
nameFromItem :: When -> TStorageItem a -> Id
nameFromItem whn (Item _ c name _) = c @@ name @@ show whn
-- Construct the smt2 variable name for a given storage location
nameFromLoc :: When -> StorageLocation -> Id
nameFromLoc whn (Loc _ item) = nameFromItem whn item
-- Construct the smt2 variable name for a given decl
nameFromDecl :: Id -> Decl -> Id
nameFromDecl ifaceName (Decl _ name) = ifaceName @@ name
-- Construct the smt2 variable name for a given act variable
nameFromVarId :: Id -> Ctx Id
nameFromVarId name = [behvName @@ name | behvName <- ask]
(@@) :: String -> String -> String
x @@ y = x <> "_" <> y
--- ** Util ** ---
-- | The target expression of a query.
target :: Query -> Exp Bool
target (Postcondition _ e _) = e
target (Inv (Invariant _ _ _ e) _ _) = invExp e
getQueryContract :: Query -> Id
getQueryContract (Postcondition (Ctor ctor) _ _) = _cname ctor
getQueryContract (Postcondition (Behv behv) _ _) = _contract behv
getQueryContract (Inv (Invariant c _ _ _) _ _) = c
isFail :: SMTResult -> Bool
isFail Unsat = False
isFail _ = True
isPass :: SMTResult -> Bool
isPass = not . isFail
getBehvName :: Query -> Doc
getBehvName (Postcondition (Ctor _) _ _) = (text "the") <+> (bold . text $ "constructor")
getBehvName (Postcondition (Behv behv) _ _) = (text "behaviour") <+> (bold . text $ _name behv)
getBehvName (Inv {}) = error "Internal Error: invariant queries do not have an associated behaviour"
identifier :: Query -> Doc
identifier q@(Inv (Invariant _ _ _ e) _ _) = (bold . text . prettyInvPred $ e) <+> text "of" <+> (bold . text . getQueryContract $ q)
identifier q@Postcondition {} = (bold . text . prettyExp . target $ q) <+> text "in" <+> getBehvName q <+> text "of" <+> (bold . text . getQueryContract $ q)
getSMT :: Query -> Doc
getSMT (Postcondition _ _ smt) = pretty smt
getSMT (Inv _ (_, csmt) behvs) = text "; constructor" <$$> sep' <$$> line <> pretty csmt <$$> vsep (fmap formatBehv behvs)
where
formatBehv (b, smt) = line <> text "; behaviour: " <> (text . _name $ b) <$$> sep' <$$> line <> pretty smt
sep' = text "; -------------------------------"
ifExists :: Foldable t => t a -> Doc -> Doc
ifExists a b = if null a then empty else b