{-# LANGUAGE DefaultSignatures #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE Rank2Types #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Data.SBV.SMT.SMT (
Modelable(..)
, SatModel(..), genParse
, extractModels, getModelValues
, getModelDictionaries, getModelUninterpretedValues
, displayModels, showModel, shCV, showModelDictionary
, standardEngine
, ThmResult(..)
, SatResult(..)
, AllSatResult(..)
, SafeResult(..)
, OptimizeResult(..)
)
where
import qualified Control.Exception as C
import Control.Concurrent (newEmptyMVar, takeMVar, putMVar, forkIO)
import Control.DeepSeq (NFData(..))
import Control.Monad (zipWithM)
import Data.Char (isSpace)
import Data.Maybe (fromMaybe, isJust)
import Data.Int (Int8, Int16, Int32, Int64)
import Data.List (intercalate, isPrefixOf, transpose)
import Data.Word (Word8, Word16, Word32, Word64)
import Data.IORef (readIORef, writeIORef)
import Data.Time (getZonedTime, defaultTimeLocale, formatTime, diffUTCTime, getCurrentTime)
import System.Directory (findExecutable)
import System.Environment (getEnv)
import System.Exit (ExitCode(..))
import System.IO (hClose, hFlush, hPutStrLn, hGetContents, hGetLine)
import System.Process (runInteractiveProcess, waitForProcess, terminateProcess)
import qualified Data.Map.Strict as M
import Data.SBV.Core.AlgReals
import Data.SBV.Core.Data
import Data.SBV.Core.Symbolic (SMTEngine, State(..))
import Data.SBV.Core.Concrete (showCV)
import Data.SBV.Core.Kind (showBaseKind)
import Data.SBV.SMT.Utils (showTimeoutValue, alignPlain, debug, mergeSExpr, SBVException(..))
import Data.SBV.Utils.PrettyNum
import Data.SBV.Utils.Lib (joinArgs, splitArgs)
import Data.SBV.Utils.SExpr (parenDeficit)
import Data.SBV.Utils.TDiff (Timing(..), showTDiff)
import qualified System.Timeout as Timeout (timeout)
resultConfig :: SMTResult -> SMTConfig
resultConfig (Unsatisfiable c _ ) = c
resultConfig (Satisfiable c _ ) = c
resultConfig (SatExtField c _ ) = c
resultConfig (Unknown c _ ) = c
resultConfig (ProofError c _ _) = c
newtype ThmResult = ThmResult SMTResult
deriving NFData
newtype SatResult = SatResult SMTResult
deriving NFData
newtype AllSatResult = AllSatResult (Bool, Bool, Bool, [SMTResult])
newtype SafeResult = SafeResult (Maybe String, String, SMTResult)
data OptimizeResult = LexicographicResult SMTResult
| ParetoResult (Bool, [SMTResult])
| IndependentResult [(String, SMTResult)]
instance Show ThmResult where
show (ThmResult r) = showSMTResult "Q.E.D."
"Unknown"
"Falsifiable" "Falsifiable. Counter-example:\n" "Falsifiable in an extension field:\n" r
instance Show SatResult where
show (SatResult r) = showSMTResult "Unsatisfiable"
"Unknown"
"Satisfiable" "Satisfiable. Model:\n" "Satisfiable in an extension field. Model:\n" r
instance Show SafeResult where
show (SafeResult (mbLoc, msg, r)) = showSMTResult (tag "No violations detected")
(tag "Unknown")
(tag "Violated") (tag "Violated. Model:\n") (tag "Violated in an extension field:\n") r
where loc = maybe "" (++ ": ") mbLoc
tag s = loc ++ msg ++ ": " ++ s
instance Show AllSatResult where
show (AllSatResult (l, e, u, xs)) = go (0::Int) xs
where warnings = case (e, u) of
(False, False) -> ""
(False, True) -> " (Search stopped since solver has returned unknown.)"
(True, False) -> " (Unique up to prefix existentials.)"
(True, True) -> " (Search stopped becase solver has returned unknown, only prefix existentials were considered.)"
go c (s:ss) = let c' = c+1
(ok, o) = sh c' s
in c' `seq` if ok then o ++ "\n" ++ go c' ss else o
go c [] = case (l, c) of
(True, _) -> "Search stopped since model count request was reached." ++ warnings
(False, 0) -> "No solutions found."
(False, 1) -> "This is the only solution." ++ warnings
(False, _) -> "Found " ++ show c ++ " different solutions." ++ warnings
sh i c = (ok, showSMTResult "Unsatisfiable"
"Unknown"
("Solution #" ++ show i ++ ":\nSatisfiable") ("Solution #" ++ show i ++ ":\n")
("Solution $" ++ show i ++ " in an extension field:\n")
c)
where ok = case c of
Satisfiable{} -> True
_ -> False
instance Show OptimizeResult where
show res = case res of
LexicographicResult r -> sh id r
IndependentResult rs -> multi "objectives" (map (uncurry shI) rs)
ParetoResult (False, [r]) -> sh ("Unique pareto front: " ++) r
ParetoResult (False, rs) -> multi "pareto optimal values" (zipWith shP [(1::Int)..] rs)
ParetoResult (True, rs) -> multi "pareto optimal values" (zipWith shP [(1::Int)..] rs)
++ "\n*** Note: Pareto-front extraction was terminated as requested by the user."
++ "\n*** There might be many other results!"
where multi w [] = "There are no " ++ w ++ " to display models for."
multi _ xs = intercalate "\n" xs
shI n = sh (\s -> "Objective " ++ show n ++ ": " ++ s)
shP i = sh (\s -> "Pareto front #" ++ show i ++ ": " ++ s)
sh tag = showSMTResult (tag "Unsatisfiable.")
(tag "Unknown.")
(tag "Optimal with no assignments.")
(tag "Optimal model:" ++ "\n")
(tag "Optimal in an extension field:" ++ "\n")
class SatModel a where
parseCVs :: [CV] -> Maybe (a, [CV])
cvtModel :: (a -> Maybe b) -> Maybe (a, [CV]) -> Maybe (b, [CV])
cvtModel f x = x >>= \(a, r) -> f a >>= \b -> return (b, r)
default parseCVs :: Read a => [CV] -> Maybe (a, [CV])
parseCVs (CV _ (CUserSort (_, s)) : r) = Just (read s, r)
parseCVs _ = Nothing
genParse :: Integral a => Kind -> [CV] -> Maybe (a, [CV])
genParse k (x@(CV _ (CInteger i)):r) | kindOf x == k = Just (fromIntegral i, r)
genParse _ _ = Nothing
instance SatModel () where
parseCVs xs = return ((), xs)
instance SatModel Bool where
parseCVs xs = do (x, r) <- genParse KBool xs
return ((x :: Integer) /= 0, r)
instance SatModel Word8 where
parseCVs = genParse (KBounded False 8)
instance SatModel Int8 where
parseCVs = genParse (KBounded True 8)
instance SatModel Word16 where
parseCVs = genParse (KBounded False 16)
instance SatModel Int16 where
parseCVs = genParse (KBounded True 16)
instance SatModel Word32 where
parseCVs = genParse (KBounded False 32)
instance SatModel Int32 where
parseCVs = genParse (KBounded True 32)
instance SatModel Word64 where
parseCVs = genParse (KBounded False 64)
instance SatModel Int64 where
parseCVs = genParse (KBounded True 64)
instance SatModel Integer where
parseCVs = genParse KUnbounded
instance SatModel AlgReal where
parseCVs (CV KReal (CAlgReal i) : r) = Just (i, r)
parseCVs _ = Nothing
instance SatModel Float where
parseCVs (CV KFloat (CFloat i) : r) = Just (i, r)
parseCVs _ = Nothing
instance SatModel Double where
parseCVs (CV KDouble (CDouble i) : r) = Just (i, r)
parseCVs _ = Nothing
instance SatModel CV where
parseCVs (cv : r) = Just (cv, r)
parseCVs [] = Nothing
instance SatModel RoundingMode
instance SatModel a => SatModel [a] where
parseCVs [] = Just ([], [])
parseCVs xs = case parseCVs xs of
Just (a, ys) -> case parseCVs ys of
Just (as, zs) -> Just (a:as, zs)
Nothing -> Just ([], ys)
Nothing -> Just ([], xs)
instance (SatModel a, SatModel b) => SatModel (a, b) where
parseCVs as = do (a, bs) <- parseCVs as
(b, cs) <- parseCVs bs
return ((a, b), cs)
instance (SatModel a, SatModel b, SatModel c) => SatModel (a, b, c) where
parseCVs as = do (a, bs) <- parseCVs as
((b, c), ds) <- parseCVs bs
return ((a, b, c), ds)
instance (SatModel a, SatModel b, SatModel c, SatModel d) => SatModel (a, b, c, d) where
parseCVs as = do (a, bs) <- parseCVs as
((b, c, d), es) <- parseCVs bs
return ((a, b, c, d), es)
instance (SatModel a, SatModel b, SatModel c, SatModel d, SatModel e) => SatModel (a, b, c, d, e) where
parseCVs as = do (a, bs) <- parseCVs as
((b, c, d, e), fs) <- parseCVs bs
return ((a, b, c, d, e), fs)
instance (SatModel a, SatModel b, SatModel c, SatModel d, SatModel e, SatModel f) => SatModel (a, b, c, d, e, f) where
parseCVs as = do (a, bs) <- parseCVs as
((b, c, d, e, f), gs) <- parseCVs bs
return ((a, b, c, d, e, f), gs)
instance (SatModel a, SatModel b, SatModel c, SatModel d, SatModel e, SatModel f, SatModel g) => SatModel (a, b, c, d, e, f, g) where
parseCVs as = do (a, bs) <- parseCVs as
((b, c, d, e, f, g), hs) <- parseCVs bs
return ((a, b, c, d, e, f, g), hs)
class Modelable a where
modelExists :: a -> Bool
getModelAssignment :: SatModel b => a -> Either String (Bool, b)
getModelDictionary :: a -> M.Map String CV
getModelValue :: SymVal b => String -> a -> Maybe b
getModelValue v r = fromCV `fmap` (v `M.lookup` getModelDictionary r)
getModelUninterpretedValue :: String -> a -> Maybe String
getModelUninterpretedValue v r = case v `M.lookup` getModelDictionary r of
Just (CV _ (CUserSort (_, s))) -> Just s
_ -> Nothing
extractModel :: SatModel b => a -> Maybe b
extractModel a = case getModelAssignment a of
Right (_, b) -> Just b
_ -> Nothing
getModelObjectives :: a -> M.Map String GeneralizedCV
getModelObjectiveValue :: String -> a -> Maybe GeneralizedCV
getModelObjectiveValue v r = v `M.lookup` getModelObjectives r
getModelUIFuns :: a -> M.Map String (SBVType, ([([CV], CV)], CV))
getModelUIFunValue :: String -> a -> Maybe (SBVType, ([([CV], CV)], CV))
getModelUIFunValue v r = v `M.lookup` getModelUIFuns r
extractModels :: SatModel a => AllSatResult -> [a]
extractModels (AllSatResult (_, _, _, xs)) = [ms | Right (_, ms) <- map getModelAssignment xs]
getModelDictionaries :: AllSatResult -> [M.Map String CV]
getModelDictionaries (AllSatResult (_, _, _, xs)) = map getModelDictionary xs
getModelValues :: SymVal b => String -> AllSatResult -> [Maybe b]
getModelValues s (AllSatResult (_, _, _, xs)) = map (s `getModelValue`) xs
getModelUninterpretedValues :: String -> AllSatResult -> [Maybe String]
getModelUninterpretedValues s (AllSatResult (_, _, _, xs)) = map (s `getModelUninterpretedValue`) xs
instance Modelable ThmResult where
getModelAssignment (ThmResult r) = getModelAssignment r
modelExists (ThmResult r) = modelExists r
getModelDictionary (ThmResult r) = getModelDictionary r
getModelObjectives (ThmResult r) = getModelObjectives r
getModelUIFuns (ThmResult r) = getModelUIFuns r
instance Modelable SatResult where
getModelAssignment (SatResult r) = getModelAssignment r
modelExists (SatResult r) = modelExists r
getModelDictionary (SatResult r) = getModelDictionary r
getModelObjectives (SatResult r) = getModelObjectives r
getModelUIFuns (SatResult r) = getModelUIFuns r
instance Modelable SMTResult where
getModelAssignment (Unsatisfiable _ _ ) = Left "SBV.getModelAssignment: Unsatisfiable result"
getModelAssignment (Satisfiable _ m ) = Right (False, parseModelOut m)
getModelAssignment (SatExtField _ _ ) = Left "SBV.getModelAssignment: The model is in an extension field"
getModelAssignment (Unknown _ m ) = Left $ "SBV.getModelAssignment: Solver state is unknown: " ++ show m
getModelAssignment (ProofError _ s _) = error $ unlines $ "SBV.getModelAssignment: Failed to produce a model: " : s
modelExists Satisfiable{} = True
modelExists Unknown{} = False
modelExists _ = False
getModelDictionary Unsatisfiable{} = M.empty
getModelDictionary (Satisfiable _ m) = M.fromList (modelAssocs m)
getModelDictionary SatExtField{} = M.empty
getModelDictionary Unknown{} = M.empty
getModelDictionary ProofError{} = M.empty
getModelObjectives Unsatisfiable{} = M.empty
getModelObjectives (Satisfiable _ m) = M.fromList (modelObjectives m)
getModelObjectives (SatExtField _ m) = M.fromList (modelObjectives m)
getModelObjectives Unknown{} = M.empty
getModelObjectives ProofError{} = M.empty
getModelUIFuns Unsatisfiable{} = M.empty
getModelUIFuns (Satisfiable _ m) = M.fromList (modelUIFuns m)
getModelUIFuns (SatExtField _ m) = M.fromList (modelUIFuns m)
getModelUIFuns Unknown{} = M.empty
getModelUIFuns ProofError{} = M.empty
parseModelOut :: SatModel a => SMTModel -> a
parseModelOut m = case parseCVs [c | (_, c) <- modelAssocs m] of
Just (x, []) -> x
Just (_, ys) -> error $ "SBV.parseModelOut: Partially constructed model; remaining elements: " ++ show ys
Nothing -> error $ "SBV.parseModelOut: Cannot construct a model from: " ++ show m
displayModels :: SatModel a => (Int -> (Bool, a) -> IO ()) -> AllSatResult -> IO Int
displayModels disp (AllSatResult (_, _, _, ms)) = do
inds <- zipWithM display [a | Right a <- map (getModelAssignment . SatResult) ms] [(1::Int)..]
return $ last (0:inds)
where display r i = disp i r >> return i
showSMTResult :: String -> String -> String -> String -> String -> SMTResult -> String
showSMTResult unsatMsg unkMsg satMsg satMsgModel satExtMsg result = case result of
Unsatisfiable _ uc -> unsatMsg ++ showUnsatCore uc
Satisfiable _ (SMTModel _ _ [] []) -> satMsg
Satisfiable _ m -> satMsgModel ++ showModel cfg m
SatExtField _ (SMTModel b _ _ _) -> satExtMsg ++ showModelDictionary True False cfg b
Unknown _ r -> unkMsg ++ ".\n" ++ " Reason: " `alignPlain` show r
ProofError _ [] Nothing -> "*** An error occurred. No additional information available. Try running in verbose mode."
ProofError _ ls Nothing -> "*** An error occurred.\n" ++ intercalate "\n" (map ("*** " ++) ls)
ProofError _ ls (Just r) -> intercalate "\n" $ [ "*** " ++ l | l <- ls]
++ [ "***"
, "*** Alleged model:"
, "***"
]
++ ["*** " ++ l | l <- lines (showSMTResult unsatMsg unkMsg satMsg satMsgModel satExtMsg r)]
where cfg = resultConfig result
showUnsatCore Nothing = ""
showUnsatCore (Just xs) = ". Unsat core:\n" ++ intercalate "\n" [" " ++ x | x <- xs]
showModel :: SMTConfig -> SMTModel -> String
showModel cfg model
| null uiFuncs
= nonUIFuncs
| True
= sep nonUIFuncs ++ intercalate "\n\n" (map (showModelUI cfg) uiFuncs)
where nonUIFuncs = showModelDictionary (null uiFuncs) False cfg [(n, RegularCV c) | (n, c) <- modelAssocs model]
uiFuncs = modelUIFuns model
sep "" = ""
sep x = x ++ "\n\n"
showModelDictionary :: Bool -> Bool -> SMTConfig -> [(String, GeneralizedCV)] -> String
showModelDictionary warnEmpty includeEverything cfg allVars
| null allVars
= warn "[There are no variables bound by the model.]"
| null relevantVars
= warn "[There are no model-variables bound by the model.]"
| True
= intercalate "\n" . display . map shM $ relevantVars
where warn s = if warnEmpty then s else ""
relevantVars = filter (not . ignore) allVars
ignore (s, _)
| includeEverything = False
| True = "__internal_sbv_" `isPrefixOf` s || isNonModelVar cfg s
shM (s, RegularCV v) = let vs = shCV cfg v in ((length s, s), (vlength vs, vs))
shM (s, other) = let vs = show other in ((length s, s), (vlength vs, vs))
display svs = map line svs
where line ((_, s), (_, v)) = " " ++ right (nameWidth - length s) s ++ " = " ++ left (valWidth - lTrimRight (valPart v)) v
nameWidth = maximum $ 0 : [l | ((l, _), _) <- svs]
valWidth = maximum $ 0 : [l | (_, (l, _)) <- svs]
right p s = s ++ replicate p ' '
left p s = replicate p ' ' ++ s
vlength s = case dropWhile (/= ':') (reverse (takeWhile (/= '\n') s)) of
(':':':':r) -> length (dropWhile isSpace r)
_ -> length s
valPart "" = ""
valPart (':':':':_) = ""
valPart (x:xs) = x : valPart xs
lTrimRight = length . dropWhile isSpace . reverse
showModelUI :: SMTConfig -> (String, (SBVType, ([([CV], CV)], CV))) -> String
showModelUI cfg (nm, (SBVType ts, (defs, dflt))) = intercalate "\n" [" " ++ l | l <- sig : map align body]
where noOfArgs = length ts - 1
sig = nm ++ " :: " ++ intercalate " -> " (map showBaseKind ts)
ls = map line defs
defLine = (nm : replicate noOfArgs "_", scv dflt)
body = ls ++ [defLine]
colWidths = [maximum (0 : map length col) | col <- transpose (map fst body)]
resWidth = maximum (0 : map (length . snd) body)
align (xs, r) = unwords $ zipWith left colWidths xs ++ ["=", left resWidth r]
where left i x = take i (x ++ repeat ' ')
scv = sh (printBase cfg)
where sh 2 = binP
sh 10 = showCV False
sh 16 = hexP
sh _ = show
line :: ([CV], CV) -> ([String], String)
line (args, r) = (nm : map (paren . scv) args, scv r)
where
paren :: String -> String
paren x@('-':_) = '(' : x ++ ")"
paren x = x
shCV :: SMTConfig -> CV -> String
shCV = sh . printBase
where sh 2 = binS
sh 10 = show
sh 16 = hexS
sh n = \w -> show w ++ " -- Ignoring unsupported printBase " ++ show n ++ ", use 2, 10, or 16."
pipeProcess :: SMTConfig -> State -> String -> [String] -> String -> (State -> IO a) -> IO a
pipeProcess cfg ctx execName opts pgm continuation = do
mbExecPath <- findExecutable execName
case mbExecPath of
Nothing -> error $ unlines [ "Unable to locate executable for " ++ show (name (solver cfg))
, "Executable specified: " ++ show execName
]
Just execPath -> runSolver cfg ctx execPath opts pgm continuation
`C.catches`
[ C.Handler (\(e :: SBVException) -> C.throwIO e)
, C.Handler (\(e :: C.ErrorCall) -> C.throwIO e)
, C.Handler (\(e :: C.SomeException) -> handleAsync e $ error $ unlines [ "Failed to start the external solver:\n" ++ show e
, "Make sure you can start " ++ show execPath
, "from the command line without issues."
])
]
standardEngine :: String
-> String
-> SMTEngine
standardEngine envName envOptName cfg ctx pgm continuation = do
execName <- getEnv envName `C.catch` (\(e :: C.SomeException) -> handleAsync e (return (executable (solver cfg))))
execOpts <- (splitArgs `fmap` getEnv envOptName) `C.catch` (\(e :: C.SomeException) -> handleAsync e (return (options (solver cfg) cfg)))
let cfg' = cfg {solver = (solver cfg) {executable = execName, options = const execOpts}}
standardSolver cfg' ctx pgm continuation
standardSolver :: SMTConfig
-> State
-> String
-> (State -> IO a)
-> IO a
standardSolver config ctx pgm continuation = do
let msg s = debug config ["** " ++ s]
smtSolver= solver config
exec = executable smtSolver
opts = options smtSolver config
msg $ "Calling: " ++ (exec ++ (if null opts then "" else " ") ++ joinArgs opts)
rnf pgm `seq` pipeProcess config ctx exec opts pgm continuation
data SolverLine = SolverRegular String
| SolverTimeout String
| SolverException String
runSolver :: SMTConfig -> State -> FilePath -> [String] -> String -> (State -> IO a) -> IO a
runSolver cfg ctx execPath opts pgm continuation
= do let nm = show (name (solver cfg))
msg = debug cfg . map ("*** " ++)
clean = preprocess (solver cfg)
(send, ask, getResponseFromSolver, terminateSolver, cleanUp, pid) <- do
(inh, outh, errh, pid) <- runInteractiveProcess execPath opts Nothing Nothing
let
send :: Maybe Int -> String -> IO ()
send mbTimeOut command
| parenDeficit command /= 0
= error $ unlines $ [ ""
, "*** Data.SBV: Unbalanced input detected."
, "***"
, "*** Sending: "
]
++ [ "*** " ++ l | l <- lines command ]
++ [ "***"
, "*** This is most likely an SBV bug. Please report!"
]
| True
= do hPutStrLn inh (clean command)
hFlush inh
recordTranscript (transcript cfg) $ Left (command, mbTimeOut)
ask :: Maybe Int -> String -> IO String
ask mbTimeOut command =
let cmd = dropWhile isSpace command
in if null cmd || ";" `isPrefixOf` cmd
then return "success"
else do send mbTimeOut command
getResponseFromSolver (Just command) mbTimeOut
getResponseFromSolver :: Maybe String -> Maybe Int -> IO String
getResponseFromSolver mbCommand mbTimeOut = do
response <- go True 0 []
let collated = intercalate "\n" $ reverse response
recordTranscript (transcript cfg) $ Right collated
return collated
where safeGetLine isFirst h =
let timeOutToUse | isFirst = mbTimeOut
| True = Just 5000000
timeOutMsg t | isFirst = "User specified timeout of " ++ showTimeoutValue t ++ " exceeded"
| True = "A multiline complete response wasn't received before " ++ showTimeoutValue t ++ " exceeded"
getFullLine :: IO String
getFullLine = intercalate "\n" . reverse <$> collect False []
where collect inString sofar = do ln <- hGetLine h
let walk inside [] = inside
walk inside ('"':cs) = walk (not inside) cs
walk inside (_:cs) = walk inside cs
stillInside = walk inString ln
sofar' = ln : sofar
if stillInside
then collect True sofar'
else return sofar'
in case timeOutToUse of
Nothing -> SolverRegular <$> getFullLine
Just t -> do r <- Timeout.timeout t getFullLine
case r of
Just l -> return $ SolverRegular l
Nothing -> return $ SolverTimeout $ timeOutMsg t
go isFirst i sofar = do
errln <- safeGetLine isFirst outh `C.catch` (\(e :: C.SomeException) -> handleAsync e (return (SolverException (show e))))
case errln of
SolverRegular ln -> let need = i + parenDeficit ln
empty = case dropWhile isSpace ln of
[] -> True
(';':_) -> True
_ -> False
in case (empty, need <= 0) of
(True, _) -> do debug cfg ["[SKIP] " `alignPlain` ln]
go isFirst need sofar
(False, False) -> go False need (ln:sofar)
(False, True) -> return (ln:sofar)
SolverException e -> do terminateProcess pid
C.throwIO SBVException { sbvExceptionDescription = e
, sbvExceptionSent = mbCommand
, sbvExceptionExpected = Nothing
, sbvExceptionReceived = Just $ unlines (reverse sofar)
, sbvExceptionStdOut = Nothing
, sbvExceptionStdErr = Nothing
, sbvExceptionExitCode = Nothing
, sbvExceptionConfig = cfg { solver = (solver cfg) { executable = execPath } }
, sbvExceptionReason = Nothing
, sbvExceptionHint = Nothing
}
SolverTimeout e -> do terminateProcess pid
C.throwIO SBVException { sbvExceptionDescription = "Timeout! " ++ e
, sbvExceptionSent = mbCommand
, sbvExceptionExpected = Nothing
, sbvExceptionReceived = Just $ unlines (reverse sofar)
, sbvExceptionStdOut = Nothing
, sbvExceptionStdErr = Nothing
, sbvExceptionExitCode = Nothing
, sbvExceptionConfig = cfg { solver = (solver cfg) { executable = execPath } }
, sbvExceptionReason = Nothing
, sbvExceptionHint = if not (verbose cfg)
then Just ["Run with 'verbose=True' for further information"]
else Nothing
}
terminateSolver = do hClose inh
outMVar <- newEmptyMVar
out <- hGetContents outh `C.catch` (\(e :: C.SomeException) -> handleAsync e (return (show e)))
_ <- forkIO $ C.evaluate (length out) >> putMVar outMVar ()
err <- hGetContents errh `C.catch` (\(e :: C.SomeException) -> handleAsync e (return (show e)))
_ <- forkIO $ C.evaluate (length err) >> putMVar outMVar ()
takeMVar outMVar
takeMVar outMVar
hClose outh `C.catch` (\(e :: C.SomeException) -> handleAsync e (return ()))
hClose errh `C.catch` (\(e :: C.SomeException) -> handleAsync e (return ()))
ex <- waitForProcess pid `C.catch` (\(e :: C.SomeException) -> handleAsync e (return (ExitFailure (-999))))
return (out, err, ex)
cleanUp
= do (out, err, ex) <- terminateSolver
msg $ [ "Solver : " ++ nm
, "Exit code: " ++ show ex
]
++ [ "Std-out : " ++ intercalate "\n " (lines out) | not (null out)]
++ [ "Std-err : " ++ intercalate "\n " (lines err) | not (null err)]
finalizeTranscript (transcript cfg) ex
recordEndTime cfg ctx
case ex of
ExitSuccess -> return ()
_ -> if ignoreExitCode cfg
then msg ["Ignoring non-zero exit code of " ++ show ex ++ " per user request!"]
else C.throwIO SBVException { sbvExceptionDescription = "Failed to complete the call to " ++ nm
, sbvExceptionSent = Nothing
, sbvExceptionExpected = Nothing
, sbvExceptionReceived = Nothing
, sbvExceptionStdOut = Just out
, sbvExceptionStdErr = Just err
, sbvExceptionExitCode = Just ex
, sbvExceptionConfig = cfg { solver = (solver cfg) { executable = execPath } }
, sbvExceptionReason = Nothing
, sbvExceptionHint = if not (verbose cfg)
then Just ["Run with 'verbose=True' for further information"]
else Nothing
}
return (send, ask, getResponseFromSolver, terminateSolver, cleanUp, pid)
let executeSolver = do let sendAndGetSuccess :: Maybe Int -> String -> IO ()
sendAndGetSuccess mbTimeOut l
| not (supportsCustomQueries (capabilities (solver cfg)))
= do send mbTimeOut l
debug cfg ["[ISSUE] " `alignPlain` l]
| True
= do r <- ask mbTimeOut l
case words r of
["success"] -> debug cfg ["[GOOD] " `alignPlain` l]
_ -> do debug cfg ["[FAIL] " `alignPlain` l]
let isOption = "(set-option" `isPrefixOf` dropWhile isSpace l
reason | isOption = [ "Backend solver reports it does not support this option."
, "Check the spelling, and if correct please report this as a"
, "bug/feature request with the solver!"
]
| True = [ "Check solver response for further information. If your code is correct,"
, "please report this as an issue either with SBV or the solver itself!"
]
mbExtras <- (Right <$> getResponseFromSolver Nothing (Just 5000000))
`C.catch` (\(e :: C.SomeException) -> handleAsync e (return (Left (show e))))
let extras = case mbExtras of
Left _ -> []
Right xs -> xs
(outOrig, errOrig, ex) <- terminateSolver
let out = intercalate "\n" . lines $ outOrig
err = intercalate "\n" . lines $ errOrig
exc = SBVException { sbvExceptionDescription = "Unexpected non-success response from " ++ nm
, sbvExceptionSent = Just l
, sbvExceptionExpected = Just "success"
, sbvExceptionReceived = Just $ r ++ "\n" ++ extras
, sbvExceptionStdOut = Just out
, sbvExceptionStdErr = Just err
, sbvExceptionExitCode = Just ex
, sbvExceptionConfig = cfg { solver = (solver cfg) {executable = execPath } }
, sbvExceptionReason = Just reason
, sbvExceptionHint = Nothing
}
C.throwIO exc
sendAndGetSuccess Nothing "; Automatically generated by SBV. Do not edit."
let backend = name $ solver cfg
if not (supportsCustomQueries (capabilities (solver cfg)))
then debug cfg ["** Skipping heart-beat for the solver " ++ show backend]
else do let heartbeat = "(set-option :print-success true)"
r <- ask (Just 5000000) heartbeat
case words r of
["success"] -> debug cfg ["[GOOD] " ++ heartbeat]
["unsupported"] -> error $ unlines [ ""
, "*** Backend solver (" ++ show backend ++ ") does not support the command:"
, "***"
, "*** (set-option :print-success true)"
, "***"
, "*** SBV relies on this feature to coordinate communication!"
, "*** Please request this as a feature!"
]
_ -> error $ unlines [ ""
, "*** Data.SBV: Failed to initiate contact with the solver!"
, "***"
, "*** Sent : " ++ heartbeat
, "*** Expected: success"
, "*** Received: " ++ r
, "***"
, "*** Try running in debug mode for further information."
]
if not (supportsGlobalDecls (capabilities (solver cfg)))
then debug cfg [ "** Backend solver " ++ show backend ++ " does not support global decls."
, "** Some incremental calls, such as pop, will be limited."
]
else sendAndGetSuccess Nothing "(set-option :global-declarations true)"
mapM_ (sendAndGetSuccess Nothing) (mergeSExpr (lines pgm))
let qs = QueryState { queryAsk = ask
, querySend = send
, queryRetrieveResponse = getResponseFromSolver Nothing
, queryConfig = cfg
, queryTerminate = cleanUp
, queryTimeOutValue = Nothing
, queryAssertionStackDepth = 0
, queryTblArrPreserveIndex = Nothing
}
qsp = rQueryState ctx
mbQS <- readIORef qsp
case mbQS of
Nothing -> writeIORef qsp (Just qs)
Just _ -> error $ unlines [ ""
, "Data.SBV: Impossible happened, query-state was already set."
, "Please report this as a bug!"
]
continuation ctx
let launchSolver = do startTranscript (transcript cfg) cfg
executeSolver
launchSolver `C.catch` (\(e :: C.SomeException) -> handleAsync e $ do terminateProcess pid
ec <- waitForProcess pid
recordException (transcript cfg) (show e)
finalizeTranscript (transcript cfg) ec
recordEndTime cfg ctx
C.throwIO e)
recordEndTime :: SMTConfig -> State -> IO ()
recordEndTime SMTConfig{timing} state = case timing of
NoTiming -> return ()
PrintTiming -> do e <- elapsed
putStrLn $ "*** SBV: Elapsed time: " ++ showTDiff e
SaveTiming here -> writeIORef here =<< elapsed
where elapsed = getCurrentTime >>= \end -> return $ diffUTCTime end (startTime state)
startTranscript :: Maybe FilePath -> SMTConfig -> IO ()
startTranscript Nothing _ = return ()
startTranscript (Just f) cfg = do ts <- show <$> getZonedTime
mbExecPath <- findExecutable (executable (solver cfg))
writeFile f $ start ts mbExecPath
where SMTSolver{name, options} = solver cfg
start ts mbPath = unlines [ ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"
, ";;; SBV: Starting at " ++ ts
, ";;;"
, ";;; Solver : " ++ show name
, ";;; Executable: " ++ fromMaybe "Unable to locate the executable" mbPath
, ";;; Options : " ++ unwords (options cfg)
, ";;;"
, ";;; This file is an auto-generated loadable SMT-Lib file."
, ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"
, ""
]
finalizeTranscript :: Maybe FilePath -> ExitCode -> IO ()
finalizeTranscript Nothing _ = return ()
finalizeTranscript (Just f) ec = do ts <- show <$> getZonedTime
appendFile f $ end ts
where end ts = unlines [ ""
, ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"
, ";;;"
, ";;; SBV: Finished at " ++ ts
, ";;;"
, ";;; Exit code: " ++ show ec
, ";;;"
, ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"
]
recordTranscript :: Maybe FilePath -> Either (String, Maybe Int) String -> IO ()
recordTranscript Nothing _ = return ()
recordTranscript (Just f) m = do tsPre <- formatTime defaultTimeLocale "; [%T%Q" <$> getZonedTime
let ts = take 15 $ tsPre ++ repeat '0'
case m of
Left (sent, mbTimeOut) -> appendFile f $ unlines $ (ts ++ "] " ++ to mbTimeOut ++ "Sending:") : lines sent
Right recv -> appendFile f $ unlines $ case lines (dropWhile isSpace recv) of
[] -> [ts ++ "] Received: <NO RESPONSE>"]
[x] -> [ts ++ "] Received: " ++ x]
xs -> (ts ++ "] Received: ") : map ("; " ++) xs
where to Nothing = ""
to (Just i) = "[Timeout: " ++ showTimeoutValue i ++ "] "
{-# INLINE recordTranscript #-}
recordException :: Maybe FilePath -> String -> IO ()
recordException Nothing _ = return ()
recordException (Just f) m = do ts <- show <$> getZonedTime
appendFile f $ exc ts
where exc ts = unlines $ [ ""
, ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"
, ";;;"
, ";;; SBV: Caught an exception at " ++ ts
, ";;;"
]
++ [ ";;; " ++ l | l <- dropWhile null (lines m) ]
++ [ ";;;"
, ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"
]
handleAsync :: C.SomeException -> IO a -> IO a
handleAsync e cont
| isAsynchronous = C.throwIO e
| True = cont
where
isAsynchronous :: Bool
isAsynchronous = isJust (C.fromException e :: Maybe C.AsyncException) || isJust (C.fromException e :: Maybe C.SomeAsyncException)