{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Data.SBV.Tools.WeakestPreconditions (
Program(..), Stmt(..), assert, stable
, Invariant, Measure, Stable
, VC(..)
, ProofResult(..)
, WPConfig(..), defaultWPCfg
, wpProve, wpProveWith
, traceExecution, Status(..)
) where
import Data.List (intercalate)
import Data.Maybe (fromJust, isJust, isNothing)
import Control.Monad (when)
import Data.SBV
import Data.SBV.Control
data Program st = Program { setup :: Symbolic ()
, precondition :: st -> SBool
, program :: Stmt st
, postcondition :: st -> SBool
, stability :: Stable st
}
type Stable st = [st -> st -> (String, SBool)]
type Invariant st = st -> SBool
type Measure st = st -> [SInteger]
data Stmt st = Skip
| Abort String
| Assign (st -> st)
| If (st -> SBool) (Stmt st) (Stmt st)
| While String (Invariant st) (Maybe (Measure st)) (st -> SBool) (Stmt st)
| Seq [Stmt st]
assert :: String -> (st -> SBool) -> Stmt st
assert nm cond = If cond Skip (Abort nm)
stable :: EqSymbolic a => String -> (st -> a) -> st -> st -> (String, SBool)
stable nm f before after = (nm, f before .=== f after)
isTotal :: Stmt st -> Bool
isTotal Skip = True
isTotal (Abort _) = True
isTotal (Assign _) = True
isTotal (If _ tb fb) = all isTotal [tb, fb]
isTotal (While _ _ msr _ s) = isJust msr && isTotal s
isTotal (Seq ss) = all isTotal ss
data VC st m = BadPrecondition st
| BadPostcondition st st
| Unstable String st st
| AbortReachable String st st
| InvariantPre String st
| InvariantMaintain String st st
| MeasureBound String (st, [m])
| MeasureDecrease String (st, [m]) (st, [m])
dispVC :: String -> [(String, String)] -> String
dispVC tag flds = intercalate "\n" $ col tag : map showField flds
where col "" = ""
col t = t ++ ":"
showField (t, c) = intercalate "\n" $ zipWith mark [(1::Int)..] (lines c)
where tt = if null t then "" else col t ++ " "
sp = replicate (length tt) ' '
mark i s = " " ++ (if i == 1 then tt else sp) ++ s
showMeasure :: Show a => [a] -> String
showMeasure [x] = show x
showMeasure xs = show xs
instance (Show st, Show m) => Show (VC st m) where
show (BadPrecondition s) = dispVC "Precondition fails"
[("", show s)]
show (BadPostcondition s1 s2) = dispVC "Postcondition fails"
[ ("Start", show s1)
, ("End ", show s2)
]
show (Unstable m s1 s2) = dispVC ("Stability fails for " ++ show m)
[ ("Before", show s1)
, ("After ", show s2)
]
show (AbortReachable nm s1 s2) = dispVC ("Abort " ++ show nm ++ " condition is satisfiable")
[ ("Before", show s1)
, ("After ", show s2)
]
show (InvariantPre nm s) = dispVC ("Invariant for loop " ++ show nm ++ " fails upon entry")
[("", show s)]
show (InvariantMaintain nm s1 s2) = dispVC ("Invariant for loop " ++ show nm ++ " is not maintaned by the body")
[ ("Before", show s1)
, ("After ", show s2)
]
show (MeasureBound nm (s, m)) = dispVC ("Measure for loop " ++ show nm ++ " is negative")
[ ("State ", show s)
, ("Measure", showMeasure m )
]
show (MeasureDecrease nm (s1, m1) (s2, m2)) = dispVC ("Measure for loop " ++ show nm ++ " does not decrease")
[ ("Before ", show s1)
, ("Measure", showMeasure m1)
, ("After ", show s2)
, ("Measure", showMeasure m2)
]
data ProofResult res = Proven Bool
| Indeterminate String
| Failed [VC res Integer]
instance Show res => Show (ProofResult res) where
show (Proven True) = "Q.E.D."
show (Proven False) = "Q.E.D. [Partial: not all termination measures were provided.]"
show (Indeterminate s) = "Indeterminate: " ++ s
show (Failed vcs) = intercalate "\n" $ ("Proof failure. Failing verification condition" ++ if length vcs > 1 then "s:" else ":")
: map (\vc -> intercalate "\n" [" " ++ l | l <- lines (show vc)]) vcs
wpProveWith :: forall st res. (Show res, Mergeable st, Queriable IO st res) => WPConfig -> Program st -> IO (ProofResult res)
wpProveWith cfg@WPConfig{wpVerbose} Program{setup, precondition, program, postcondition, stability} =
runSMTWith (wpSolver cfg) $ do setup
query q
where q = do start <- create
weakestPrecondition <- wp start program (\st -> [(postcondition st, BadPostcondition start st)])
let vcs = weakestPrecondition start
constrain $ sNot $ precondition start .=> sAnd (map fst vcs)
cs <- checkSat
case cs of
Unk -> Indeterminate . show <$> getUnknownReason
Unsat -> do let t = isTotal program
if t then msg "Total correctness is established."
else msg "Partial correctness is established."
return $ Proven t
Sat -> do let checkVC :: (SBool, VC st SInteger) -> Query [VC res Integer]
checkVC (cond, vc) = do c <- getValue cond
if c
then return []
else do vc' <- case vc of
BadPrecondition s -> BadPrecondition <$> project s
BadPostcondition s1 s2 -> BadPostcondition <$> project s1 <*> project s2
Unstable l s1 s2 -> Unstable l <$> project s1 <*> project s2
AbortReachable l s1 s2 -> AbortReachable l <$> project s1 <*> project s2
InvariantPre l s -> InvariantPre l <$> project s
InvariantMaintain l s1 s2 -> InvariantMaintain l <$> project s1 <*> project s2
MeasureBound l (s, m) -> do r <- project s
v <- mapM getValue m
return $ MeasureBound l (r, v)
MeasureDecrease l (s1, i1) (s2, i2) -> do r1 <- project s1
v1 <- mapM getValue i1
r2 <- project s2
v2 <- mapM getValue i2
return $ MeasureDecrease l (r1, v1) (r2, v2)
return [vc']
badVCs <- concat <$> mapM checkVC vcs
when (null badVCs) $ error "Data.SBV.proveWP: Impossible happened. Proof failed, but no failing VC found!"
let plu w (_:_:_) = w ++ "s"
plu w _ = w
m = "Following proof " ++ plu "obligation" badVCs ++ " failed:"
msg m
msg $ replicate (length m) '='
let disp c = mapM_ msg [" " ++ l | l <- lines (show c)]
mapM_ disp badVCs
return $ Failed badVCs
msg = io . when wpVerbose . putStrLn
wp :: st -> Stmt st -> (st -> [(SBool, VC st SInteger)]) -> Query (st -> [(SBool, VC st SInteger)])
wp _ Skip post = return post
wp start (Abort nm) _ = return $ \st -> [(sFalse, AbortReachable nm start st)]
wp _ (Assign f) post = return $ \st -> let st' = f st
vcs = map (\s -> let (nm, b) = s st st' in (b, Unstable nm st st')) stability
in vcs ++ post st'
wp start (If c tb fb) post = do tWP <- wp start tb post
fWP <- wp start fb post
return $ \st -> let cond = c st
in [( cond .=> b, v) | (b, v) <- tWP st]
++ [(sNot cond .=> b, v) | (b, v) <- fWP st]
wp _ (Seq []) post = return post
wp start (Seq (s:ss)) post = wp start s =<< wp start (Seq ss) post
wp start (While nm inv mm cond body) post = do
st' <- create
let noMeasure = isNothing mm
m = fromJust mm
curM = m st'
zero = map (const 0) curM
iterates = inv st' .&& cond st'
terminates = inv st' .&& sNot (cond st')
invHoldsPrior <- wp start Skip (\st -> [(inv st, InvariantPre nm st)])
invMaintained <- wp st' body (\st -> [(iterates .=> inv st, InvariantMaintain nm st' st)])
invEstablish <- wp st' body (const [(terminates .=> b, v) | (b, v) <- post st'])
measureNonNegative <- if noMeasure
then return (const [])
else wp st' Skip (const [(iterates .=> curM .>= zero, MeasureBound nm (st', curM))])
measureDecreases <- if noMeasure
then return (const [])
else wp st' body (\st -> let prevM = m st in [(iterates .=> prevM .< curM, MeasureDecrease nm (st', curM) (st, prevM))])
return $ \st -> invHoldsPrior st
++ invMaintained st'
++ invEstablish st'
++ measureNonNegative st'
++ measureDecreases st'
wpProve :: (Show res, Mergeable st, Queriable IO st res) => Program st -> IO (ProofResult res)
wpProve = wpProveWith defaultWPCfg
data WPConfig = WPConfig { wpSolver :: SMTConfig
, wpVerbose :: Bool
}
defaultWPCfg :: WPConfig
defaultWPCfg = WPConfig { wpSolver = defaultSMTCfg
, wpVerbose = False
}
data Location = Line Int
| Iteration Int
type Loc = [Location]
data Status st = Good st
| Stuck (VC st Integer)
instance Show st => Show (Status st) where
show (Good st) = "Program terminated successfully. Final state:\n" ++ intercalate "\n" [" " ++ l | l <- lines (show st)]
show (Stuck vc) = "Program is stuck.\n" ++ show vc
traceExecution :: forall st. Show st
=> Program st
-> st
-> IO (Status st)
traceExecution Program{precondition, program, postcondition, stability} start = do
status <- if unwrap [] "checking precondition" (precondition start)
then go [Line 1] program =<< step [] start "*** Precondition holds, starting execution:"
else giveUp start (BadPrecondition start) "*** Initial state does not satisfy the precondition:"
case status of
s@Stuck{} -> return s
Good end -> if unwrap [] "checking postcondition" (postcondition end)
then step [] end "*** Program successfully terminated, post condition holds of the final state:"
else giveUp end (BadPostcondition start end) "*** Failed, final state does not satisfy the postcondition:"
where sLoc :: Loc -> String -> String
sLoc l m
| null l = m
| True = "===> [" ++ intercalate "." (map sh (reverse l)) ++ "] " ++ m
where sh (Line i) = show i
sh (Iteration i) = "{" ++ show i ++ "}"
step :: Loc -> st -> String -> IO (Status st)
step l st m = do putStrLn $ sLoc l m
printST st
return $ Good st
stop :: Loc -> VC st Integer -> String -> IO (Status st)
stop l vc m = do putStrLn $ sLoc l m
return $ Stuck vc
giveUp :: st -> VC st Integer -> String -> IO (Status st)
giveUp st vc m = do r <- stop [] vc m
printST st
return r
dispST :: st -> String
dispST st = intercalate "\n" [" " ++ l | l <- lines (show st)]
printST :: st -> IO ()
printST = putStrLn . dispST
unwrap :: SymVal a => Loc -> String -> SBV a -> a
unwrap l m v = case unliteral v of
Just c -> c
Nothing -> error $ unlines [ ""
, "*** Data.SBV.WeakestPreconditions.traceExecution:"
, "***"
, "*** Unable to extract concrete value:"
, "*** " ++ sLoc l m
, "***"
, "*** Make sure the starting state is fully concrete and"
, "*** there are no uninterpreted functions in play!"
]
go :: Loc -> Stmt st -> Status st -> IO (Status st)
go _ _ s@Stuck{} = return s
go loc p (Good st) = analyze p
where analyze Skip = step loc st "Skip"
analyze (Abort nm) = stop loc (AbortReachable nm start st) $ "Abort command executed, labeled: " ++ show nm
analyze (Assign f) = case [nm | s <- stability, let (nm, b) = s st st', not (unwrap loc ("evaluation stability condition " ++ show nm) b)] of
[] -> step loc st' "Assign"
nms -> let comb = intercalate ", " nms
bad = Unstable comb st st'
in stop loc bad $ "Stability condition fails for: " ++ show comb
where st' = f st
analyze (If c tb eb)
| branchTrue = go (Line 1 : loc) tb =<< step loc st "Conditional, taking the \"then\" branch"
| True = go (Line 2 : loc) eb =<< step loc st "Conditional, taking the \"else\" branch"
where branchTrue = unwrap loc "evaluating the test condition" (c st)
analyze (Seq stmts) = walk stmts 1 (Good st)
where walk [] _ is = return is
walk (s:ss) c is = walk ss (c+1) =<< go (Line c : loc) s is
analyze (While loopName invariant mbMeasure condition body)
| currentInvariant st
= while 1 st Nothing (Good st)
| True
= stop loc (InvariantPre loopName st) $ tag "invariant fails to hold prior to loop entry"
where tag s = "Loop " ++ show loopName ++ ": " ++ s
hasMeasure = isJust mbMeasure
measure = fromJust mbMeasure
currentCondition = unwrap loc (tag "evaluating the while condition") . condition
currentMeasure = map (unwrap loc (tag "evaluating the measure")) . measure
currentInvariant = unwrap loc (tag "evaluating the invariant") . invariant
while _ _ _ s@Stuck{} = return s
while c prevST mbPrev (Good is)
| not (currentCondition is)
= step loc is $ tag "condition fails, terminating"
| not (currentInvariant is)
= stop loc (InvariantMaintain loopName prevST is) $ tag "invariant fails to hold in iteration " ++ show c
| hasMeasure && mCur < zero
= stop loc (MeasureBound loopName (is, mCur)) $ tag "measure must be non-negative, evaluated to: " ++ show mCur
| hasMeasure, Just mPrev <- mbPrev, mCur >= mPrev
= stop loc (MeasureDecrease loopName (prevST, mPrev) (is, mCur)) $ tag $ "measure failed to decrease, prev = " ++ show mPrev ++ ", current = " ++ show mCur
| True
= do nextState <- go (Iteration c : loc) body =<< step loc is (tag "condition holds, executing the body")
while (c+1) is (Just mCur) nextState
where mCur = currentMeasure is
zero = map (const 0) mCur
{-# ANN traceExecution ("HLint: ignore Use fromMaybe" :: String) #-}