Newer
Older
module LogicIR.Backend.Z3.API
( equivalentTo
, showZ3AST
)
where
import qualified Z3.Base as Z3
import Z3.Monad hiding (Model)
import Control.Exception.Base (tryJust)
import qualified Data.Map as M
import Data.Maybe (fromJust)
import Data.String
import LogicIR.Expr (LExpr, lnot, (.&&), (.<==>))
data Z3Response = Satisfiable Model | Unsatisfiable | Undecidable
deriving Show
-- | Determine the equality of two method's pre/post conditions.
equivalentTo :: LExpr -> LExpr -> IO Response
equivalentTo l l' = do
equiv <- checkZ3 $ lnot (l .<==> l')
case equiv of
Satisfiable m -> do
res <- sequence $ checkZ3 <$>
[l .&& l', l .&& lnot l', lnot l .&& l', lnot l .&& lnot l']
let feedback = mkFeedback $ toBool <$> res
return $ NotEquivalent m Feedback {pre = feedback, post = defFeedback}
Unsatisfiable -> return Equivalent
Undecidable -> return Undefined
where
mkFeedback :: [Bool] -> SingleFeedback
mkFeedback [b1, b2, b3, b4] = (b1, b2, b3, b4)
mkFeedback _ = defFeedback
toBool :: Z3Response -> Bool
toBool (Satisfiable _) = True
toBool _ = False
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
-- | Checks the validity of a logical statement.
checkZ3 :: LExpr -> IO Z3Response
checkZ3 l = catchTimeout $ tryZ3 $ do
-- solverReset
fv <- freeVars l
ast <- lExprToZ3Ast l
-- Get model
(r, model) <- local $ assert ast >> solverCheckAndGetModel
response <- case r of
Sat -> do
-- Construct counter-model
ms <- local (sanitize . M.fromList <$>
forM (M.toList fv) (evalAST fv (fromJust model)))
return $ Satisfiable ms
Unsat -> return Unsatisfiable
_ -> return Undecidable
solverReset
return response
where
-- Construct model values
evalAST :: FreeVars -> Z3.Model -> (String, AST) -> Z3 (String, ModelVal)
evalAST fv m (k, ast) = do
v <- fromJust <$> modelEval m ast True
sortKind <- getSort v >>= getSortKind
if sortKind == Z3_ARRAY_SORT then do
-- Retrieve array's length
lenName <- mkStringSymbol (k ++ "?length") >>= mkIntVar
f <- snd <$> evalAST fv m (k ++ "?length", lenName)
let len = case f of
(IntVal i) -> i
_ -> error "non-int length"
-- Iterate array "points"
modelVals <- forM [0..(len-1)] (\i -> do
indexAST <- mkInteger $ toInteger i
pointAST <- mkSelect v indexAST
snd <$> evalAST fv m ("", pointAST)
)
return (k, ManyVal modelVals)
else do
v' <- astToString v
return (k, fromString v' :: ModelVal)
-- | Run a Z3 action in 'IO', but catch timeouts.
catchTimeout :: IO Z3Response -> IO Z3Response
catchTimeout prog = do
res <- tryJust errorSelector prog
return $ case res of
Left () -> Undecidable
Right r -> r
where
errorSelector :: Z3Error -> Maybe ()
errorSelector err =
case errCode err of
InvalidUsage -> Just ()
_ -> Nothing
-- | Z3 try evaluation with timeout.
tryZ3 prog = do
env <- newEnv Nothing ( opt "timeout" (5000 :: Integer)
+? opt "model_validate" True
+? opt "well_sorted_check" True
+? opt "auto_config" True
-- +? opt "unsat_core" True
)
evalZ3WithEnv prog env
-- | Pretty-print Z3 AST.
showZ3AST :: Z3 AST -> IO String
showZ3AST ast' = tryZ3 $ ast' >>= astToString